From bd3596a68232dc77f91d9d12b36afa82d70acea4 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 17:18:04 -0300 Subject: [PATCH 01/60] feat: full soul war quest --- .../quests/soul_war/aspect_of_power.lua | 25 +- ...nia.lua => goshnar's_megalomania_blue.lua} | 80 +- .../soul_war/goshnar's_megalomania_green.lua | 160 ++ .../soul_war/goshnar's_megalomania_purple.lua | 132 ++ .../quests/soul_war/goshnars_cruelty.lua | 19 +- .../quests/soul_war/goshnars_greed.lua | 40 +- .../quests/soul_war/goshnars_hatred.lua | 17 +- .../quests/soul_war/goshnars_malice.lua | 17 +- .../quests/soul_war/goshnars_spite.lua | 7 +- .../monster/quests/soul_war/greedbeast.lua | 98 ++ .../monster/quests/soul_war/mirror_image.lua | 40 + .../normal_monsters}/bony_sea_devil.lua | 8 + .../normal_monsters}/brachiodemon.lua | 8 + .../normal_monsters}/branchy_crawler.lua | 8 + .../ashes_of_burning_hatred.lua | 89 ++ .../blaze_of_burning_hatred.lua | 89 ++ .../flame_of_burning_hatred.lua | 89 ++ .../spark_of_burning_hatred.lua | 89 ++ .../burning_hatred/symbol_of_hatred.lua | 84 ++ .../normal_monsters}/capricious_phantom.lua | 4 + .../normal_monsters}/distorted_phantom.lua | 4 + .../normal_monsters}/druid's_apparition.lua | 4 + .../furious_crater/a_greedy_eye.lua | 93 ++ .../furious_crater}/cloak_of_terror.lua | 9 + .../furious_crater}/courage_leech.lua | 4 + .../furious_crater/poor_soul.lua | 119 ++ .../furious_crater}/vibrant_phantom.lua | 4 + .../{ => normal_monsters}/hateful_soul.lua | 2 +- .../normal_monsters}/infernal_demon.lua | 4 + .../normal_monsters}/infernal_phantom.lua | 4 + .../normal_monsters}/knight's_apparition.lua | 4 + .../soul_war/normal_monsters}/many_faces.lua | 8 + .../greater_splinter_of_madness.lua | 107 ++ .../lesser_splinter_of_madness.lua | 107 ++ .../mighty_splinter_of_madness.lua | 113 ++ .../megalomania_room/necromantic_focus.lua | 82 + .../normal_monsters}/mould_phantom.lua | 4 + .../normal_monsters}/paladin's_apparition.lua | 4 + .../normal_monsters}/rotten_golem.lua | 4 + .../sorcerer's_apparition.lua | 4 + .../normal_monsters}/turbulent_elemental.lua | 4 + .../monster/quests/soul_war/powerful_soul.lua | 115 ++ .../monster/quests/soul_war/soul_cage.lua | 71 + .../monster/quests/soul_war/soul_sphere.lua | 109 ++ .../monster/quests/soul_war/soulsnatcher.lua | 115 ++ .../quests/soul_war/spiteful_spitter.lua | 4 + .../monster/quests/soul_war/strong_soul.lua | 106 ++ .../monster/quests/soul_war/weak_soul.lua | 106 ++ .../monster/quests/soul_war/weeping_soul.lua | 91 ++ .../monster/undeads/hazardous_phantom.lua | 4 + data-otservbr-global/npc/flickering_soul.lua | 195 +++ .../actions/bosses_levers/goshnar_cruelty.lua | 23 - .../actions/bosses_levers/goshnar_greed.lua | 23 - .../actions/bosses_levers/goshnar_hatred.lua | 23 - .../actions/bosses_levers/goshnar_malice.lua | 23 - .../bosses_levers/goshnar_megalomania.lua | 23 - .../actions/bosses_levers/goshnar_spite.lua | 23 - .../actions/quests/soul_war/bosses_killed.lua | 24 - .../quests/soul_war/portal_megalomania.lua | 38 - .../quests/soul_war/reward_soul_war.lua | 89 -- .../quests/soul_war/soulwar_entrances.lua | 63 - .../scripts/lib/quests/soul-war.lua | 1343 +++++++++++++++++ .../soul_war/action-reward_soul_war.lua | 60 + .../eventcallback_on_combat_taint.lua | 127 ++ ...ntcallback_on_drop_loot_bag_you_desire.lua | 16 + .../globalevent-ebb_and_flow_change_maps.lua | 132 ++ .../soul_war/moveevent-soul_war_entrances.lua | 151 ++ .../moveevent-teleport_entrance_reward.lua} | 7 +- .../quests/soul_war/soul_war_mechanics.lua | 1081 +++++++++++++ .../quests/soul_war/spell-eye_beam.lua | 38 + .../soul_war/spell-fire_beam_cruelty.lua | 61 + .../soul_war/spell-fire_beam_megalomania.lua | 54 + .../soul_war/spell-megalomania_blue.lua | 58 + .../quests/soul_war/spell-soulsnatcher.lua | 58 + .../iron_servant_transformation.lua | 2 +- .../world/otservbr-monster.xml | 965 ++++++------ data-otservbr-global/world/otservbr-npc.xml | 3 + .../ebb_and_flow/ebb-flow-empty-zones.xml | 2 + .../soul_war/ebb_and_flow/ebb-flow-empty.otbm | Bin 0 -> 129535 bytes .../ebb_and_flow/ebb-flow-inundate-zones.xml | 2 + .../{inundate.otbm => ebb-flow-inundate.otbm} | Bin 325937 -> 323339 bytes .../quest/soul_war/ebb_and_flow/empty.otbm | Bin 13957 -> 0 bytes data/items/items.xml | 52 +- data/libs/functions/bosslever.lua | 7 +- data/libs/functions/creature.lua | 2 +- data/libs/functions/revscriptsys.lua | 8 + data/libs/systems/zones.lua | 10 +- data/scripts/actions/items/cobra_flask.lua | 2 +- data/scripts/eventcallbacks/README.md | 17 +- .../creature/on_area_combat.lua | 2 +- .../eventcallbacks/creature/on_hear.lua | 2 +- .../eventcallbacks/monster/on_spawn.lua | 2 +- .../monster/ondroploot__base.lua | 4 +- .../monster/ondroploot_boosted.lua | 2 +- .../monster/ondroploot_gem_atelier.lua | 2 +- .../monster/ondroploot_hazard.lua | 2 +- .../monster/ondroploot_prey.lua | 2 +- .../monster/ondroploot_wealth_duplex.lua | 2 +- .../monster/postdroploot_analyzer.lua | 2 +- .../eventcallbacks/party/on_disband.lua | 2 +- .../eventcallbacks/player/on_browse_field.lua | 2 +- .../scripts/eventcallbacks/player/on_look.lua | 2 +- .../eventcallbacks/player/on_look_in_shop.lua | 2 +- .../player/on_look_in_trade.lua | 2 +- .../eventcallbacks/player/on_remove_count.lua | 2 +- .../player/on_request_quest_line.lua | 2 +- .../player/on_request_quest_log.lua | 2 +- .../eventcallbacks/player/on_rotate_item.lua | 2 +- .../player/on_storage_update.lua | 2 +- .../eventcallbacks/player/on_trade_accept.lua | 2 +- data/scripts/lib/register_lever_tables.lua | 4 +- data/scripts/lib/register_monster_type.lua | 4 +- data/scripts/lib/register_spells.lua | 6 + data/scripts/spells/healing/heal_malice.lua | 29 + .../talkactions/gm/distance_effect.lua | 37 + src/creatures/appearance/outfit/outfit.hpp | 10 + src/creatures/combat/combat.cpp | 10 + src/creatures/creature.cpp | 2 +- src/creatures/creature.hpp | 10 +- src/creatures/creatures_definitions.hpp | 14 +- src/creatures/monsters/monster.cpp | 103 +- src/creatures/monsters/monster.hpp | 35 +- src/creatures/monsters/monsters.cpp | 37 +- src/creatures/monsters/monsters.hpp | 2 + .../monsters/spawns/spawn_monster.cpp | 1 + src/creatures/players/grouping/party.cpp | 2 +- src/creatures/players/player.cpp | 16 +- src/creatures/players/player.hpp | 10 +- src/game/bank/bank.cpp | 4 + src/game/game.cpp | 8 +- src/game/zones/zone.cpp | 5 + src/io/functions/iologindata_load_player.cpp | 7 +- src/io/io_bosstiary.cpp | 1 + src/io/iomap.cpp | 2 +- src/lua/callbacks/callbacks_definitions.hpp | 3 + src/lua/callbacks/event_callback.cpp | 105 +- src/lua/callbacks/event_callback.hpp | 22 +- src/lua/callbacks/events_callbacks.cpp | 28 +- src/lua/callbacks/events_callbacks.hpp | 23 +- src/lua/creature/creatureevent.cpp | 6 +- src/lua/creature/creatureevent.hpp | 2 +- src/lua/creature/events.cpp | 2 +- .../functions/core/game/bank_functions.cpp | 1 + .../functions/core/game/game_functions.cpp | 1 + src/lua/functions/core/game/lua_enums.cpp | 8 +- .../functions/core/game/zone_functions.cpp | 2 +- .../creatures/creature_functions.cpp | 2 +- .../creatures/monster/monster_functions.cpp | 86 +- .../creatures/monster/monster_functions.hpp | 13 + .../monster/monster_type_functions.cpp | 4 +- .../monster/monster_type_functions.hpp | 2 + .../creatures/player/player_functions.cpp | 30 +- .../creatures/player/player_functions.hpp | 4 + .../events/event_callback_functions.cpp | 14 +- src/map/map.cpp | 9 +- src/map/mapcache.cpp | 16 +- 156 files changed, 7053 insertions(+), 1050 deletions(-) rename data-otservbr-global/monster/quests/soul_war/{goshnars_megalomania.lua => goshnar's_megalomania_blue.lua} (63%) create mode 100644 data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/greedbeast.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/bony_sea_devil.lua (96%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/brachiodemon.lua (96%) rename data-otservbr-global/monster/{plants => quests/soul_war/normal_monsters}/branchy_crawler.lua (95%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/capricious_phantom.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/distorted_phantom.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/druid's_apparition.lua (98%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua rename data-otservbr-global/monster/{plants => quests/soul_war/normal_monsters/furious_crater}/cloak_of_terror.lua (95%) rename data-otservbr-global/monster/{extra_dimensional => quests/soul_war/normal_monsters/furious_crater}/courage_leech.lua (98%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters/furious_crater}/vibrant_phantom.lua (98%) rename data-otservbr-global/monster/quests/soul_war/{ => normal_monsters}/hateful_soul.lua (99%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/infernal_demon.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/infernal_phantom.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/knight's_apparition.lua (98%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/many_faces.lua (96%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/mould_phantom.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/paladin's_apparition.lua (98%) rename data-otservbr-global/monster/{constructs => quests/soul_war/normal_monsters}/rotten_golem.lua (98%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/sorcerer's_apparition.lua (98%) rename data-otservbr-global/monster/{elementals => quests/soul_war/normal_monsters}/turbulent_elemental.lua (98%) create mode 100644 data-otservbr-global/monster/quests/soul_war/powerful_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soul_cage.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soul_sphere.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/strong_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/weak_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/weeping_soul.lua create mode 100644 data-otservbr-global/npc/flickering_soul.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua delete mode 100644 data-otservbr-global/scripts/actions/quests/soul_war/bosses_killed.lua delete mode 100644 data-otservbr-global/scripts/actions/quests/soul_war/portal_megalomania.lua delete mode 100644 data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua delete mode 100644 data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua create mode 100644 data-otservbr-global/scripts/lib/quests/soul-war.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua rename data-otservbr-global/scripts/{actions/quests/soul_war/portal_reward_soulwar.lua => quests/soul_war/moveevent-teleport_entrance_reward.lua} (78%) create mode 100644 data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml rename data-otservbr-global/world/quest/soul_war/ebb_and_flow/{inundate.otbm => ebb-flow-inundate.otbm} (83%) delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm create mode 100644 data/scripts/spells/healing/heal_malice.lua create mode 100644 data/scripts/talkactions/gm/distance_effect.lua diff --git a/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua b/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua index 8ff012c96ef..896cb520722 100644 --- a/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua +++ b/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua @@ -4,20 +4,20 @@ local monster = {} monster.description = "an aspect of power" monster.experience = 0 monster.outfit = { - lookType = 1303, + lookType = 1306, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, - lookAddons = 1, + lookAddons = 0, lookMount = 0, } monster.health = 25000 monster.maxHealth = 25000 monster.race = "undead" -monster.corpse = 0 -monster.speed = 125 +monster.corpse = 33949 +monster.speed = 175 monster.manaCost = 0 monster.changeTarget = { @@ -25,8 +25,14 @@ monster.changeTarget = { chance = 8, } +monster.events = { + "SoulWarAspectOfPowerDeath", +} + monster.strategiesTarget = { nearest = 100, + health = 20, + damage = 30, } monster.flags = { @@ -64,16 +70,15 @@ monster.attacks = { { name = "combat", interval = 1700, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -400, maxDamage = -950, radius = 3, shootEffect = CONST_ANI_ENVENOMEDARROW, effect = CONST_ME_HITBYPOISON, target = true }, { name = "combat", interval = 1700, chance = 25, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -850, length = 4, spread = 0, effect = CONST_ME_ENERGYHIT, target = false }, { name = "combat", interval = 1700, chance = 35, type = COMBAT_DEATHDAMAGE, minDamage = -700, maxDamage = -1550, radius = 3, effect = CONST_ME_MORTAREA, target = false }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's hatred" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's greed" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's malice" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's spite" }, } monster.defenses = { defense = 40, - armor = 0, - -- mitigation = ???, + armor = 40, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Malice" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Cruelty" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Hatred" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Spite" }, } monster.elements = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua similarity index 63% rename from data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua rename to data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 7346e628cfc..69bd51a6923 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -1,44 +1,40 @@ -local mType = Game.createMonsterType("Goshnar's Megalomania") +local mType = Game.createMonsterType("Goshnar's Megalomania Blue") local monster = {} +monster.name = "Goshnar's Megalomania" monster.description = "Goshnar's Megalomania" -monster.experience = 200000 +monster.experience = 3000000 monster.outfit = { - lookType = 1308, - lookHead = 0, - lookBody = 0, - lookLegs = 0, - lookFeet = 0, - lookAddons = 0, - lookMount = 0, + lookType = 1337, } -monster.events = { - "SoulwarsBossDeath", -} - -monster.health = 500000 -monster.maxHealth = 500000 +monster.health = 620000 +monster.maxHealth = 620000 monster.race = "undead" monster.corpse = 33889 -monster.speed = 165 +monster.speed = 0 monster.manaCost = 0 - -monster.changeTarget = { - interval = 2000, - chance = 10, -} +monster.maxSummons = 4 monster.bosstiary = { bossRaceId = 1969, bossRace = RARITY_NEMESIS, } +monster.changeTarget = { + interval = 4000, + chance = 10, +} + monster.strategiesTarget = { - nearest = 70, + nearest = 80, health = 10, damage = 10, - random = 10, +} + +monster.events = { + "GoshnarsHatredBuff", + "MegalomaniaDeath", } monster.flags = { @@ -51,7 +47,7 @@ monster.flags = { illusionable = false, canPushItems = true, canPushCreatures = true, - staticAttackChance = 95, + staticAttackChance = 80, targetDistance = 1, runHealth = 0, healthHidden = false, @@ -67,14 +63,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "aspect of power", chance = 50, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -110,19 +98,14 @@ monster.loot = { } monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -8000 }, - { name = "combat", interval = 2000, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -2950, maxDamage = -4400, range = 7, radius = 3, shootEffect = CONST_ANI_DEATH, effect = CONST_ME_MORTAREA, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -3000, maxDamage = -5500, length = 8, spread = 0, effect = CONST_ME_INSECTS, target = false }, - { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -3300, maxDamage = -5500, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -3300, maxDamage = -5200, length = 10, spread = 0, effect = CONST_ME_BLUE_GHOST, target = false }, + { name = "melee", interval = 2000, chance = 100, minDamage = -400, maxDamage = -2225 }, + { name = "megalomania blue", interval = 6000, chance = 100, target = true }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, } monster.defenses = { - defense = 160, - armor = 160, - mitigation = 8.40, - { name = "speed", interval = 1000, chance = 20, speedChange = 500, effect = CONST_ME_MAGIC_RED, target = false, duration = 10000 }, - { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 2250, maxDamage = 4250, effect = CONST_ME_MAGIC_BLUE, target = false }, + defense = 55, + armor = 55, } monster.elements = { @@ -145,18 +128,21 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end - mType.onAppear = function(monster, creature) if monster:getType():isRewardBoss() then monster:setReward(true) end end -mType.onDisappear = function(monster, creature) end +local intervalBetweenExecutions = 10000 -mType.onMove = function(monster, creature, fromPosition, toPosition) end +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() -mType.onSay = function(monster, creature, type, message) end +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua new file mode 100644 index 00000000000..5c6170d3f92 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -0,0 +1,160 @@ +local mType = Game.createMonsterType("Goshnar's Megalomania Green") +local monster = {} + +monster.name = "Goshnar's Megalomania" +monster.description = "Goshnar's Megalomania" +monster.experience = 3000000 +monster.outfit = { + lookType = 99, + lookHead = 95, + lookBody = 116, + lookLegs = 119, + lookFeet = 115, + lookAddons = 0, + lookMount = 0, +} + +monster.bosstiary = { + bossRaceId = 1969, + bossRace = RARITY_NEMESIS, +} + +monster.health = 620000 +monster.maxHealth = 620000 +monster.race = "undead" +monster.corpse = 33889 +monster.speed = 250 +monster.manaCost = 0 +monster.maxSummons = 4 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.events = { + "GoshnarsHatredBuff", + "MegalomaniaDeath", +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.loot = { + { name = "crystal coin", chance = 55000, minCount = 70, maxCount = 75 }, + { id = 281, chance = 1150 }, -- giant shimmering pearl (green) + { name = "giant sapphire", chance = 10000, maxCount = 1 }, + { name = "giant topaz", chance = 10000, maxCount = 1 }, + { name = "violet gem", chance = 6000, maxCount = 1 }, + { name = "blue gem", chance = 10000, maxCount = 3 }, + { id = 3039, chance = 10000, maxCount = 3 }, -- red gem + { name = "green gem", chance = 10000, maxCount = 3 }, + { name = "yellow gem", chance = 10000, maxCount = 3 }, + { name = "white gem", chance = 6000, maxCount = 3 }, + { name = "dragon figurine", chance = 10000, maxCount = 1 }, + { name = "bullseye potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "mastermind potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "berserk potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "ultimate mana potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "supreme health potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "ultimate spirit potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "figurine of malice", chance = 400 }, + { name = "figurine of cruelty", chance = 400 }, + { name = "figurine of hatred", chance = 400 }, + { name = "figurine of greed", chance = 400 }, + { name = "figurine of spite", chance = 400 }, + { name = "figurine of megalomania", chance = 400 }, + { name = "megalomania's skull", chance = 400 }, + { name = "megalomania's essence", chance = 400 }, + { name = "bag you desire", chance = 100 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -800, maxDamage = -2500 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_BLACK_BLOOD, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_GHOST_SMOKE, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_SMALLCLOUDS, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -950, maxDamage = -1400, radius = 3, effect = CONST_ME_MORTAREA, target = true }, + { name = "soulwars fear", interval = 35000, chance = 100, target = true }, + { name = "megalomania transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +local intervalBetweenExecutions = 10000 + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua new file mode 100644 index 00000000000..6b7b12968e5 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua @@ -0,0 +1,132 @@ +local mType = Game.createMonsterType("Goshnar's Megalomania Purple") +local monster = {} + +monster.name = "Goshnar's Megalomania" +monster.description = "Goshnar's Megalomania" +monster.experience = 0 +monster.outfit = { + lookType = 1308, +} + +monster.health = 620000 +monster.maxHealth = 620000 +monster.race = "undead" +monster.corpse = 6028 +monster.speed = 250 +monster.manaCost = 0 +monster.maxSummons = 4 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.events = { + "GoshnarsHatredBuff", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -800, maxDamage = -2500 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_BLACK_BLOOD, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_GHOST_SMOKE, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_SMALLCLOUDS, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -950, maxDamage = -1400, radius = 3, effect = CONST_ME_MORTAREA, target = true }, + { name = "soulwars fear", interval = 35000, chance = 100, target = true }, + { name = "megalomania transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, +} + +monster.defenses = { + defense = 25, + armor = 25, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monster) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end + + if SoulWarQuest.changeBlueEvent then + stopEvent(SoulWarQuest.changeBlueEvent) + end + if SoulWarQuest.changePurpleEvent then + stopEvent(SoulWarQuest.changePurpleEvent) + end + + SoulWarQuest.changeBlueEvent = addEvent(SoulWarQuest.changeMegalomaniaBlue, 6 * 60 * 1000) + + local bossKV = monster:getSoulWarKV() + bossKV:set("aspect-of-power-death-count", 0) + monster:resetHatredDamageMultiplier() +end + +local intervalBetweenExecutions = 10000 + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 40957e0bde4..90a0235225b 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -14,7 +14,7 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", } monster.health = 300000 @@ -67,14 +67,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "mean maw", chance = 30, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -112,6 +104,7 @@ monster.attacks = { { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -1700, maxDamage = -2500, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -1000, maxDamage = -2500, range = 7, radius = 4, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_DRAWBLOOD, target = true }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -1500, maxDamage = -3000, radius = 3, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "cruelty transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, } monster.defenses = { @@ -142,9 +135,13 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +mType.onThink = function(monster) + monster:goshnarsDefenseIncrease("greedy-maw-action") +end + +mType.onAppear = function(monster, creature) end -mType.onAppear = function(monster, creature) +mType.onSpawn = function(monster) if monster:getType():isRewardBoss() then monster:setReward(true) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 171f963f5ff..bd0e064a6b4 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -14,7 +14,7 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", } monster.health = 300000 @@ -68,10 +68,9 @@ monster.light = { } monster.summon = { - maxSummons = 4, + maxSummons = 1, summons = { - { name = "dreadful harvester", chance = 10, interval = 1000, count = 2 }, - { name = "hateful soul", chance = 10, interval = 1000, count = 2 }, + { name = "dreadful harvester", chance = 10, interval = 1000, count = 1 }, }, } @@ -139,12 +138,41 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +local immuneTimeCount = 0 +local isImmune = nil +local createdSoulSphere = nil +mType.onThink = function(monsterCallback, interval) + if GreedbeastKills == 5 and isImmune == nil then + isImmune = monsterCallback:immune(false) + monsterCallback:teleportTo(Position(33741, 31659, 14)) + monsterCallback:setSpeed(0) + createdSoulSphere = Game.createMonster("Soul Sphere", Position(33752, 31659, 14), true, true) + end + if isImmune ~= nil then + immuneTimeCount = immuneTimeCount + interval + logger.info("Immune time count {}", immuneTimeCount) + if immuneTimeCount >= 45000 then + monsterCallback:immune(true) + monsterCallback:setSpeed(monster.speed) + monsterCallback:teleportTo(Position(33746, 31666, 14)) + immuneTimeCount = 0 + GreedbeastKills = 0 + isImmune = nil + if createdSoulSphere then + createdSoulSphere:remove() + end + end + end +end -mType.onAppear = function(monster, creature) +mType.onSpawn = function(monster) if monster:getType():isRewardBoss() then monster:setReward(true) end + + monster:immune(true) + immuneTimeCount = 0 + GreedbeastKills = 0 end mType.onDisappear = function(monster, creature) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index fa5ccf36984..09970df24fe 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -14,7 +14,8 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "GoshnarsHatredBuff", + "SoulWarBossesDeath", } monster.health = 300000 @@ -67,14 +68,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 10, interval = 1000, count = 2 }, - { name = "hateful soul", chance = 10, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -143,10 +136,14 @@ monster.immunities = { mType.onThink = function(monster, interval) end -mType.onAppear = function(monster, creature) +mType.onAppear = function(monster, creature) end + +mType.onSpawn = function(monster) if monster:getType():isRewardBoss() then monster:setReward(true) end + + monster:resetHatredDamageMultiplier() end mType.onDisappear = function(monster, creature) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index 5e79dcccbdd..2d71ff568b2 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -14,7 +14,8 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", + "Goshnar's-Malice", } monster.health = 300000 @@ -141,7 +142,19 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +local zone = Zone.getByName("boss.goshnar's-malice") +local zonePositions = zone:getPositions() + +local accumulatedTime = 0 +local desiredInterval = 40000 +mType.onThink = function(monster, interval) + accumulatedTime = accumulatedTime + interval + -- Execute only after 40 seconds + if accumulatedTime >= desiredInterval then + monster:createSoulWarWhiteTiles(SoulWarQuest.levers.goshnarsMalice.boss.position, zonePositions) + accumulatedTime = 0 + end +end mType.onAppear = function(monster, creature) if monster:getType():isRewardBoss() then diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 19d35cd1af7..0e41373cc8e 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -14,7 +14,7 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", } monster.health = 300000 @@ -70,8 +70,8 @@ monster.light = { monster.summon = { maxSummons = 4, summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "spiteful spitter", chance = 30, interval = 1000, count = 2 }, + { name = "dreadful harvester", chance = 50, interval = 1000, count = 2 }, + { name = "spiteful spitter", chance = 50, interval = 1000, count = 2 }, }, } @@ -111,6 +111,7 @@ monster.attacks = { { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -1700, maxDamage = -1900, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -1200, maxDamage = -3500, range = 7, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = true }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -1400, maxDamage = -2200, length = 8, spread = 0, effect = CONST_ME_GREEN_RINGS, target = false }, + { name = "soulwars fear", interval = 2000, chance = 10, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/soul_war/greedbeast.lua b/data-otservbr-global/monster/quests/soul_war/greedbeast.lua new file mode 100644 index 00000000000..db7076d5710 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/greedbeast.lua @@ -0,0 +1,98 @@ +local mType = Game.createMonsterType("Greedbeast") +local monster = {} + +monster.description = "a greedbeast" +monster.experience = 0 +monster.outfit = { + lookType = 101, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 200 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500, condition = { type = CONDITION_POISON, totalDamage = 100, interval = 4000 } }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -50, maxDamage = -90, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false }, + { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -25, maxDamage = -47, radius = 3, effect = CONST_ME_MAGIC_RED, target = false }, + -- poison + { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -200, maxDamage = -400, radius = 3, effect = CONST_ME_POISONAREA, target = false }, + -- poison + { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -200, maxDamage = -400, length = 6, spread = 0, effect = CONST_ME_POISONAREA, target = false }, + { name = "speed", interval = 2000, chance = 15, speedChange = -600, target = true, duration = 13000 }, +} + +monster.defenses = { + defense = 70, + armor = 80, + mitigation = 1.15, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_HEALING, minDamage = 50, maxDamage = 60, effect = CONST_ME_HITBYPOISON, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = -10 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = -25 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua index 8813ba2e93f..ceacde54c26 100644 --- a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua +++ b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua @@ -13,6 +13,10 @@ monster.outfit = { lookMount = 0, } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 35000 monster.maxHealth = 35000 monster.race = "blood" @@ -106,4 +110,40 @@ monster.events = { "MirrorImageTransform", } +mType.onPlayerAttack = function(monster, attackerPlayer) + logger.info("Player {}, attacking monster {}", attackerPlayer:getName(), monster:getName()) + + local apparitionType = "" + local apparitionTypes = { + "Druid's Apparition", + "Knight's Apparition", + "Paladin's Apparition", + "Sorcerer's Apparition", + } + + local sameVocationProbability = 70 -- 70% chance for create monster of first player attack vocation + if attackerPlayer:isDruid() then + apparitionType = "Druid's Apparition" + elseif attackerPlayer:isKnight() then + apparitionType = "Knight's Apparition" + elseif attackerPlayer:isPaladin() then + apparitionType = "Paladin's Apparition" + elseif attackerPlayer:isSorcerer() then + apparitionType = "Sorcerer's Apparition" + end + + if math.random(100) > sameVocationProbability then + repeat + local randomIndex = math.random(#apparitionTypes) + if apparitionTypes[randomIndex] ~= apparitionType then + apparitionType = apparitionTypes[randomIndex] + break + end + until false + end + + Game.createMonster(apparitionType, monster:getPosition(), true, true) + monster:remove() +end + mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/bony_sea_devil.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua similarity index 96% rename from data-otservbr-global/monster/undeads/bony_sea_devil.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua index 1199240df4b..2737bd3a098 100644 --- a/data-otservbr-global/monster/undeads/bony_sea_devil.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 24000 monster.maxHealth = 24000 monster.race = "undead" @@ -136,4 +140,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Get out the way!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/demons/brachiodemon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua similarity index 96% rename from data-otservbr-global/monster/demons/brachiodemon.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua index 930ecfe469f..378a15814c6 100644 --- a/data-otservbr-global/monster/demons/brachiodemon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 25000 monster.maxHealth = 25000 monster.race = "blood" @@ -137,4 +141,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Burn in hell!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/plants/branchy_crawler.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua similarity index 95% rename from data-otservbr-global/monster/plants/branchy_crawler.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua index d319fcde5e5..0798c9f482f 100644 --- a/data-otservbr-global/monster/plants/branchy_crawler.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "blood" @@ -132,4 +136,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("My growth is your death!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua new file mode 100644 index 00000000000..72c4082ee0e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Ashes of Burning Hatred") +local monster = {} + +monster.description = "a ashes of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34009, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua new file mode 100644 index 00000000000..c0b76ce98df --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Blaze of Burning Hatred") +local monster = {} + +monster.description = "a blaze of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34013, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua new file mode 100644 index 00000000000..483c5156fb1 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Flame of Burning Hatred") +local monster = {} + +monster.description = "a flame of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34011, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua new file mode 100644 index 00000000000..dc9ff25f05e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Spark of Burning Hatred") +local monster = {} + +monster.description = "a spark of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34010, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua new file mode 100644 index 00000000000..9df86b773a9 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua @@ -0,0 +1,84 @@ +local mType = Game.createMonsterType("Symbol of Hatred") +local monster = {} + +monster.description = "a symbol of hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 11427, +} + +monster.health = 14000 +monster.maxHealth = 14000 +monster.race = "undead" +monster.corpse = 33792 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local intervalBetweenExecutions = 3000 + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 30, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsHatred.boss.position) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/capricious_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua similarity index 98% rename from data-otservbr-global/monster/undeads/capricious_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua index cd402486853..6a3b5ea0bbc 100644 --- a/data-otservbr-global/monster/undeads/capricious_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 30000 monster.maxHealth = 30000 monster.race = "undead" diff --git a/data-otservbr-global/monster/undeads/distorted_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua similarity index 98% rename from data-otservbr-global/monster/undeads/distorted_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua index c1fdc41461b..a27ae569343 100644 --- a/data-otservbr-global/monster/undeads/distorted_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Mirrored Nightmare.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 26000 monster.maxHealth = 26000 monster.race = "undead" diff --git a/data-otservbr-global/monster/undeads/druid's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua similarity index 98% rename from data-otservbr-global/monster/undeads/druid's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua index 414de6155bf..6948a58fb53 100644 --- a/data-otservbr-global/monster/undeads/druid's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua @@ -32,6 +32,10 @@ monster.corpse = 6081 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua new file mode 100644 index 00000000000..aed9dcfc829 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua @@ -0,0 +1,93 @@ +local mType = Game.createMonsterType("A Greedy Eye") +local monster = {} + +monster.description = "a greedy eye" +monster.experience = 0 +monster.outfit = { + lookType = 925, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 30000 +monster.maxHealth = 30000 +monster.race = "blood" +monster.corpse = 5995 +monster.speed = 0 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 70, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "greedy eye beam", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/plants/cloak_of_terror.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua similarity index 95% rename from data-otservbr-global/monster/plants/cloak_of_terror.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua index 184ba93fab2..4048e0fd69e 100644 --- a/data-otservbr-global/monster/plants/cloak_of_terror.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua @@ -26,6 +26,11 @@ monster.Bestiary = { Locations = "Furious Crater.", } +monster.events = { + "FourthTaintBossesPrepareDeath", + "CloakOfTerrorHealthLoss", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "undead" @@ -131,4 +136,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("I am your terror!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/extra_dimensional/courage_leech.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua similarity index 98% rename from data-otservbr-global/monster/extra_dimensional/courage_leech.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua index d8ad3147a8e..8e4a61b6ef0 100644 --- a/data-otservbr-global/monster/extra_dimensional/courage_leech.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Furious Crater", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "undead" diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua new file mode 100644 index 00000000000..55f5ccb623f --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua @@ -0,0 +1,119 @@ +local mType = Game.createMonsterType("Poor Soul") +local monster = {} + +monster.description = "a poor soul" +monster.experience = 0 +monster.outfit = { + lookType = 1296, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 500 +monster.maxHealth = 500 +monster.race = "undead" +monster.corpse = 33891 +monster.speed = 140 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 5, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "I have a head start.", yell = false }, + { text = "Look into my eyes! No, the other ones!", yell = false }, + { text = "The mirrors can't contain the night!", yell = false }, +} + +monster.loot = { + { name = "crystal coin", chance = 70540 }, + { name = "ultimate health potion", chance = 12220, maxCount = 7 }, + { name = "violet gem", chance = 4560 }, + { name = "green gem", chance = 5760 }, + { name = "blue gem", chance = 4960 }, + { name = "northwind rod", chance = 5920 }, + { name = "sacred tree amulet", chance = 5520 }, + { id = 33933, chance = 7920 }, -- apron + { id = 3067, chance = 7220 }, -- hailstorm rod + { name = "glacier shoes", chance = 2520 }, + { name = "glacier robe", chance = 2220 }, + { name = "stone skin amulet", chance = 5920 }, + { id = 23533, chance = 4892 }, -- ring of red plasma + { id = 33932, chance = 3520 }, -- head + { name = "glacial rod", chance = 620 }, + { id = 34024, chance = 650 }, -- gruesome fan + { id = 34109, chance = 1 }, -- bag you desire +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -250, maxDamage = -450 }, +} + +monster.defenses = { + defense = 90, + armor = 105, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = -300 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = true }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/vibrant_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua similarity index 98% rename from data-otservbr-global/monster/undeads/vibrant_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua index 9f17bee76ee..b6d4b89f024 100644 --- a/data-otservbr-global/monster/undeads/vibrant_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Furious Crater.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "undead" diff --git a/data-otservbr-global/monster/quests/soul_war/hateful_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua similarity index 99% rename from data-otservbr-global/monster/quests/soul_war/hateful_soul.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua index 4aad05f3f33..69c413aa242 100644 --- a/data-otservbr-global/monster/quests/soul_war/hateful_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua @@ -16,7 +16,7 @@ monster.outfit = { monster.health = 25000 monster.maxHealth = 25000 monster.race = "undead" -monster.corpse = 0 +monster.corpse = 33793 monster.speed = 125 monster.manaCost = 0 diff --git a/data-otservbr-global/monster/demons/infernal_demon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua similarity index 98% rename from data-otservbr-global/monster/demons/infernal_demon.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua index 7c4a1d80302..8760d3b1757 100644 --- a/data-otservbr-global/monster/demons/infernal_demon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 32000 monster.maxHealth = 32000 monster.race = "blood" diff --git a/data-otservbr-global/monster/undeads/infernal_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua similarity index 98% rename from data-otservbr-global/monster/undeads/infernal_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua index 0b61e291291..e8ef7b231f1 100644 --- a/data-otservbr-global/monster/undeads/infernal_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 26000 monster.maxHealth = 26000 monster.race = "undead" diff --git a/data-otservbr-global/monster/undeads/knight's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua similarity index 98% rename from data-otservbr-global/monster/undeads/knight's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua index 0a4415aa2fd..86893bfa01c 100644 --- a/data-otservbr-global/monster/undeads/knight's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 111 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, diff --git a/data-otservbr-global/monster/demons/many_faces.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua similarity index 96% rename from data-otservbr-global/monster/demons/many_faces.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua index 8b9724a6252..627f1dc8052 100644 --- a/data-otservbr-global/monster/demons/many_faces.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Mirrored Nightmare.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 30000 monster.maxHealth = 30000 monster.race = "undead" @@ -133,4 +137,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Hands off my comrades!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua new file mode 100644 index 00000000000..2193aac5447 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua @@ -0,0 +1,107 @@ +local mType = Game.createMonsterType("Greater Splinter of Madness") +local monster = {} + +monster.description = "a greater splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 82, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -450 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -350, maxDamage = -800, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -750, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + eventMonster:setType("Mighty Splinter of Madness", true) + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua new file mode 100644 index 00000000000..7927ac18a26 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua @@ -0,0 +1,107 @@ +local mType = Game.createMonsterType("Lesser Splinter of Madness") +local monster = {} + +monster.description = "a lesser splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 57, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -350 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -550, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -550, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + eventMonster:setType("Greater Splinter of Madness", true) + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua new file mode 100644 index 00000000000..18dd5825b89 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua @@ -0,0 +1,113 @@ +local mType = Game.createMonsterType("Mighty Splinter of Madness") +local monster = {} + +monster.description = "a mighty splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 93, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -750 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -450, maxDamage = -800, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -400, maxDamage = -800, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + creature:say("Goshnar's Megalomania feeds on its own madness and becomes stronger!", TALKTYPE_MONSTER_SAY, 0, 0, Position(34091, 31026, 9)) + creature:remove() + local boss = Creature("Goshnar's Megalomania") + if boss then + boss:increaseHatredDamageMultiplier(5) + logger.debug("Goshnar's Megalomania has increased its damage multiplier to 5.") + end + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua new file mode 100644 index 00000000000..53c607ace7f --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua @@ -0,0 +1,82 @@ +local mType = Game.createMonsterType("Necromantic Focus") +local monster = {} + +monster.description = "a necromantic focus" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 7059, +} + +monster.health = 12000 +monster.maxHealth = 12000 +monster.race = "undead" +monster.corpse = 33984 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.events = { + "NecromanticFocusDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/mould_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua similarity index 98% rename from data-otservbr-global/monster/undeads/mould_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua index c3d7ea8ea86..54f3794360b 100644 --- a/data-otservbr-global/monster/undeads/mould_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "undead" diff --git a/data-otservbr-global/monster/undeads/paladin's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua similarity index 98% rename from data-otservbr-global/monster/undeads/paladin's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua index 066266e70e3..5162f5d1808 100644 --- a/data-otservbr-global/monster/undeads/paladin's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 111 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, diff --git a/data-otservbr-global/monster/constructs/rotten_golem.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua similarity index 98% rename from data-otservbr-global/monster/constructs/rotten_golem.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua index edc6f909a70..bf9ea5b7555 100644 --- a/data-otservbr-global/monster/constructs/rotten_golem.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "venom" diff --git a/data-otservbr-global/monster/undeads/sorcerer's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua similarity index 98% rename from data-otservbr-global/monster/undeads/sorcerer's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua index 8cf059be62c..c054a0d584f 100644 --- a/data-otservbr-global/monster/undeads/sorcerer's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 6081 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, diff --git a/data-otservbr-global/monster/elementals/turbulent_elemental.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua similarity index 98% rename from data-otservbr-global/monster/elementals/turbulent_elemental.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua index 3bef5da44e3..feaf706ae97 100644 --- a/data-otservbr-global/monster/elementals/turbulent_elemental.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "blood" diff --git a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua new file mode 100644 index 00000000000..cee228f7185 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua @@ -0,0 +1,115 @@ +local mType = Game.createMonsterType("Powerful Soul") +local monster = {} + +monster.description = "a powerful soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 30000 +monster.maxHealth = 30000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 1000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -2000, maxDamage = -3000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -2000, maxDamage = -3000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 2, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + CreateGoshnarsGreedMonster("Weak Soul", GreedMonsters[monster:getName()]) + monster:remove() + local boss = Creature("Goshnar's Greed") + if boss then + for elementType, reflectPercent in pairs(SoulWarReflectDamageMap) do + boss:addReflectElement(elementType, reflectPercent) + end + boss:addDefense(10) + boss:setMaxHealth(boss:getHealth() + 10000) + boss:addHealth(10000) + end + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soul_cage.lua b/data-otservbr-global/monster/quests/soul_war/soul_cage.lua new file mode 100644 index 00000000000..c6c65b32547 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soul_cage.lua @@ -0,0 +1,71 @@ +local mType = Game.createMonsterType("Soul Cage") +local monster = {} + +monster.description = "a soul cage" +monster.experience = 100000 +monster.outfit = { + lookType = 863, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 100000 +monster.maxHealth = 100000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 0 +monster.manaCost = 0 + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, +} + +monster.events = { + "SoulCageDeath", + "SoulCageHealthChange", +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 80, + armor = 100, + { name = "Heal Malice", interval = 2000, chance = 90, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "invisible", condition = true }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua new file mode 100644 index 00000000000..c0142e3e611 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua @@ -0,0 +1,109 @@ +local mType = Game.createMonsterType("Soul Sphere") +local monster = {} + +monster.description = "a soul sphere" +monster.experience = 0 +monster.outfit = { + lookType = 979, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.corpse = 0 +monster.speed = 0 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -500, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local moveTimeCount = 0 +mType.onThink = function(monster, interval) + moveTimeCount = moveTimeCount + interval + if moveTimeCount == 3000 then + monster:move(DIRECTION_WEST) + moveTimeCount = 0 + local monsterPos = monster:getPosition() + local nextPos = Position(monsterPos.x - 1, monsterPos.y, monsterPos.z) + local tile = Tile(nextPos) + if not tile then + return + end + + for _, creatureId in pairs(tile:getCreatures()) do + local monster = Monster(creatureId) + if monster and monster:getName() == "Goshnar's Greed" then + monster:setHealth(monster:getMaxHealth()) + break + end + end + end +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua b/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua new file mode 100644 index 00000000000..faa819dec6e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua @@ -0,0 +1,115 @@ +local mType = Game.createMonsterType("Soulsnatcher") +local monster = {} + +monster.description = "a soulsnatcher" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 94, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = true, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -1500 }, + { name = "soulsnatcher-lifedrain-beam", interval = 2000, chance = 20, minDamage = -1000, maxDamage = -1500, target = false }, + { name = "soulsnatcher-lifedrain-missile", interval = 2000, chance = 25, minDamage = -1000, maxDamage = -1500, target = true }, + { name = "soulsnatcher-manadrain-ball", interval = 2000, chance = 30, minDamage = -500, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + transformTimeCount = 0 + local zone = Zone("boss.goshnar's-greed") + if zone then + local players = zone:getPlayers() + for _, player in ipairs(players) do + player:addHealth(-math.random(500, 1000)) + end + end + CreateGoshnarsGreedMonster(monster:getName(), GreedMonsters[monster:getName()]) + monster:remove() + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua b/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua index 8b8cec55ccf..d51b52e2e84 100644 --- a/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua +++ b/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua @@ -99,4 +99,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("You have been chosen for a harvest!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/strong_soul.lua b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua new file mode 100644 index 00000000000..3fb844d1f97 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua @@ -0,0 +1,106 @@ +local mType = Game.createMonsterType("Strong Soul") +local monster = {} + +monster.description = "a strong soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 20000 +monster.maxHealth = 20000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 1000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -2000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -2000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 2, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + Game.createMonster("Powerful Soul", GreedMonsters[monster:getName()], true, false) + monster:remove() + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/weak_soul.lua b/data-otservbr-global/monster/quests/soul_war/weak_soul.lua new file mode 100644 index 00000000000..b7150efa00f --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/weak_soul.lua @@ -0,0 +1,106 @@ +local mType = Game.createMonsterType("Weak Soul") +local monster = {} + +monster.description = "a weak soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -1000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -500, maxDamage = -1000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + Game.createMonster("Strong Soul", GreedMonsters[monster:getName()], true, false) + monster:remove() + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua new file mode 100644 index 00000000000..3631fa3cb8e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua @@ -0,0 +1,91 @@ +local mType = Game.createMonsterType("Weeping Soul") +local monster = {} + +monster.description = "a weeping soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 7000 +monster.maxHealth = 7000 +monster.race = "undead" +monster.corpse = 33876 +monster.speed = 150 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 15, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -220, maxDamage = -650 }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 40 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/hazardous_phantom.lua b/data-otservbr-global/monster/undeads/hazardous_phantom.lua index 29384eafe55..4d3eb338b19 100644 --- a/data-otservbr-global/monster/undeads/hazardous_phantom.lua +++ b/data-otservbr-global/monster/undeads/hazardous_phantom.lua @@ -20,6 +20,10 @@ monster.corpse = 34125 monster.speed = 100 monster.manaCost = 0 +monster.events = { + "HazardousPhantomDeath", +} + monster.changeTarget = { interval = 4000, chance = 0, diff --git a/data-otservbr-global/npc/flickering_soul.lua b/data-otservbr-global/npc/flickering_soul.lua new file mode 100644 index 00000000000..a6e11c311c6 --- /dev/null +++ b/data-otservbr-global/npc/flickering_soul.lua @@ -0,0 +1,195 @@ +local internalNpcName = "Flickering Soul" +local npcType = Game.createNpcType(internalNpcName) +local npcConfig = {} + +npcConfig.name = internalNpcName +npcConfig.description = internalNpcName + +npcConfig.health = 100 +npcConfig.maxHealth = npcConfig.health +npcConfig.walkInterval = 2000 +npcConfig.walkRadius = 2 + +npcConfig.outfit = { + lookType = 1219, + lookHead = 6, + lookBody = 26, + lookLegs = 26, + lookFeet = 6, + lookAddons = 0, + lookMount = 0, +} + +npcConfig.flags = { + floorchange = false, +} + +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) + +npcType.onThink = function(npc, interval) + npcHandler:onThink(npc, interval) +end + +npcType.onAppear = function(npc, player) + npcHandler:onAppear(npc, player) +end + +npcType.onDisappear = function(npc, player) + npcHandler:onDisappear(npc, player) +end + +npcType.onMove = function(npc, player, fromPosition, toPosition) + npcHandler:onMove(npc, player, fromPosition, toPosition) +end + +npcType.onSay = function(npc, player, type, message) + npcHandler:onSay(npc, player, type, message) +end + +npcType.onCloseChannel = function(npc, player) + npcHandler:onCloseChannel(npc, player) +end + +local function playerSayCallback(npc, player, type, message) + if not npcHandler:checkInteraction(npc, player) then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + + local playerId = player:getId() + if MsgContains(message, "living") then + npcHandler:say("It has been a while since I roamed the world of the living in a mortal shell.", npc, player) + elseif MsgContains(message, "mortal") then + npcHandler:say("I had many names in my live. The one that would be the most recognizable is probably the name Goshnar. Even that was an assumed name that I took when I left my mundane past behind.", npc, player) + elseif MsgContains(message, "Goshnar") then + npcHandler:say({ + "I was once known as the necromant king. ...", + "For some it was meant as a curse, others used the name with reverence. To me it was just another stepping stone, in a life that burned with ambition.", + }, npc, player, 4000) + elseif MsgContains(message, "ambition") then + npcHandler:say({ + "My ambitions were high and knew no limits. Mastery over life and death was but a milestone that I wanted to accomplish. In the end I aspired probably somewhat like godhood. ...", + "Though in hindsight even that wouldn't have been enough. There was a hunger in me that nothing could put to rest.", + }, npc, player, 4000) + elseif MsgContains(message, "milestone") then + npcHandler:say("Everything in my life was just a tool to further my goals. The brotherhood of bones was just a tool for me. As was everyone or everything. In the path I had chosen nothing mattered but me and my ambitions.", npc, player) + elseif MsgContains(message, "everything") then + npcHandler:say("Necromancy was a passion at first, another tool while I amassed power and a crutch when my ambitions surpassed that what it could accomplish.", npc, player) + elseif MsgContains(message, "accomplish") then + npcHandler:say({ + "I was so convinced about my brilliance, my greatness, my destiny. And this hunger for more, it let me not have peace at any point in my life. I was always driven. There was no time to rest. ...", + "And there was no looking back. I never cared to remember my humble beginnings, what I had sacrificed to get where I was. All that I had left behind and that I had lost forever. ...", + "Now I see the bitter irony. I could bring back the dead, but I couldn't create second chances. I couldn't restore the truly important things that I had lost.", + }, npc, player, 4000) + elseif MsgContains(message, "dead") then + npcHandler:say("My demise did not meet me unprepared. As a powerful necromancer I had fettered my soul in the living world and the realms beyond. I had prepared for my return and was confident in my power.", npc, player) + elseif MsgContains(message, "confident") then + npcHandler:say("My soul wandered the plains of Zarganash, waiting for my wards to power up. Waiting for my soul to be slowly pulled back and manifest in the world of the living once again. What I had not taken into consideration was peace.", npc, player) + elseif MsgContains(message, "peace") then + npcHandler:say({ + "Zarganash was not a place without its dangers, but for a soul as powerful as mine, there was little threat at all. For the first time in my existence I had to stop running forward. I had to wait for things to fit into their places. ...", + "And me, who had seen things that horrible, they would have obliterated a lesser man's mind, finally took the time to look back. And what I saw was frightening in its own right. ...", + "A great tiredness overcame me. With the flames of my ambitions calming down for the first time since I could remember, all my aspirations and plans seemed to petty and futile. ...", + "Everything I had worked for and my plans for the things to come seemed pointless, and the things I had lost and never allowed myself to experience weighed heavily on my soul.", + }, npc, player, 4000) + elseif MsgContains(message, "soul") then + npcHandler:say({ + "I talked to other souls, lost in Zarganash, and most of them seemed like mirrors to myself. Their faults, their shortcomings, the things that were important to them and the things they had lost. ...", + "It was all like miniature copies of my own grand plans and losses. It made me think. And the great tiredness weighed even more heavy on me. A weariness of the world, of the hunger that drove me.", + }, npc, player, 4000) + elseif MsgContains(message, "weariness") then + npcHandler:say({ + "Then I met a wise soul. A teacher that did not lecture. I never was impressed by anything but my own accomplishments. But the inner balance and peace of this soul, it did impress me. A lot. ...", + "I, who fancied myself to have been the epitome of knowledge, learned things that were entirely new to me. But this knowledge wasn't about power. It was about me.", + }, npc, player, 4000) + elseif MsgContains(message, "knowledge") then + npcHandler:say("I recognized the extent of my folly and failure. I decided not to return to the world of the living.", npc, player) + elseif MsgContains(message, "return") then + npcHandler:say({ + "I decided to stay here, even pass on into the great beyond at some point. Yet I still feel the pull of my fetters. I can faintly hear those who think they are my followers, calling to me.", + "And I feel others, many others who crave my powers and try to bring me back for their own gain.", + }, npc, player, 4000) + elseif MsgContains(message, "fetters") then + npcHandler:say({ + "Over my time in Zarganash I split away the parts of me that my worldly fetters were bound to. Yet I had to recognize that they are still a part of me and I'm bound to them. ...", + "The fetters and the efforts to call me back are empowering them. I feel them growing in strength and gaining awareness on their own. ...", + "They are beginning to feed not only on the fetters and incarnations but also on me. As I grow weaker, they grow more powerful over time.", + }, npc, player, 4000) + elseif MsgContains(message, "powerful") then + npcHandler:say("The only way I can get rid of them is to disperse them, to 'kill' them so to say. But they are tainted parts of myself and even going near them might endanger my sanity and stability. So all I can do is to ask you to do this daunting task.", npc, player) + elseif MsgContains(message, "task") then + local soulWarQuest = player:soulWarQuestKV() + -- Checks if the boss has already been defeated + if soulWarQuest:get("goshnar's-megalomania-killed") then + npcHandler:say({ + "You did it! For the first time I can feel free from the pull of my past. Now I'm free at last. ...", + "I might stay a while and teach other souls about the inner peace, but will eventually pass on. Thank you so much, my hero. My eternal gratitude and blessings will be with you!", + }, npc, player, 2000) + npcHandler:setTopic(playerId, 2) + player:addOutfit("Revenant") + else + npcHandler:say("I'm aware I have no right to ask and I have little to offer as a payment, but I ask you nonetheless. Will you face my fettered vices and destroy them for me?", npc, player) + npcHandler:setTopic(playerId, 1) + end + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 1 then + npcHandler:say("Thank you for accepting this burden.", npc, player) + soulWarQuest:set("teleport-access", true) + elseif MsgContains(message, "burden") then + npcHandler:say({ + "You will have to reach each of the negative parts of my personality that I split away. They are hidden deep in the depths of Zarganash and will have corrupted and twisted their surroundings into dangerous nightmares. ...", + "Even worse, you'll likely encounter minions of those who want to claim my soul as their prize for their own depraved reasons. You will have to destroy my shards to set me free.", + }, npc, player, 5000) + elseif MsgContains(message, "shards") then + npcHandler:say("You haven't killed Malice yet. You haven't killed Hatred yet. You haven't killed Spite yet. You haven't killed Cruelty yet. You haven't killed Greed yet.", npc, player) + elseif MsgContains(message, "hate") then + npcHandler:say({ + "I hated the world for its flaws and the reluctance of people to comply with my will. I was convinced I was destined for greatness and to change everything. Ordinary beings were far beneath me and my consideration. ...", + "All this opposition, all the wars were a nuisance on my way to greatness. I would have sacrificed the whole world to reach my goals.", + }, npc, player, 4000) + elseif MsgContains(message, "fermuba") then + npcHandler:say("My daughter was as ambitious as me, yet she lacked my intellect and my raw talent. She still was great and talented yet I sadly let her feel my disdain. One of the many errors that my way of hubris made me do.", npc, player) + elseif MsgContains(message, "ferumbras") then + npcHandler:say({ + "Even in the lands of the dead, this one caused a stir. The dead were whispering his name. It made me feel jealous and angry at first, but at some point, after much self-reflection, I could recognize my own faults in the stories about him.", + "It was almost like looking into a mirror for the first time. However, he lived way later than me, and I never met his soul here, so I can't tell more about him.", + }, npc, player, 4000) + elseif MsgContains(message, "grandson") then + npcHandler:say(" I'm not aware of the fate of my linage. Neither I'm able to relate to the mortal world in that way. Each of us is an individual, not bound by ties of blood or herritage.", npc, player) + elseif MsgContains(message, "pale worm") then + npcHandler:say("His avatar might be destroyed for now and it'd grip on Zarganash considerably weakened. Yet he burrowed to deep into the essence of this realm to be annihilated this easy.", npc, player) + elseif MsgContains(message, "necromant king") then + npcHandler:say({ + "They called me the necromant king, in an act of reverence, but to me it was always more of a slander. To limit my greatness to this insignificant aspect was an insult to my ego. But I let it slip for the greater good. ...", + "I felt it was beneath me to correct them and I went along.", + }, npc, player, 4000) + elseif MsgContains(message, "minions") or MsgContains(message, "followers") then + npcHandler:say("I despised my followers for their petty agendas and for their limited vision of my own goals and personality.", npc, player) + elseif MsgContains(message, "shards") then + local bossesYetToDefeat = {} + for bossName, _ in pairs(SoulWarBosses) do + if not soulWarQuest:get(bossName) then + table.insert(bossesYetToDefeat, bossName) + end + end + + local message + if #bossesYetToDefeat > 0 then + message = "You haven't killed " .. table.concat(bossesYetToDefeat, ", ") .. " yet." + else + message = "You have defeated all the Goshnar's Bosses. Your soul shines brighter with each victory." + end + npcHandler:say(message, npc, player) + end + return true +end + +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, playerSayCallback) + +npcHandler:setMessage(MESSAGE_GREET, "Be greeted, living soul!") + +npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) + +-- npcType registering the npcConfig table +npcType:register(npcConfig) diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua deleted file mode 100644 index 2e01880589d..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Cruelty", - position = Position(33856, 31866, 7), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33854, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33855, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33856, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33857, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33858, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33847, 31858, 7), - to = Position(33864, 31874, 7), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33853, y = 31854, z = 6 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua deleted file mode 100644 index 522cde76845..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Greed", - position = Position(33746, 31666, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33776, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33778, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33779, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33780, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33737, 31658, 14), - to = Position(33755, 31673, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33775, y = 31665, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua deleted file mode 100644 index 963404c8d85..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Hatred", - position = Position(33744, 31599, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33773, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33774, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33776, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33735, 31592, 14), - to = Position(33751, 31606, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33772, y = 31601, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua deleted file mode 100644 index 44d102ad598..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Malice", - position = Position(33710, 31599, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33679, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33680, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33681, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33682, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33683, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33699, 31590, 14), - to = Position(33718, 31607, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33678, y = 31599, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua deleted file mode 100644 index 5896faa74c4..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Megalomania", - position = Position(33710, 31634, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33676, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33677, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33678, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33679, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33680, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33701, 31626, 14), - to = Position(33719, 31642, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33675, y = 31634, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua deleted file mode 100644 index 0fd2396ae03..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Spite", - position = Position(33743, 31632, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33774, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33776, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33778, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33734, 31624, 14), - to = Position(33751, 31640, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33773, y = 31634, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/bosses_killed.lua b/data-otservbr-global/scripts/actions/quests/soul_war/bosses_killed.lua deleted file mode 100644 index de4b8ae182f..00000000000 --- a/data-otservbr-global/scripts/actions/quests/soul_war/bosses_killed.lua +++ /dev/null @@ -1,24 +0,0 @@ -local bosses = { - ["goshnar's malice"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarMaliceKilled }, - ["goshnar's hatred"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarHatredKilled }, - ["goshnar's spite"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarSpiteKilled }, - ["goshnar's cruelty"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarCrueltyKilled }, - ["goshnar's greed"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarGreedKilled }, - ["goshnar's megalomania"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarMegalomaniaKilled }, -} - -local bossesSoulWar = CreatureEvent("SoulwarsBossDeath") -function bossesSoulWar.onDeath(creature) - local bossConfig = bosses[creature:getName():lower()] - if not bossConfig then - return true - end - onDeathForDamagingPlayers(creature, function(creature, player) - if bossConfig.storage then - player:setStorageValue(bossConfig.storage, 1) - end - end) - return true -end - -bossesSoulWar:register() diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/portal_megalomania.lua b/data-otservbr-global/scripts/actions/quests/soul_war/portal_megalomania.lua deleted file mode 100644 index 41d3763c1c7..00000000000 --- a/data-otservbr-global/scripts/actions/quests/soul_war/portal_megalomania.lua +++ /dev/null @@ -1,38 +0,0 @@ -local storagesTable = { - { storage = Storage.Quest.U12_40.SoulWar.GoshnarMaliceKilled, bossName = "Goshnar's Malice" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarHatredKilled, bossName = "Goshnar's Hatred" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarSpiteKilled, bossName = "Goshnar's Spite" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarCrueltyKilled, bossName = "Goshnar's Cruelty" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarGreedKilled, bossName = "Goshnar's Greed" }, -} - -local portalMegalomania = MoveEvent() -function portalMegalomania.onStepIn(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - local text = "" - for value in pairs(storagesTable) do - if player:getStorageValue(storagesTable[value].storage) < 0 then - text = text .. "\n" .. storagesTable[value].bossName - end - end - if text == "" then - return true - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You still need to defeat:" .. text) - player:teleportTo(fromPosition, true) - return false - end -end - -portalMegalomania:type("stepin") -portalMegalomania:position({ x = 33611, y = 31430, z = 10 }) -portalMegalomania:register() diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua b/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua deleted file mode 100644 index 1e3c9c773e0..00000000000 --- a/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua +++ /dev/null @@ -1,89 +0,0 @@ -local rewards = { - { id = 34082, name = "Soulcutter" }, - { id = 34083, name = "Soulshredder" }, - { id = 34084, name = "Soulbiter" }, - { id = 34085, name = "Souleater" }, - { id = 34086, name = "Soulcrusher" }, - { id = 34087, name = "Soulmaimer" }, - { id = 34088, name = "Soulbleeder" }, - { id = 34089, name = "Soulpiercer" }, - { id = 34090, name = "Soultainter" }, - { id = 34091, name = "Soulhexer" }, - { id = 34092, name = "Soulshanks" }, - { id = 34093, name = "Soulstrider" }, - { id = 34094, name = "Soulshell" }, - { id = 34095, name = "Soulmantel" }, - { id = 34096, name = "Soulshroud" }, - { id = 34097, name = "Pair of Soulwalkers" }, - { id = 34098, name = "Pair of Soulstalkers" }, - { id = 34099, name = "Soulbastion" }, -} -local outfits = { 1322, 1323 } - -local function addOutfits(player) - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward) < 0 then - player:addOutfit(outfits[1], 0) - player:addOutfit(outfits[2], 0) - player:setStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward, 1) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations you received the Revenant Outfit.") - end -end - -local rewardSoulWar = Action() -function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, isHotkey) - local randId = math.random(1, #rewards) - local rewardItem = rewards[randId] - local player = creature:getPlayer() - if not player then - return false - end - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.QuestReward) < 0 then - player:addItem(rewardItem.id, 1) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. rewardItem.name .. ".") - player:setStorageValue(Storage.Quest.U12_40.SoulWar.QuestReward, 1) - addOutfits(player) - return true - else - addOutfits(player) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already collected your reward") - return false - end -end - -rewardSoulWar:position({ x = 33620, y = 31400, z = 10 }) -rewardSoulWar:register() - ------------------------------ --- Phantasmal Jade Mount function - -local phantasmalJadeMount = Action() -function phantasmalJadeMount.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local storage = Storage.Quest.U12_40.SoulWar.MountReward - if player:getStorageValue(storage) == 1 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already have Phantasmal Jade mount!") - return false - end - - if table.contains({ 34072, 34073, 34074 }, item.itemid) then - -- check items - if player:getItemCount(34072) >= 4 and player:getItemCount(34073) == 1 and player:getItemCount(34074) == 1 then - player:removeItem(34072, 4) - player:removeItem(34073, 1) - player:removeItem(34074, 1) - player:addMount(167) - player:setStorageValue(storage, 1) - player:addAchievement("You got Horse Power") - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won Phantasmal Jade mount.") - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won You got Horse Power achievement.") - player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE) - return true - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have the necessary items!") - player:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end - end -end - -phantasmalJadeMount:id(34072, 34073, 34074) -phantasmalJadeMount:register() diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua b/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua deleted file mode 100644 index 51f8281a04d..00000000000 --- a/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua +++ /dev/null @@ -1,63 +0,0 @@ -local config = { - { position = { x = 33615, y = 31422, z = 10 }, destination = { x = 34009, y = 31014, z = 9 } }, -- hunt infernal demon - { position = { x = 33618, y = 31422, z = 10 }, destination = { x = 33972, y = 31041, z = 11 } }, -- hunt rotten - { position = { x = 33621, y = 31422, z = 10 }, destination = { x = 33894, y = 31019, z = 8 } }, -- hunt bony sea devil - { position = { x = 33624, y = 31422, z = 10 }, destination = { x = 33858, y = 31831, z = 3 } }, -- hunt cloak - { position = { x = 33627, y = 31422, z = 10 }, destination = { x = 33887, y = 31188, z = 10 } }, -- hunt many faces - { position = { x = 33950, y = 31109, z = 8 }, destination = { x = 33780, y = 31634, z = 14 } }, -- goshnar's spite entrance - { position = { x = 33937, y = 31217, z = 11 }, destination = { x = 33782, y = 31665, z = 14 } }, -- goshnar's greed entrance - { position = { x = 34022, y = 31091, z = 11 }, destination = { x = 33685, y = 31599, z = 14 } }, -- goshnar's malice entrance - { position = { x = 33856, y = 31884, z = 5 }, destination = { x = 33857, y = 31865, z = 6 } }, -- goshnar's cruelty entrance - { position = { x = 33889, y = 31873, z = 3 }, destination = { x = 33830, y = 31881, z = 4 } }, -- 1st to 2nd floor cloak - { position = { x = 33829, y = 31880, z = 4 }, destination = { x = 33856, y = 31889, z = 5 } }, -- 2nd to 3rd floor cloak -} - -local portal = { position = { x = 33914, y = 31032, z = 12 }, destination = { x = 33780, y = 31601, z = 14 } } -- goshnar's hatred entrance - -local soulWarEntrances = MoveEvent() -function soulWarEntrances.onStepIn(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - for value in pairs(config) do - if Position(config[value].position) == player:getPosition() then - player:teleportTo(Position(config[value].destination)) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return true - end - end -end - -soulWarEntrances:type("stepin") -for value in pairs(config) do - soulWarEntrances:position(config[value].position) -end -soulWarEntrances:register() - -local portalHatred = Action() -function portalHatred.onUse(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - doSendMagicEffect(item:getPosition(), CONST_ME_TELEPORT) - player:teleportTo(Position(portal.destination)) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return true -end - -portalHatred:position(portal.position) -portalHatred:register() diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/scripts/lib/quests/soul-war.lua new file mode 100644 index 00000000000..ba44d49943f --- /dev/null +++ b/data-otservbr-global/scripts/lib/quests/soul-war.lua @@ -0,0 +1,1343 @@ +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 + + baseBagYouDesireChance = 1, -- 1% base chance + bagYouDesireChancePerTaint = 1, -- Increases 1% 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.", + }, + bagYouDesireBosses = { + "Goshnar's Cruelty", + "Goshnar's Spite", + "Goshnar's Malice", + "Goshnar's Hatred", + "Goshnar's Greed", + }, + + -- Goshnar's Cruelty pulsating energy monsters + pulsatingEnergyMonsters = { + "Vibrant Phantom", + "Cloak of Terror", + "Courage Leech", + }, + + 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 }, + }, + + -- 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), + }, + 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.debug("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") + 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" }, + }, + + 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", + }, + + -- 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.debug("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 } }, + }, +} + +-- Initialize ebb and flow zone area +SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 33964, y = 31147, z = 9 }) + +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 +} + +SoulWarBosses = { + ["Goshnar's Malice"] = true, + ["Goshnar's Hatred"] = true, + ["Goshnar's Spite"] = true, + ["Goshnar's Cruelty"] = true, + ["Goshnar's Greed"] = true, +} + +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.debug("Spawning {} in position {}", name, position:toString()) + end + + for i = 7, 9 do + addEvent(sendEffect, i * 1000) + end + + addEvent(spawnMonster, 10000) +end + +local soulWarSpawnMonsters = { + ["soulwars.claustrophobic-inferno"] = "Brachiodemon", + ["soulwars.mirrored-nightmare"] = "Many Faces", + ["soulwars.ebb-and-flow"] = "Bony Sea Devil", + ["soulwars.furious-crater"] = "Cloak of Terror", + ["soulwars.rotten-wasteland"] = "Branchy Crawler", + ["boss-rooms"] = "Dreadful Harvester", +} + +function RemoveSoulCageAndBuffMalice() + local tile = Tile(SoulCagePosition) + local creatures = tile:getCreatures() or {} + local soulCage + for i, creature in ipairs(creatures) do + if creature:getName() == "Soul Cage" then + soulCage = creature + logger.debug("Removing Soul Cage, the players not be able to kill him") + break + end + end + + local rangeX = 20 + local rangeY = 20 + local spectators = Game.getSpectators(Position(33709, 31599, 14), false, false, rangeX, rangeX, rangeY, rangeY) + if soulCage then + local malice + for i = 1, #spectators do + logger.debug("Specs found {}", i) + if spectators[i]:isMonster() then + logger.debug("Malice Spectators {}", spectators[i]:getName()) + if spectators[i]:getName() == "Goshnar's Malice" then + logger.debug("Found malice") + malice = Monster(spectators[i]) + break + end + end + end + + soulCage:remove() + addEvent(SpawnSoulCage, 23000) + + if malice then + logger.debug("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 + for i, creature in ipairs(creatures) do + if creature:getName() == "Soul Cage" then + soulCage = true + break + end + end + + if not soulCage then + Game.createMonster("Soul Cage", SoulCagePosition, true, true) + logger.debug("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 function revertTilesAndApplyDamage(zonePositions) + 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 + pos:sendMagicEffect(CONST_ME_REDSMOKE) + player:addHealth(-8000, COMBAT_DEATHDAMAGE) + end + 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 Monster:generateBagYouDesireLoot(player) + local playerTaintLevel = player:getTaintLevel() + if not playerTaintLevel then + return {} + end + + local monsterName = self:getName() + local isMonsterValid = false + for _, monster in ipairs(SoulWarQuest.bagYouDesireMonsters) do + if monsterName == monster then + isMonsterValid = true + break + end + end + + if not isMonsterValid then + return {} + end + + -- Calculates the chances based on the number of taints + local totalChance = SoulWarQuest.baseBagYouDesireChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) + logger.debug("Player {} killed {} and has {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) + -- Generate loot + local loot = {} + if math.random(1, 100) <= totalChance then + local itemType = ItemType(SoulWarQuest.bagYouDesireItemId) + if itemType then + loot[itemType:getId()] = { count = 1 } + logger.debug("Player {} killed {} and got a bag you desire with drop chance {}", player:getName(), monsterName, totalChance) + end + end + + return loot +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) + local haveTaintName = nil + local soulWarQuest = self:soulWarQuestKV() + local taintName = soulWarTaints[taintNumber] + if taintName and soulWarQuest:get(taintName) then + haveTaintName = taintName + end + + return haveTaintName +end + +function Player:addNextTaint() + local soulWarQuest = self:soulWarQuestKV() + for _, taint in ipairs(soulWarTaints) do + if not soulWarQuest:get(taint) then + soulWarQuest:set(taint, true) + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have gained the " .. taint .. ".") + break + end + 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() + local soulWarQuest = self:soulWarQuestKV() + local firstTaintTime = soulWarQuest:get("firstTaintTime") + if firstTaintTime and os.time() >= (firstTaintTime + TaintDurationSeconds) then + -- Reset all taints + for _, taint in ipairs(soulWarTaints) do + if soulWarQuest:get(taint) then + soulWarQuest:remove(taint) + end + end + + soulWarQuest:remove("firstTaintTime") + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your goshnar's taints have been reset. You didn't finish the quest in 14 days") + 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 + logger.debug("Checking teleport monster for monster {}", self:getName()) + for i, spectator in ipairs(spectators) do + if spectator:isPlayer() then + local player = spectator:getPlayer() + if player:getTaintNameByNumber(1) ~= nil then + local distance = self:getPosition():getDistance(player:getPosition()) + if distance > maxDistance then + maxDistance = distance + farthestPlayer = player + logger.debug("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.debug("Cooldown is active to player {}", farthestPlayer:getName()) + return + end + + if not TaintTeleportCooldown[farthestPlayer:getId()] then + TaintTeleportCooldown[farthestPlayer:getId()] = true + + logger.debug("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.debug("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 or 10) +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.debug("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.debug("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() + if tormentCounter <= maxLimit then + player:increaseGoshnarSymbolTormentCounter(maxLimit) + logger.debug("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.debug("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 + 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.debug("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) + logger.debug("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.debug("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 + logger.debug("{} old defense {}", self:getName(), self:getDefense()) + self:addDefense(SoulWarQuest.goshnarsCrueltyDefenseChange) + logger.debug("{} new defense {}", self:getName(), self:getDefense()) + + --- 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.debug("{} skips increase cooldown due to recent item use.", self:getName()) + end +end + +function Player:getSoulWarZoneMonster() + local zoneMonsterName = nil + for zoneName, monsterName in pairs(soulWarSpawnMonsters) 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.debug("Player {} is in boat spot", self:getName()) + return true + end + + logger.debug("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 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())) + 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 + + local condition = Condition(CONDITION_OUTFIT) + condition:setTicks(outfitConfig.time) + condition:setOutfit(outfitConfig.outfit) + target:addCondition(condition) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + + addEvent(delayedCastSpell, SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, self:getId(), var, combat, target:getId()) + + return true +end + +function string.toPosition(str) + local patterns = { + -- table format + "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", + -- Position format + "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", + -- x, y, z format + "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", + } + + for _, pattern in ipairs(patterns) do + local x, y, z = string.match(str, pattern) + if x and y and z then + return Position(tonumber(x), tonumber(y), tonumber(z)) + end + end +end diff --git a/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua b/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua new file mode 100644 index 00000000000..fae3fc59794 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua @@ -0,0 +1,60 @@ +local rewardSoulWar = Action() + +function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, isHotkey) + local rewardItem = SoulWarQuest.finalRewards[math.random(1, #SoulWarQuest.finalRewards)] + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + if soulWarQuest:get("final-reward") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already received your reward.") + return true + end + + if not soulWarQuest:get("goshnar's-megalomania-killed") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to defeat Goshnar's Megalomania to receive your reward.") + return true + end + + player:addItem(rewardItem.id, 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. rewardItem.name .. ".") + soulWarQuest:set("final-reward", true) + return true +end + +rewardSoulWar:position({ x = 33620, y = 31400, z = 10 }) +rewardSoulWar:register() + +local phantasmalJadeMount = Action() + +function phantasmalJadeMount.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local soulWarQuest = player:soulWarQuestKV() + if soulWarQuest:get("panthasmal-jade-mount") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already have Phantasmal Jade mount!") + return true + end + + if table.contains({ 34072, 34073, 34074 }, item.itemid) then + if player:getItemCount(34072) >= 4 and player:getItemCount(34073) == 1 and player:getItemCount(34074) == 1 then + player:removeItem(34072, 4) + player:removeItem(34073, 1) + player:removeItem(34074, 1) + player:addMount(167) + player:addAchievement("You got Horse Power") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won Phantasmal Jade mount.") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won You got Horse Power achievement.") + player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE) + soulWarQuest:set("panthasmal-jade-mount", true) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have the necessary items!") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + end + + return true +end + +phantasmalJadeMount:id(34072, 34073, 34074) +phantasmalJadeMount:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua new file mode 100644 index 00000000000..01637f1aff8 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua @@ -0,0 +1,127 @@ +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 + logger.debug("Checking soul war fifth taint, interval {}", interval) + 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("Removing '{}' mana and '{}' health from player {}", manaLoss, hpLoss, player:getName()) + end + + accumulatedTime[playerId] = 0 + end +end + +callback:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua new file mode 100644 index 00000000000..04fe2035956 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua @@ -0,0 +1,16 @@ +local callback = EventCallback("MonsterOnDropBagYouDesire") + +function callback.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 + + corpse:addLoot(monster:generateBagYouDesireLoot(player)) +end + +callback:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua new file mode 100644 index 00000000000..7bc644448b3 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua @@ -0,0 +1,132 @@ +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.debug("Teleporting player to down.") + end + player:sendCreatureAppear() + end + end + end + + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.empty) + SoulWarQuest.ebbAndFlow.setLoadedEmptyMap(true) + SoulWarQuest.ebbAndFlow.setActive(false) + + local updatePlayers = EventCallback("UpdatePlayersEmptyEbbFlowMap") + function updatePlayers.mapOnLoad(mapPath) + if mapPath ~= SoulWarQuest.ebbAndFlow.mapsPath.empty then + return + end + + SoulWarQuest.ebbAndFlow.updateZonePlayers() + end + + updatePlayers:register() + + addEvent(function() + -- Change the appearance of puddles to indicate the next filling + updateWaterPoolsSize() + end, 80000) -- 80 seconds +end + +local function getDistance(pos1, pos2) + return math.sqrt((pos1.x - pos2.x) ^ 2 + (pos1.y - pos2.y) ^ 2 + (pos1.z - pos2.z) ^ 2) +end + +local function findNearestRoomPosition(playerPosition) + local nearestPosition = nil + local smallestDistance = nil + for _, room in ipairs(SoulWarQuest.ebbAndFlow.centerRoomPositions) do + local distance = getDistance(playerPosition, room.conor) + if not smallestDistance or distance < smallestDistance then + smallestDistance = distance + nearestPosition = room.teleportPosition + end + end + return nearestPosition +end + +local function loadMapInundate() + if SoulWarQuest.ebbAndFlow.getZone():countPlayers() > 0 then + local players = SoulWarQuest.ebbAndFlow.getZone():getPlayers() + for _, player in ipairs(players) do + local playerPosition = player:getPosition() + if playerPosition.z == 9 then + if player:isInBoatSpot() then + local nearestCenterPosition = findNearestRoomPosition(playerPosition) + player:teleportTo(nearestCenterPosition) + logger.debug("Teleporting player to the near center position room and updating tile.") + else + player:teleportTo(SoulWarQuest.ebbAndFlow.waitPosition) + logger.debug("Teleporting player to wait position and updating tile.") + end + playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + player:sendCreatureAppear() + end + end + + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.inundate) + SoulWarQuest.ebbAndFlow.setLoadedEmptyMap(false) + SoulWarQuest.ebbAndFlow.setActive(true) + + local updatePlayers = EventCallback("UpdatePlayersInundateEbbFlowMap") + function updatePlayers.mapOnLoad(mapPath) + if mapPath ~= SoulWarQuest.ebbAndFlow.mapsPath.inundate then + return + end + + SoulWarQuest.ebbAndFlow.updateZonePlayers() + end + + updatePlayers:register() +end + +local loadEmptyMap = GlobalEvent("SoulWarQuest.ebbAndFlow") +function loadEmptyMap.onStartup() + loadMapEmpty() + SoulWarQuest.ebbAndFlow.updateZonePlayers() +end + +loadEmptyMap:register() + +local eddAndFlowInundate = GlobalEvent("eddAndFlowInundate") +function eddAndFlowInundate.onThink(interval, lastExecution) + if SoulWarQuest.ebbAndFlow.isLoadedEmptyMap() then + logger.debug("Map change to empty in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + loadMapInundate() + elseif SoulWarQuest.ebbAndFlow.isActive() then + logger.debug("Map change to inundate in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + loadMapEmpty() + end + + return true +end + +eddAndFlowInundate:interval(SoulWarQuest.ebbAndFlow.intervalChangeMap * 60 * 1000) +eddAndFlowInundate:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua new file mode 100644 index 00000000000..e1728e3a66d --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua @@ -0,0 +1,151 @@ +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 + + [Position(34022, 31091, 11)] = Position(33685, 31599, 14), -- goshnar's malice entrance +} + +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(SoulWarBosses) 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 areasConfig = { + [Position(34013, 31049, 9)] = Position(34014, 31058, 9), + [Position(34010, 31073, 10)] = Position(34012, 31063, 10), + [Position(34009, 31038, 11)] = Position(34012, 31047, 11), +} + +local soul_war_areas_timer = MoveEvent() + +function soul_war_areas_timer.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(areasConfig) do + if tablePosition == position then + player:teleportTo(toPosition) + toPosition:sendMagicEffect(CONST_ME_TELEPORT) + break + end + end + + return true +end + +for key, value in pairs(areasConfig) do + soul_war_areas_timer:position(key) +end + +soul_war_areas_timer:register() + +local goshnarSpiteEntrance = MoveEvent() + +function goshnarSpiteEntrance.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + local killCount = soulWarQuest:get("hazardous-phantom-death") or 0 + if killCount < 20 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have killed " .. killCount .. " and need to kill 20 Hazardous Phantoms") + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return false + end + + if position == SoulWarQuest.goshnarSpiteEntrancePosition.fromPos then + player:teleportTo(SoulWarQuest.goshnarSpiteEntrancePosition.toPos) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + end + + return false +end + +goshnarSpiteEntrance:position(SoulWarQuest.goshnarSpiteEntrancePosition.fromPos) +goshnarSpiteEntrance:register() diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/portal_reward_soulwar.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua similarity index 78% rename from data-otservbr-global/scripts/actions/quests/soul_war/portal_reward_soulwar.lua rename to data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua index cf999f89c7b..7aaff112056 100644 --- a/data-otservbr-global/scripts/actions/quests/soul_war/portal_reward_soulwar.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua @@ -1,10 +1,14 @@ local portalReward = MoveEvent() + function portalReward.onStepIn(creature, item, position, fromPosition) local player = creature:getPlayer() if not player then return false end - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.GoshnarMegalomaniaKilled) < 1 then + + local soulWarQuest = player:soulWarQuestKV() + -- Checks if the boss has already been defeated + if not soulWarQuest:get("goshnar's-megalomania-killed") then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Only warriors who defeated Goshnar's Megalomania can access this area.") player:teleportTo(fromPosition, true) return false @@ -15,6 +19,5 @@ function portalReward.onStepIn(creature, item, position, fromPosition) return true end -portalReward:type("stepin") portalReward:position({ x = 33621, y = 31416, z = 10 }) portalReward:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua new file mode 100644 index 00000000000..8daa70f3060 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -0,0 +1,1081 @@ +-- 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()) + +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) + 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 SoulWarBosses[bossName] then + 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(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 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)) + if taintName ~= nil then + 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()) + 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 setTaint = TalkAction("/changeflowmap") + +function setTaint.onSay(player, words, param) + if param == "empty" then + Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm") + elseif param == "inundate" then + Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/inundate.otbm") + elseif param == "ebb" then + Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb_and_flow.otbm") + end +end + +setTaint:separator(" ") +setTaint:groupType("god") +setTaint: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 zone = Zone.getByName("boss.goshnar's-spite") + if not zone then + logger.error("Goshnar's Spite zone not found. Check the lever boss register.") + return + end + + local monsters = zone:getMonsters() + for _, monster in ipairs(monsters) do + if monster:getName() == "Goshnar's Spite" 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 + break + 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 zone = Zone.getByName("boss.goshnar's-spite") + if not zone then + logger.error("Goshnar's Spite zone not found. Check the lever boss register.") + return + end + + local monsters = zone:getMonsters() + for _, monster in ipairs(monsters) do + if monster:getName() == "Goshnar's Spite" then + monster:addDefense(SoulWarQuest.goshnarsSpiteIncreaseDefense) + logger.debug("Found Goshnar's Spite on boss zone, adding defense.") + break + end + 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 burningHatredMonsters = { + "Ashes of Burning Hatred", + "Spark of Burning Hatred", + "Flame of Burning Hatred", + "Blaze of Burning Hatred", +} + +local goshnarsHatredSorrow = Action() + +function goshnarsHatredSorrow.onUse(player, item, fromPosition, target, toPosition, isHotkey) + logger.debug("Player {} used the item {} on target {}.", player:getName(), item:getId(), target:getName()) + if not target then + return + end + + if not table.contains(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() + 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_INFO_DESCR, "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_INFO_DESCR, "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_INFO_DESCR, "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_INFO_DESCR, "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_INFO_DESCR, "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 + Game.createItem(SoulWarQuest.theBloodOfCloakTerrorIds[1], 1, creature:getPosition()) + 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 + + 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_INFO_DESCR, "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 + 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) + + 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!") + item:remove() + 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_INFO_DESCR, "Use the item again within " .. timeToIncreaseDefense .. " seconds, or the monster's defense will increase by 2 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() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua b/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua new file mode 100644 index 00000000000..7a73979930a --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua @@ -0,0 +1,38 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_SMALLCLOUDS) + +combat:setArea(createCombatArea({ + { 1 }, + { 1 }, + { 3 }, +})) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + local target = tile:getTopCreature() + if tile then + if target then + if target:isMonster() and target:getName() == "Poor Soul" then + target:addHealth(-1000) + end + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("greedy eye beam") +spell:words("greedy eye beam") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua new file mode 100644 index 00000000000..2bc321f24e0 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua @@ -0,0 +1,61 @@ +local areaSpell = { + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(areaSpell) + +local combat = Combat() +combat:setArea(area) +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + if tile then + local target = tile:getTopCreature() + if target and target:isPlayer() then + target:addHealth(math.random(2300, 3000)) + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local function delayedCastSpell(cid, var, targetId) + local creature = Creature(cid) + if not creature then + return + end + + local target = Player(targetId) + if target then + combat:execute(creature, positionToVariant(target:getPosition())) + end +end + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var, isHotkey) + return creature:applyZoneEffect(var, combat, "boss.goshnar's-cruelty") +end + +spell:name("cruelty transform elemental") +spell:words("cruelty transform elemental") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua new file mode 100644 index 00000000000..2906f213d74 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua @@ -0,0 +1,54 @@ +local areaSpell = { + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(areaSpell) + +local combat = Combat() +combat:setArea(area) +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + if tile then + local target = tile:getTopCreature() + if target and target:isPlayer() then + target:addHealth(math.random(2300, 3000)) + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local config = { + outfit = { lookType = 242, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, lookAddons = 0 }, + time = 7000, +} + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var, isHotkey) + return creature:applyZoneEffect(var, combat, "boss.goshnar's-megalomania-purple") +end + +spell:name("megalomania transform elemental") +spell:words("megalomania transform elemental") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua b/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua new file mode 100644 index 00000000000..d8a9c8e9875 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua @@ -0,0 +1,58 @@ +local area = { + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local createArea = createCombatArea(area) + +local combat = Combat() +combat:setArea(createArea) + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +function onTargetTile(creature, pos) + for _, pos in ipairs(zonePositions) do + local tile = Tile(pos) + if tile and tile:getGround() and tile:getGround():getId() ~= 409 then + local creature = tile:getTopCreature() + if creature then + local player = creature:getPlayer() + if player then + player:addHealth(-6000, COMBAT_DEATHDAMAGE) + end + end + end + end + + pos:sendMagicEffect(CONST_ME_BLACKSMOKE) + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, positionToVariant(creature:getPosition())) +end + +spell:name("megalomania blue") +spell:words("megalomania blue") +spell:isAggressive(true) +spell:blockWalls(false) +spell:needLearn(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua b/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua new file mode 100644 index 00000000000..9292f10530f --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua @@ -0,0 +1,58 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) + +combat:setArea(createCombatArea(CrossBeamArea3X2)) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-lifedrain-beam") +spell:words("soulsnatcher-lifedrain-beam") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_HOLY) + +spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-lifedrain-missile") +spell:words("soulsnatcher-lifedrain-missile") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needTarget(true) +spell:register() + +-- Mana drain ball +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) + +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-manadrain-ball") +spell:words("soulsnatcher-manadrain-ball") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:register() diff --git a/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua b/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua index 65e6af5a3b2..7da5f5d1b99 100644 --- a/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua +++ b/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua @@ -1,4 +1,4 @@ -local ironServantTransformation = EventCallback() +local ironServantTransformation = EventCallback("IronServantTransformationOnSpawn") ironServantTransformation.monsterOnSpawn = function(monster, position) if monster:getName():lower() ~= "iron servant replica" then diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index d3bbc69e858..84419e700e7 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -34109,6 +34109,12 @@ + + + + + + @@ -34219,6 +34225,9 @@ + + + @@ -34244,6 +34253,9 @@ + + + @@ -48967,11 +48979,20 @@ + + + + + + + + + - - + + @@ -48979,18 +49000,12 @@ - - - - - - - - - + + + @@ -49000,6 +49015,9 @@ + + + @@ -49018,15 +49036,12 @@ + + + - - - - - - @@ -49034,9 +49049,6 @@ - - - @@ -49056,6 +49068,9 @@ + + + @@ -49112,9 +49127,6 @@ - - - @@ -49209,10 +49221,19 @@ + + + + + + + + + @@ -49225,8 +49246,8 @@ - - + + @@ -49277,11 +49298,14 @@ - + - - + + + + + @@ -49305,11 +49329,8 @@ - - - - - + + @@ -49317,6 +49338,12 @@ + + + + + + @@ -49326,11 +49353,14 @@ - + - - + + + + + @@ -49341,14 +49371,8 @@ - - - - - - - - + + @@ -49356,17 +49380,11 @@ - - - - - - - - + + @@ -49392,9 +49410,6 @@ - - - @@ -49406,25 +49421,37 @@ - - + + - + - - + + - - + + - - + + - + + + + - + + + + + + + + + + @@ -49441,21 +49468,6 @@ - - - - - - - - - - - - - - - @@ -49571,6 +49583,12 @@ + + + + + + @@ -49597,14 +49615,8 @@ - - - - - - - - + + @@ -49613,8 +49625,11 @@ - - + + + + + @@ -49649,24 +49664,30 @@ - + + + + + + + - + - - + + - - + + @@ -49677,24 +49698,21 @@ + + + - - - - - - - - + + @@ -49724,8 +49742,8 @@ - - + + @@ -49733,6 +49751,12 @@ + + + + + + @@ -49745,12 +49769,6 @@ - - - - - - @@ -49760,17 +49778,17 @@ + + + - - - - - + + @@ -49791,6 +49809,9 @@ + + + @@ -49812,8 +49833,8 @@ - - + + @@ -49854,8 +49875,8 @@ - - + + @@ -49867,19 +49888,13 @@ - - - - - - - + @@ -49916,8 +49931,8 @@ - - + + @@ -49962,7 +49977,7 @@ - + @@ -49974,9 +49989,6 @@ - - - @@ -49995,6 +50007,9 @@ + + + @@ -50028,10 +50043,7 @@ - - - - + @@ -50127,6 +50139,9 @@ + + + @@ -50136,6 +50151,9 @@ + + + @@ -50151,8 +50169,8 @@ - - + + @@ -50172,6 +50190,12 @@ + + + + + + @@ -50181,12 +50205,6 @@ - - - - - - @@ -50202,9 +50220,6 @@ - - - @@ -50242,8 +50257,8 @@ - - + + @@ -50257,6 +50272,9 @@ + + + @@ -50275,6 +50293,9 @@ + + + @@ -50284,6 +50305,9 @@ + + + @@ -50298,9 +50322,6 @@ - - - @@ -50310,15 +50331,12 @@ - + - + - - - @@ -50329,8 +50347,11 @@ - - + + + + + @@ -50346,12 +50367,6 @@ - - - - - - @@ -50359,9 +50374,12 @@ - + + + + @@ -50377,6 +50395,12 @@ + + + + + + @@ -50393,6 +50417,9 @@ + + + @@ -50411,12 +50438,6 @@ - - - - - - @@ -50433,12 +50454,9 @@ - + - - - @@ -50451,8 +50469,8 @@ - - + + @@ -50460,9 +50478,6 @@ - - - @@ -50487,8 +50502,8 @@ - - + + @@ -50537,9 +50552,6 @@ - - - @@ -73808,20 +73820,34 @@ + + + + + + + + + + + + + + + + + + + + - - - - - - - + @@ -73833,22 +73859,8 @@ - - - - - - - - - - - - - - - - + + @@ -73863,6 +73875,15 @@ + + + + + + + + + @@ -73914,15 +73935,6 @@ - - - - - - - - - @@ -73970,6 +73982,14 @@ + + + + + + + + @@ -73988,14 +74008,6 @@ - - - - - - - - @@ -74060,8 +74072,23 @@ - - + + + + + + + + + + + + + + + + + @@ -74091,6 +74118,9 @@ + + + @@ -74146,24 +74176,6 @@ - - - - - - - - - - - - - - - - - - @@ -74182,6 +74194,15 @@ + + + + + + + + + @@ -74234,15 +74255,6 @@ - - - - - - - - - @@ -74255,6 +74267,12 @@ + + + + + + @@ -74283,12 +74301,6 @@ - - - - - - @@ -74350,6 +74362,18 @@ + + + + + + + + + + + + @@ -74359,6 +74383,23 @@ + + + + + + + + + + + + + + + + + @@ -74386,16 +74427,7 @@ - - - - - - - - - - + @@ -74438,14 +74470,6 @@ - - - - - - - - @@ -74464,15 +74488,6 @@ - - - - - - - - - @@ -74494,9 +74509,6 @@ - - - @@ -74607,6 +74619,15 @@ + + + + + + + + + @@ -74640,15 +74661,6 @@ - - - - - - - - - @@ -74727,6 +74739,14 @@ + + + + + + + + @@ -74746,6 +74766,17 @@ + + + + + + + + + + + @@ -74769,13 +74800,14 @@ - - + + - - - - + + + + + @@ -74818,17 +74850,6 @@ - - - - - - - - - - - @@ -74843,15 +74864,6 @@ - - - - - - - - - @@ -74872,6 +74884,9 @@ + + + @@ -74896,6 +74911,9 @@ + + + @@ -74918,9 +74936,6 @@ - - - @@ -74977,9 +74992,6 @@ - - - @@ -75028,35 +75040,22 @@ - - - - - - - - - - - - - - + - - + + - + - + @@ -75065,6 +75064,19 @@ + + + + + + + + + + + + + @@ -75095,6 +75107,15 @@ + + + + + + + + + @@ -75106,6 +75127,9 @@ + + + @@ -75141,15 +75165,6 @@ - - - - - - - - - @@ -75180,9 +75195,6 @@ - - - @@ -75208,6 +75220,15 @@ + + + + + + + + + @@ -75285,15 +75306,6 @@ - - - - - - - - - @@ -75317,6 +75329,15 @@ + + + + + + + + + @@ -75329,6 +75350,14 @@ + + + + + + + + @@ -75349,15 +75378,6 @@ - - - - - - - - - @@ -75407,13 +75427,8 @@ - - - - - - - + + @@ -75442,6 +75457,16 @@ + + + + + + + + + + @@ -75479,25 +75504,12 @@ - - - - - - - - - - - - - @@ -75584,6 +75596,15 @@ + + + + + + + + + @@ -75621,15 +75642,6 @@ - - - - - - - - - @@ -75654,6 +75666,16 @@ + + + + + + + + + + @@ -75706,15 +75728,13 @@ - - - - - - - + + + + + - + @@ -75769,13 +75789,13 @@ - - - - - - - + + + + + + + @@ -75852,13 +75872,15 @@ - - - - - + + + + + - + + + @@ -75902,6 +75924,16 @@ + + + + + + + + + + @@ -75911,16 +75943,6 @@ - - - - - - - - - - @@ -75951,15 +75973,13 @@ - - - - - - - - - + + + + + + + @@ -75996,14 +76016,6 @@ - - - - - - - - @@ -162037,6 +162049,9 @@ + + + @@ -162063,6 +162078,9 @@ + + + @@ -162072,6 +162090,12 @@ + + + + + + @@ -162119,6 +162143,9 @@ + + + @@ -162201,6 +162228,9 @@ + + + @@ -162214,6 +162244,12 @@ + + + + + + @@ -162221,6 +162257,15 @@ + + + + + + + + + diff --git a/data-otservbr-global/world/otservbr-npc.xml b/data-otservbr-global/world/otservbr-npc.xml index 10772902341..8a2e5ebb3e9 100644 --- a/data-otservbr-global/world/otservbr-npc.xml +++ b/data-otservbr-global/world/otservbr-npc.xml @@ -2451,6 +2451,9 @@ + + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml new file mode 100644 index 00000000000..a9224bd3c2d --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm new file mode 100644 index 0000000000000000000000000000000000000000..7892efac0906d82faaa89ea3905180de70e1abcc GIT binary patch literal 129535 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 literal 0 HcmV?d00001 diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml new file mode 100644 index 00000000000..a9224bd3c2d --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/inundate.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate.otbm similarity index 83% rename from data-otservbr-global/world/quest/soul_war/ebb_and_flow/inundate.otbm rename to data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate.otbm index 1790545b34b30062748ce1249d4e00616b248c67..98992c326dd4532fb48d78bcc3c2c083cff1266e 100644 GIT binary patch delta 4311 zcmZvfYiv|S6oA|9xrJ+J3A(F+6Lw`?%Y7TUtyX z(C*zn5q9_PPW@#kWsNm*>$d?T1WVNo5|LKhhm-HMJ}9X+Ml8tx;2JKmimK$;R<%b-GZUcB#`v zUrocW)g?uX%@m9MRgi3*BfT}?k*z7&Uo}VMTBwpNk-Sg~wfyIL`qRPrYhfiRUkQ_g zHh*)5G*lIR7hc~S@X1f8w;M<2sD02!?TzBAmJssE4|77; zN6C4O!Wd>2;SBl03!|Jh0tL5f&~__J<}kz^h6INp%!IdF z8^D+_XTky#=1KV9fo=MW@4_>M)rRD6wsK{w@$a%FP9j^Nf~Cv!ZFW6fYTIshhT2-J zN?~6;fPDr8Ccv2hW`dXrBBsb}5cjvhvK@vL!bTxvL~Up?A?`SY9fvU!wlk!6E9}=- zY=cW~sgTyj4RU86)Gp#>LLTpf>P5V?Ncnyw)SJS**&b6c&k0>UIcf@zx$v){tK442_9}L-V)HmRk27<$mbkE%xN|Kv(uhwkH=E7Kdbz?HH}G+zIg-zh zz+{PS1IU{_;E}8;vZn{CWJ8K=V&d7J+%5YP{psNT?tyxJS1-I;TKGhd$Kh_F&=XU* z9cj2HYP=n3LOarUJGwWT=i8^5GBXCNgu2LZaLrhrA|^PKDV)g^&tyu-WQseK$0172 zkHe!1R39lv%eXKB%RGjp;kKgLY)Hi$rI!-W#oSDw^g_B(QGgXT9grjLVn#QAPLNtv#oSDfJEzOlTW@s-7oB>ba zLT@spQ{8BBr;cEuM_5!BQYSE&z+gfElNd~PHTCIvEu$rKKm!eB}OH4JJP)Q;># z%g_Wejmb17(>P?>4w74+L8E@?9CVd#L+*peotqdOV$X))Fn&`WLrlodkbRfzXC`dd z(=!w1KEuQ4X{unKiohtGQHIf(iC`vz80*aZatW5{&EG;p5kBJW$jp4Od~z8ou)f2g z&t9evkHg==(?ZgFh2Ci|eGkophSWFe+^N1XVP;&InQ+eZGe7Je>t`;wz-TTokO>1m zA^3n0d_V|3U=L27y-NQkyM7g#-E=vVw!}z%Hg^N<&IS=7I~1<^ltmkTRLUf&wmK zD21UEhBO>V%P~f=caiyE)i9`GFpa@<4jiOV8bfIeWf+7yaE6h1hS7M2xj-`{#-0*H zrK3^2y2{Lz#Hm1P?m-zjeGhgm!F!sSg-XeqAeFP=S&eo#HO5YQY8;(Pja3>^XJHNJ TF3bw`aBG&nJ6`)8jLQE3nPRaX delta 7138 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 diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm deleted file mode 100644 index 70036d063b14a9694c6166b1bd317cf887aeb285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13957 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 HU=#R%=4!6# diff --git a/data/items/items.xml b/data/items/items.xml index 765c6e9db51..c4ba2b3a93b 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -62027,9 +62027,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> - - - + @@ -62471,6 +62469,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> + @@ -62893,21 +62892,15 @@ hands of its owner. Granted by TibiaRoyal.com"/> - - - - - - - - - - - + + + + + - + @@ -63061,6 +63054,10 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + @@ -63156,6 +63153,11 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + + @@ -63175,6 +63177,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> + @@ -63374,6 +63377,10 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + @@ -63520,6 +63527,19 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + + + + + + + + + + @@ -64257,7 +64277,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> - + diff --git a/data/libs/functions/bosslever.lua b/data/libs/functions/bosslever.lua index 3792cdf629d..5834b4a9a5e 100644 --- a/data/libs/functions/bosslever.lua +++ b/data/libs/functions/bosslever.lua @@ -144,6 +144,7 @@ end ---@param player Player ---@return boolean function BossLever:onUse(player) + local monsterName = MonsterType(self.name):getName() local isParticipant = false for _, v in ipairs(self.playerPositions) do if Position(v.pos) == player:getPosition() then @@ -161,7 +162,7 @@ function BossLever:onUse(player) local zone = self:getZone() if zone:countPlayers(IgnoredByMonsters) > 0 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. self.name .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. monsterName .. ".") return true end @@ -178,13 +179,13 @@ function BossLever:onUse(player) return false end - if self:lastEncounterTime(creature) > os.time() then + if creature:getGroup():getId() < GROUP_TYPE_GOD and self:lastEncounterTime(creature) > os.time() then local info = lever:getInfoPositions() for _, v in pairs(info) do local newPlayer = v.creature if newPlayer then local timeLeft = self:lastEncounterTime(newPlayer) - os.time() - newPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait " .. getTimeInWords(timeLeft) .. " to face " .. self.name .. " again!") + newPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait " .. getTimeInWords(timeLeft) .. " to face " .. monsterName .. " again!") if self:lastEncounterTime(newPlayer) > os.time() then newPlayer:getPosition():sendMagicEffect(CONST_ME_POFF) end diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index be95a45144c..376f828c5b0 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -208,7 +208,7 @@ end function Creature.getKillers(self, onlyPlayers) local killers = {} local inFightTicks = configManager.getNumber(configKeys.PZ_LOCKED) - local timeNow = os.mtime() + local timeNow = systemTime() local getCreature = onlyPlayers and Player or Creature for cid, cb in pairs(self:getDamageMap()) do local creature = getCreature(cid) diff --git a/data/libs/functions/revscriptsys.lua b/data/libs/functions/revscriptsys.lua index 84b8275e3ce..b7f41b55298 100644 --- a/data/libs/functions/revscriptsys.lua +++ b/data/libs/functions/revscriptsys.lua @@ -286,6 +286,14 @@ do self:eventType(MONSTERS_EVENT_SAY) self:onSay(value) return + elseif key == "onPlayerAttack" then + self:eventType(MONSTERS_EVENT_ATTACKED_BY_PLAYER) + self:onPlayerAttack(value) + return + elseif key == "onSpawn" then + self:eventType(MONSTERS_EVENT_ON_SPAWN) + self:onSpawn(value) + return end rawset(self, key, value) end diff --git a/data/libs/systems/zones.lua b/data/libs/systems/zones.lua index ff37af5b16a..1406242517a 100644 --- a/data/libs/systems/zones.lua +++ b/data/libs/systems/zones.lua @@ -100,7 +100,7 @@ setmetatable(ZoneEvent, { function ZoneEvent:register() if self.beforeEnter then - local beforeEnter = EventCallback() + local beforeEnter = EventCallback("ZoneEventBeforeEnter", true) function beforeEnter.zoneBeforeCreatureEnter(zone, creature) if zone ~= self.zone then return true @@ -112,7 +112,7 @@ function ZoneEvent:register() end if self.beforeLeave then - local beforeLeave = EventCallback() + local beforeLeave = EventCallback("ZoneEventBeforeLeave", true) function beforeLeave.zoneBeforeCreatureLeave(zone, creature) if zone ~= self.zone then return true @@ -124,7 +124,7 @@ function ZoneEvent:register() end if self.afterEnter then - local afterEnter = EventCallback() + local afterEnter = EventCallback("ZoneEventAfterEnter", true) function afterEnter.zoneAfterCreatureEnter(zone, creature) if zone ~= self.zone then return true @@ -136,7 +136,7 @@ function ZoneEvent:register() end if self.afterLeave then - local afterLeave = EventCallback() + local afterLeave = EventCallback("ZoneEventAfterLeave", true) function afterLeave.zoneAfterCreatureLeave(zone, creature) if zone ~= self.zone then return true @@ -148,7 +148,7 @@ function ZoneEvent:register() end if self.onSpawn then - local afterEnter = EventCallback() + local afterEnter = EventCallback("ZoneEventAfterEnter", true) function afterEnter.zoneAfterCreatureEnter(zone, creature) if zone ~= self.zone then return true diff --git a/data/scripts/actions/items/cobra_flask.lua b/data/scripts/actions/items/cobra_flask.lua index ddeecf8ec4f..410e4f1af43 100644 --- a/data/scripts/actions/items/cobra_flask.lua +++ b/data/scripts/actions/items/cobra_flask.lua @@ -1,4 +1,4 @@ -local applyCobraFlaskEffectOnMonsterSpawn = EventCallback() +local applyCobraFlaskEffectOnMonsterSpawn = EventCallback("CobraFlaskEffectOnMonsterSpawn") applyCobraFlaskEffectOnMonsterSpawn.monsterOnSpawn = function(monster, position) if table.contains({ "cobra scout", "cobra vizier", "cobra assassin" }, monster:getName():lower()) then diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md index bdafcb41b33..4b0ea40bc31 100644 --- a/data/scripts/eventcallbacks/README.md +++ b/data/scripts/eventcallbacks/README.md @@ -18,6 +18,7 @@ Event callbacks are available for several categories of game entities, such as ` - `(bool)` `creatureOnTargetCombat` - `(void)` `creatureOnHear` - `(void)` `creatureOnDrainHealth` +- `(void)` `creatureOnCombat` - `(bool)` `partyOnJoin` - `(bool)` `partyOnLeave` - `(bool)` `partyOnDisband` @@ -61,7 +62,7 @@ Below are examples for each category of game entities: ### Creature Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior when a creature enters combat area @@ -74,7 +75,7 @@ callback:register() ### Player Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.playerOnLook(player, position, thing, stackpos, lookDistance) -- custom behavior when a player looks at something @@ -86,7 +87,7 @@ callback:register() ### Party Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.partyOnJoin(party, player) -- custom behavior when a player joins a party @@ -98,7 +99,7 @@ callback:register() ### Monster Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.monsterOnSpawn(monster, position) -- custom behavior when a monster spawns @@ -110,7 +111,7 @@ callback:register() ### Npc Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.npcOnSpawn(npc, position) -- custom behavior when a npc spawns @@ -128,7 +129,7 @@ If the callback returns `false`, the execution of the associated function on the Here is an example of a boolean event callback: ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.creatureOnAreaCombat(creature, tile, isAggressive) -- if the creature is not aggressive, stop the execution of the C++ function @@ -156,7 +157,7 @@ Here is an example of defining multiple callbacks for the creatureOnAreaCombat e #### Example 1 ```lua -local example1 = EventCallback() +local example1 = EventCallback("UniqueCallbackName") function example1.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior 1 when a creature enters combat area @@ -168,7 +169,7 @@ example1:register() #### Example 2 ```lua -local example2 = EventCallback() +local example2 = EventCallback("UniqueCallbackName") function example2.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior 2 when a creature enters combat area diff --git a/data/scripts/eventcallbacks/creature/on_area_combat.lua b/data/scripts/eventcallbacks/creature/on_area_combat.lua index a6295074df3..7653dc1b095 100644 --- a/data/scripts/eventcallbacks/creature/on_area_combat.lua +++ b/data/scripts/eventcallbacks/creature/on_area_combat.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("CreatureOnAreaCombatBaseEvent") function callback.creatureOnAreaCombat(creature, tile, isAggressive) return true diff --git a/data/scripts/eventcallbacks/creature/on_hear.lua b/data/scripts/eventcallbacks/creature/on_hear.lua index 871f6456c9a..2954c81c8fb 100644 --- a/data/scripts/eventcallbacks/creature/on_hear.lua +++ b/data/scripts/eventcallbacks/creature/on_hear.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("CreatureOnHearBaseEvent") function callback.creatureOnHear(creature, speaker, words, type) end diff --git a/data/scripts/eventcallbacks/monster/on_spawn.lua b/data/scripts/eventcallbacks/monster/on_spawn.lua index e40778aafce..5a490a8edac 100644 --- a/data/scripts/eventcallbacks/monster/on_spawn.lua +++ b/data/scripts/eventcallbacks/monster/on_spawn.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnSpawnBase") function callback.monsterOnSpawn(monster, position) if not monster then diff --git a/data/scripts/eventcallbacks/monster/ondroploot__base.lua b/data/scripts/eventcallbacks/monster/ondroploot__base.lua index cb7bc4207e8..900259d4fef 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot__base.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot__base.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootBaseEvent") function Player:canReceiveLoot() return self:getStamina() > 840 @@ -15,7 +15,7 @@ function callback.monsterOnDropLoot(monster, corpse) end local mType = monster:getType() if not mType then - logger.warning("monsterOnDropLoot: monster has no type") + logger.warn("monsterOnDropLoot: monster '{}' has no type", monster:getName()) return end diff --git a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua index 1d94033a572..507df354ab7 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootBoosted") function callback.monsterOnDropLoot(monster, corpse) if not monster or not corpse then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua b/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua index d1fb6ad38b1..4305e60e086 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootGemAtelier") function callback.monsterOnDropLoot(monster, corpse) if not monster or not corpse then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua index ddcaeaee782..92ae22482fd 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootHazard") function callback.monsterOnDropLoot(monster, corpse) if not monster:hazard() then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua index 1f732f173d2..a0fce3d4457 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootPrey") function callback.monsterOnDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) diff --git a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua index 20202c0b5cc..ffba226956a 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootWealthDuplex") function callback.monsterOnDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) diff --git a/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua b/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua index 8214ac98ea1..4c1e53e6f77 100644 --- a/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua +++ b/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterPostDropLootAnalyzer") function callback.monsterPostDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) diff --git a/data/scripts/eventcallbacks/party/on_disband.lua b/data/scripts/eventcallbacks/party/on_disband.lua index 60838962395..93bb1578fd1 100644 --- a/data/scripts/eventcallbacks/party/on_disband.lua +++ b/data/scripts/eventcallbacks/party/on_disband.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PartyOnDisbandEventBaseEvent") function callback.partyOnDisband(party) local members = party:getMembers() diff --git a/data/scripts/eventcallbacks/player/on_browse_field.lua b/data/scripts/eventcallbacks/player/on_browse_field.lua index a4f00341202..3b674a608a7 100644 --- a/data/scripts/eventcallbacks/player/on_browse_field.lua +++ b/data/scripts/eventcallbacks/player/on_browse_field.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnBrowseFieldBaseEvent") function callback.playerOnBrowseField(player, position) return true diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua index 90d3089052d..c4743052cfa 100644 --- a/data/scripts/eventcallbacks/player/on_look.lua +++ b/data/scripts/eventcallbacks/player/on_look.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookBaseEvent") function callback.playerOnLook(player, thing, position, distance) local description = "You see " diff --git a/data/scripts/eventcallbacks/player/on_look_in_shop.lua b/data/scripts/eventcallbacks/player/on_look_in_shop.lua index bd96296624d..0e724009b5e 100644 --- a/data/scripts/eventcallbacks/player/on_look_in_shop.lua +++ b/data/scripts/eventcallbacks/player/on_look_in_shop.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookInShopBaseEvent") function callback.playerOnLookInShop(player, itemType, count) return true diff --git a/data/scripts/eventcallbacks/player/on_look_in_trade.lua b/data/scripts/eventcallbacks/player/on_look_in_trade.lua index 77711c5b95d..c82dd9905f0 100644 --- a/data/scripts/eventcallbacks/player/on_look_in_trade.lua +++ b/data/scripts/eventcallbacks/player/on_look_in_trade.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookInTradeBaseEvent") function callback.playerOnLookInTrade(player, partner, item, distance) player:sendTextMessage(MESSAGE_LOOK, "You see " .. item:getDescription(distance)) diff --git a/data/scripts/eventcallbacks/player/on_remove_count.lua b/data/scripts/eventcallbacks/player/on_remove_count.lua index 4629d0a1b12..f39438c24ba 100644 --- a/data/scripts/eventcallbacks/player/on_remove_count.lua +++ b/data/scripts/eventcallbacks/player/on_remove_count.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRemoveCountBaseEvent") function callback.playerOnRemoveCount(player, item) player:sendWaste(item:getId()) diff --git a/data/scripts/eventcallbacks/player/on_request_quest_line.lua b/data/scripts/eventcallbacks/player/on_request_quest_line.lua index c233279cfb8..ad5737d3246 100644 --- a/data/scripts/eventcallbacks/player/on_request_quest_line.lua +++ b/data/scripts/eventcallbacks/player/on_request_quest_line.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRequestQuestLineBaseEvent") function callback.playerOnRequestQuestLine(player, questId) player:sendQuestLine(questId) diff --git a/data/scripts/eventcallbacks/player/on_request_quest_log.lua b/data/scripts/eventcallbacks/player/on_request_quest_log.lua index b6bcbbc3a64..15cfbd34719 100644 --- a/data/scripts/eventcallbacks/player/on_request_quest_log.lua +++ b/data/scripts/eventcallbacks/player/on_request_quest_log.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRequestQuestLogBaseEvent") function callback.playerOnRequestQuestLog(player) player:sendQuestLog() diff --git a/data/scripts/eventcallbacks/player/on_rotate_item.lua b/data/scripts/eventcallbacks/player/on_rotate_item.lua index 3692fcdc655..61bb2a99b2a 100644 --- a/data/scripts/eventcallbacks/player/on_rotate_item.lua +++ b/data/scripts/eventcallbacks/player/on_rotate_item.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRotateItemBaseEvent") function callback.playerOnRotateItem(player, item, position) if item:getActionId() == IMMOVABLE_ACTION_ID then diff --git a/data/scripts/eventcallbacks/player/on_storage_update.lua b/data/scripts/eventcallbacks/player/on_storage_update.lua index 8b3006b1d54..0f4233e51d2 100644 --- a/data/scripts/eventcallbacks/player/on_storage_update.lua +++ b/data/scripts/eventcallbacks/player/on_storage_update.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnStorageUpdateBaseEvent") function callback.playerOnStorageUpdate(player, key, value, oldValue, currentFrameTime) player:updateStorage(key, value, oldValue, currentFrameTime) diff --git a/data/scripts/eventcallbacks/player/on_trade_accept.lua b/data/scripts/eventcallbacks/player/on_trade_accept.lua index 3d1eba7171b..8425bdc10b3 100644 --- a/data/scripts/eventcallbacks/player/on_trade_accept.lua +++ b/data/scripts/eventcallbacks/player/on_trade_accept.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnTradeAcceptBaseEvent") function callback.playerOnTradeAccept(player, target, item, targetItem) player:closeForge() diff --git a/data/scripts/lib/register_lever_tables.lua b/data/scripts/lib/register_lever_tables.lua index 66a78f42a11..de3cba6649e 100644 --- a/data/scripts/lib/register_lever_tables.lua +++ b/data/scripts/lib/register_lever_tables.lua @@ -7,8 +7,8 @@ AscendingFerumbrasConfig = { centerRoom = Position(33392, 31473, 14), -- Center Room exitPosition = Position(33266, 31479, 14), -- Exit Position newPos = Position(33392, 31479, 14), -- Player Position on room - days = 3, + days = 5, range = 20, time = 60, -- time in minutes to remove the player - vortex = 23726, + vortex = 20121, } diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua index a9cfaee3354..cb101f0126f 100644 --- a/data/scripts/lib/register_monster_type.lua +++ b/data/scripts/lib/register_monster_type.lua @@ -934,8 +934,8 @@ function readSpell(incomingLua, mtype) if incomingLua.effect then spell:setCombatEffect(incomingLua.effect) end - if incomingLua.shootEffect then - spell:setCombatShootEffect(incomingLua.shootEffect) + if incomingLua.shootEffect or incomingLua.shooteffect then + spell:setCombatShootEffect(incomingLua.shootEffect or incomingLua.shooteffect) end end diff --git a/data/scripts/lib/register_spells.lua b/data/scripts/lib/register_spells.lua index 49a5d7aec2f..8c549859640 100644 --- a/data/scripts/lib/register_spells.lua +++ b/data/scripts/lib/register_spells.lua @@ -393,6 +393,12 @@ AREA_RING1_BURST3 = { { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, } +CrossBeamArea3X2 = { + { 1, 1, 1 }, + { 0, 1, 0 }, + { 0, 3, 0 }, +} + -- The numbered-keys represents the damage values, and their table -- contains the minimum and maximum number of rounds of those damage values. RANGE = { diff --git a/data/scripts/spells/healing/heal_malice.lua b/data/scripts/spells/healing/heal_malice.lua new file mode 100644 index 00000000000..67211a58341 --- /dev/null +++ b/data/scripts/spells/healing/heal_malice.lua @@ -0,0 +1,29 @@ +function onTargetCreature(creature, target) + if target:getName() == "Goshnar's Malice" then + logger.debug("Monster {} Healing {}", creature:getName(), target:getName()) + local min = 15000 + local max = 30000 + doTargetCombatHealth(target, target, COMBAT_HEALING, min, max) + end + return true +end + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, 0) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("Heal Malice") +spell:words("#####462") +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data/scripts/talkactions/gm/distance_effect.lua b/data/scripts/talkactions/gm/distance_effect.lua new file mode 100644 index 00000000000..4c637972cb7 --- /dev/null +++ b/data/scripts/talkactions/gm/distance_effect.lua @@ -0,0 +1,37 @@ +local magicEffect = TalkAction("/distanceeffect") + +function magicEffect.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local effect = tonumber(param) + if effect ~= nil and effect > 0 then + local playerPos = player:getPosition() + local direction = player:getDirection() + local targetPos = Position(playerPos.x, playerPos.y, playerPos.z) + + local distance = 7 + if direction == DIRECTION_NORTH then + targetPos.y = targetPos.y - distance + elseif direction == DIRECTION_EAST then + targetPos.x = targetPos.x + distance + elseif direction == DIRECTION_SOUTH then + targetPos.y = targetPos.y + distance + elseif direction == DIRECTION_WEST then + targetPos.x = targetPos.x - distance + end + + player:getPosition():sendDistanceEffect(targetPos, effect) + end + + return true +end + +magicEffect:separator(" ") +magicEffect:groupType("gamemaster") +magicEffect:register() diff --git a/src/creatures/appearance/outfit/outfit.hpp b/src/creatures/appearance/outfit/outfit.hpp index e1a5143c673..d8df003ad0f 100644 --- a/src/creatures/appearance/outfit/outfit.hpp +++ b/src/creatures/appearance/outfit/outfit.hpp @@ -47,6 +47,16 @@ class Outfits { return outfits[sex]; } + std::shared_ptr getOutfitByName(PlayerSex_t sex, const std::string &name) const { + for (const auto &outfit : outfits[sex]) { + if (outfit->name == name) { + return outfit; + } + } + + return nullptr; + } + private: std::vector> outfits[PLAYERSEX_LAST + 1]; }; diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index cf98fcb6a7b..7d6971c0038 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -12,6 +12,8 @@ #include "declarations.hpp" #include "creatures/combat/combat.hpp" #include "lua/creature/events.hpp" +#include "lua/callbacks/event_callback.hpp" +#include "lua/callbacks/events_callbacks.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" @@ -582,10 +584,15 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< targetPlayer = target->getPlayer(); } + g_logger().debug("[{}] (old) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + g_callbacks().executeCallback(EventCallback_t::creatureOnCombat, &EventCallback::creatureOnCombat, caster, target, std::ref(damage)); + g_logger().debug("[{}] (new) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + if (attackerPlayer) { std::shared_ptr item = attackerPlayer->getWeapon(); damage = applyImbuementElementalDamage(attackerPlayer, item, damage); g_events().eventPlayerOnCombat(attackerPlayer, target, item, damage); + g_callbacks().executeCallback(EventCallback_t::playerOnCombat, &EventCallback::playerOnCombat, attackerPlayer, target, item, std::ref(damage)); if (targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { if (damage.primary.type != COMBAT_HEALING) { @@ -611,6 +618,9 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< damage.primary.value += static_cast(std::ceil((damage.primary.value * slot->bonusPercentage) / 100)); damage.secondary.value += static_cast(std::ceil((damage.secondary.value * slot->bonusPercentage) / 100)); } + + // Monster type onPlayerAttack event + targetMonster->onAttackedByPlayer(attackerPlayer); } // Monster attacking player diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 6101ff0473d..ef4b1e5bbc9 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -1793,7 +1793,7 @@ void Creature::handleLostSummon(bool teleportSummons) { g_game().addMagicEffect(getPosition(), CONST_ME_POFF); } -int32_t Creature::getReflectPercent(CombatType_t combatType, bool useCharges /*= false*/) const { +double_t Creature::getReflectPercent(CombatType_t combatType, bool useCharges /*= false*/) const { try { return reflectPercent.at(combatTypeToIndex(combatType)); } catch (const std::out_of_range &e) { diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 4acbacb570a..8ee86323648 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -255,6 +255,10 @@ class Creature : virtual public Thing, public SharedObject { return creatureIcons.at(key); } + bool hasIcon(const std::string &key) const { + return creatureIcons.contains(key); + } + void setIcon(const std::string &key, CreatureIcon icon) { creatureIcons[key] = icon; iconChanged(); @@ -605,7 +609,7 @@ class Creature : virtual public Thing, public SharedObject { * @param useCharges Indicates whether charges should be considered. * @return The reflection percentage for the specified combat type. */ - virtual int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const; + virtual double_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const; /** * @brief Retrieves the flat reflection value for a given combat type. @@ -703,6 +707,10 @@ class Creature : virtual public Thing, public SharedObject { return false; } + virtual bool isDead() const { + return false; + } + static constexpr int32_t mapWalkWidth = MAP_MAX_VIEW_PORT_X * 2 + 1; static constexpr int32_t mapWalkHeight = MAP_MAX_VIEW_PORT_Y * 2 + 1; static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index 46fb5fe5979..f1929a633c1 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -494,12 +494,14 @@ enum BestiaryType_t : uint8_t { }; enum MonstersEvent_t : uint8_t { - MONSTERS_EVENT_NONE = 0, - MONSTERS_EVENT_THINK = 1, - MONSTERS_EVENT_APPEAR = 2, - MONSTERS_EVENT_DISAPPEAR = 3, - MONSTERS_EVENT_MOVE = 4, - MONSTERS_EVENT_SAY = 5, + MONSTERS_EVENT_NONE, + MONSTERS_EVENT_THINK, + MONSTERS_EVENT_APPEAR, + MONSTERS_EVENT_DISAPPEAR, + MONSTERS_EVENT_MOVE, + MONSTERS_EVENT_SAY, + MONSTERS_EVENT_ATTACKED_BY_PLAYER, + MONSTERS_EVENT_ON_SPAWN, }; enum NpcsEvent_t : uint8_t { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index f15616d6af5..9d24b9cc4f3 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -77,15 +77,57 @@ bool Monster::canWalkOnFieldType(CombatType_t combatType) const { } } -int32_t Monster::getReflectPercent(CombatType_t reflectType, bool useCharges) const { - int32_t result = Creature::getReflectPercent(reflectType, useCharges); +double_t Monster::getReflectPercent(CombatType_t reflectType, bool useCharges) const { + // Monster type reflect + auto result = Creature::getReflectPercent(reflectType, useCharges); + if (result != 0) { + g_logger().debug("[{}] before mtype reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } auto it = mType->info.reflectMap.find(reflectType); if (it != mType->info.reflectMap.end()) { result += it->second; } + + if (result != 0) { + g_logger().debug("[{}] after mtype reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } + + // Monster reflect + auto monsterReflectIt = m_reflectElementMap.find(reflectType); + if (monsterReflectIt != m_reflectElementMap.end()) { + result += monsterReflectIt->second; + } + + if (result != 0) { + g_logger().debug("[{}] (final) after monster reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } + return result; } +void Monster::addReflectElement(CombatType_t combatType, int32_t percent) { + g_logger().debug("[{}] added reflect element {}, percent {}", __FUNCTION__, fmt::underlying(combatType), percent); + m_reflectElementMap[combatType] += percent; +} + +int32_t Monster::getDefense() const { + auto mtypeDefense = mType->info.defense; + if (mtypeDefense != 0) { + g_logger().debug("[{}] old defense {}", __FUNCTION__, mtypeDefense); + } + mtypeDefense += m_defense; + if (mtypeDefense != 0) { + g_logger().debug("[{}] new defense {}", __FUNCTION__, mtypeDefense); + } + return mtypeDefense * getDefenseMultiplier(); +} + +void Monster::addDefense(int32_t defense) { + g_logger().debug("[{}] adding defense {}", __FUNCTION__, defense); + m_defense += defense; + g_logger().debug("[{}] new defense {}", __FUNCTION__, m_defense); +} + uint32_t Monster::getHealingCombatValue(CombatType_t healingType) const { auto it = mType->info.healingMap.find(healingType); if (it != mType->info.healingMap.end()) { @@ -284,6 +326,57 @@ void Monster::onCreatureSay(std::shared_ptr creature, SpeakClasses typ } } +void Monster::onAttackedByPlayer(std::shared_ptr attackerPlayer) { + if (mType->info.monsterAttackedByPlayerEvent != -1) { + // onPlayerAttack(self, attackerPlayer) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " + "script calls being nested.", + getName(), this->getName()); + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.monsterAttackedByPlayerEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.monsterAttackedByPlayerEvent); + + LuaScriptInterface::pushUserdata(L, getMonster()); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, attackerPlayer); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + scriptInterface->callVoidFunction(2); + } +} + +void Monster::onSpawn() { + if (mType->info.spawnEvent != -1) { + // onSpawn(self) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " + "script calls being nested.", + getName(), this->getName()); + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.spawnEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.spawnEvent); + + LuaScriptInterface::pushUserdata(L, getMonster()); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + scriptInterface->callVoidFunction(1); + } +} + void Monster::addFriend(const std::shared_ptr &creature) { if (creature == getMonster()) { g_logger().error("[{}]: adding creature is same of monster", __FUNCTION__); @@ -1894,6 +1987,8 @@ void Monster::death(std::shared_ptr) { if (mType) { g_game().sendSingleSoundEffect(static_self_cast()->getPosition(), mType->info.deathSound, getMonster()); } + + setDead(true); } std::shared_ptr Monster::getCorpse(std::shared_ptr lastHitCreature, std::shared_ptr mostDamageCreature) { @@ -2095,11 +2190,11 @@ bool Monster::changeTargetDistance(int32_t distance, uint32_t duration /* = 1200 } bool Monster::isImmune(ConditionType_t conditionType) const { - return mType->info.m_conditionImmunities[static_cast(conditionType)]; + return m_isImmune || mType->info.m_conditionImmunities[static_cast(conditionType)]; } bool Monster::isImmune(CombatType_t combatType) const { - return mType->info.m_damageImmunities[combatTypeToIndex(combatType)]; + return m_isImmune || mType->info.m_damageImmunities[combatTypeToIndex(combatType)]; } void Monster::getPathSearchParams(const std::shared_ptr &creature, FindPathParams &fpp) { diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index edf3874161d..6ba6c583cd6 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -77,9 +77,9 @@ class Monster final : public Creature { int32_t getArmor() const override { return mType->info.armor * getDefenseMultiplier(); } - int32_t getDefense() const override { - return mType->info.defense * getDefenseMultiplier(); - } + int32_t getDefense() const override; + + void addDefense(int32_t defense); Faction_t getFaction() const override { auto master = getMaster(); @@ -134,9 +134,11 @@ class Monster final : public Creature { this->spawnMonster = newSpawnMonster; } - int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const override; + double_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const override; uint32_t getHealingCombatValue(CombatType_t healingType) const; + void addReflectElement(CombatType_t combatType, int32_t percent); + bool canWalkOnFieldType(CombatType_t combatType) const; void onAttackedCreatureDisappear(bool isLogout) override; @@ -144,6 +146,8 @@ class Monster final : public Creature { void onRemoveCreature(std::shared_ptr creature, bool isLogout) override; void onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) override; void onCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text) override; + void onAttackedByPlayer(std::shared_ptr attackerPlayer); + void onSpawn(); void drainHealth(std::shared_ptr attacker, int32_t damage) override; void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; @@ -333,6 +337,12 @@ class Monster final : public Creature { bool isImmune(ConditionType_t conditionType) const override; bool isImmune(CombatType_t combatType) const override; + void setImmune(bool immune) { + m_isImmune = immune; + } + bool isImmune() const { + return m_isImmune; + } float getAttackMultiplier() const { float multiplier = mType->getAttackMultiplier(); @@ -347,6 +357,14 @@ class Monster final : public Creature { return multiplier * std::pow(1.02f, getForgeStack()); } + bool isDead() const override { + return m_isDead; + } + + void setDead(bool isDead) { + m_isDead = isDead; + } + private: auto getTargetIterator(const std::shared_ptr &creature) { return std::ranges::find_if(targetList.begin(), targetList.end(), [id = creature->getID()](const std::weak_ptr &ref) { @@ -371,6 +389,8 @@ class Monster final : public Creature { int64_t lastMeleeAttack = 0; + uint16_t totalPlayersOnScreen = 0; + uint32_t attackTicks = 0; uint32_t targetChangeTicks = 0; uint32_t defenseTicks = 0; @@ -384,8 +404,10 @@ class Monster final : public Creature { int32_t stepDuration = 0; int32_t targetDistance = 1; int32_t challengeMeleeDuration = 0; - uint16_t totalPlayersOnScreen = 0; int32_t runAwayHealth = 0; + int32_t m_defense = 0; + + std::unordered_map m_reflectElementMap; Position masterPos; @@ -401,6 +423,9 @@ class Monster final : public Creature { bool hazardDamageBoost = false; bool hazardDefenseBoost = false; + bool m_isDead = false; + bool m_isImmune = false; + void onCreatureEnter(std::shared_ptr creature); void onCreatureLeave(std::shared_ptr creature); void onCreatureFound(std::shared_ptr creature, bool pushFront = false); diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp index 190c14c76e5..a6d16abdfe0 100644 --- a/src/creatures/monsters/monsters.cpp +++ b/src/creatures/monsters/monsters.cpp @@ -278,17 +278,34 @@ bool MonsterType::loadCallback(LuaScriptInterface* scriptInterface) { } info.scriptInterface = scriptInterface; - if (info.eventType == MONSTERS_EVENT_THINK) { - info.thinkEvent = id; - } else if (info.eventType == MONSTERS_EVENT_APPEAR) { - info.creatureAppearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_DISAPPEAR) { - info.creatureDisappearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_MOVE) { - info.creatureMoveEvent = id; - } else if (info.eventType == MONSTERS_EVENT_SAY) { - info.creatureSayEvent = id; + + switch (info.eventType) { + case MONSTERS_EVENT_THINK: + info.thinkEvent = id; + break; + case MONSTERS_EVENT_APPEAR: + info.creatureAppearEvent = id; + break; + case MONSTERS_EVENT_DISAPPEAR: + info.creatureDisappearEvent = id; + break; + case MONSTERS_EVENT_MOVE: + info.creatureMoveEvent = id; + break; + case MONSTERS_EVENT_SAY: + info.creatureSayEvent = id; + break; + case MONSTERS_EVENT_ATTACKED_BY_PLAYER: + info.monsterAttackedByPlayerEvent = id; + break; + case MONSTERS_EVENT_ON_SPAWN: + info.spawnEvent = id; + break; + default: + g_logger().error("[MonsterType::loadCallback] - Unknown event type"); + return false; } + return true; } diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index 0fa2a3d88db..d7ba5da0774 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -120,7 +120,9 @@ class MonsterType { int32_t creatureDisappearEvent = -1; int32_t creatureMoveEvent = -1; int32_t creatureSayEvent = -1; + int32_t monsterAttackedByPlayerEvent = -1; int32_t thinkEvent = -1; + int32_t spawnEvent = -1; int32_t targetDistance = 1; int32_t runAwayHealth = 0; int32_t health = 100; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 21eab1a7c0b..39fb08327ba 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -193,6 +193,7 @@ bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const spawnedMonsterMap[spawnMonsterId] = monster; sb.lastSpawn = OTSYS_TIME(); g_events().eventMonsterOnSpawn(monster, sb.pos); + monster->onSpawn(); g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, sb.pos); return true; } diff --git a/src/creatures/players/grouping/party.cpp b/src/creatures/players/grouping/party.cpp index 880fc77594f..d3f89f45431 100644 --- a/src/creatures/players/grouping/party.cpp +++ b/src/creatures/players/grouping/party.cpp @@ -452,7 +452,7 @@ void Party::shareExperience(uint64_t experience, std::shared_ptr targe uint64_t shareExperience = experience; g_events().eventPartyOnShareExperience(getParty(), shareExperience); - g_callbacks().executeCallback(EventCallback_t::partyOnShareExperience, &EventCallback::partyOnShareExperience, getParty(), shareExperience); + g_callbacks().executeCallback(EventCallback_t::partyOnShareExperience, &EventCallback::partyOnShareExperience, getParty(), std::ref(shareExperience)); for (auto member : getMembers()) { member->onGainSharedExperience(shareExperience, target); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 75e5b5ef2af..04aa4b7787f 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -674,7 +674,7 @@ void Player::addSkillAdvance(skills_t skill, uint64_t count) { } g_events().eventPlayerOnGainSkillTries(static_self_cast(), skill, count); - g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), skill, count); + g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), std::ref(skill), std::ref(count)); if (count == 0) { return; } @@ -2205,6 +2205,8 @@ void Player::onThink(uint32_t interval) { // Wheel of destiny major spells wheel()->onThink(); + + g_callbacks().executeCallback(EventCallback_t::playerOnThink, &EventCallback::playerOnThink, getPlayer(), interval); } uint32_t Player::isMuted() const { @@ -2336,7 +2338,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool return; } - g_callbacks().executeCallback(EventCallback_t::playerOnGainExperience, &EventCallback::playerOnGainExperience, getPlayer(), target, exp, rawExp); + g_callbacks().executeCallback(EventCallback_t::playerOnGainExperience, &EventCallback::playerOnGainExperience, getPlayer(), target, std::ref(exp), std::ref(rawExp)); g_events().eventPlayerOnGainExperience(static_self_cast(), target, exp, rawExp); if (exp == 0) { @@ -2451,7 +2453,7 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) { } g_events().eventPlayerOnLoseExperience(static_self_cast(), exp); - g_callbacks().executeCallback(EventCallback_t::playerOnLoseExperience, &EventCallback::playerOnLoseExperience, getPlayer(), exp); + g_callbacks().executeCallback(EventCallback_t::playerOnLoseExperience, &EventCallback::playerOnLoseExperience, getPlayer(), std::ref(exp)); if (exp == 0) { return; } @@ -5525,15 +5527,15 @@ int32_t Player::getMagicShieldCapacityPercent(bool useCharges) const { return result; } -int32_t Player::getReflectPercent(CombatType_t combat, bool useCharges) const { - int32_t result = reflectPercent[combatTypeToIndex(combat)]; +double_t Player::getReflectPercent(CombatType_t combat, bool useCharges) const { + double_t result = reflectPercent[combatTypeToIndex(combat)]; for (const auto item : getEquippedItems()) { const ItemType &itemType = Item::items[item->getID()]; if (!itemType.abilities) { continue; } - int32_t reflectPercent = itemType.abilities->reflectPercent[combatTypeToIndex(combat)]; + double_t reflectPercent = itemType.abilities->reflectPercent[combatTypeToIndex(combat)]; if (reflectPercent != 0) { result += reflectPercent; uint16_t charges = item->getCharges(); @@ -5948,7 +5950,7 @@ bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; g_events().eventPlayerOnGainSkillTries(static_self_cast(), SKILL_MAGLEVEL, tries); - g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), SKILL_MAGLEVEL, tries); + g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), SKILL_MAGLEVEL, std::ref(tries)); uint32_t currMagLevel = magLevel; while ((manaSpent + tries) >= nextReqMana) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 592db179071..3fd4ce856c4 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -405,7 +405,7 @@ class Player final : public Creature, public Cylinder, public Bankable { magicShieldCapacityPercent += value; } - int32_t getReflectPercent(CombatType_t combat, bool useCharges = false) const override; + double_t getReflectPercent(CombatType_t combat, bool useCharges = false) const override; int32_t getReflectFlat(CombatType_t combat, bool useCharges = false) const override; @@ -2886,7 +2886,7 @@ class Player final : public Creature, public Cylinder, public Bankable { bool marketMenu = false; // Menu option 'show in market' bool exerciseTraining = false; bool moved = false; - bool dead = false; + bool m_isDead = false; bool imbuementTrackerWindowOpen = false; // Hazard system @@ -2954,10 +2954,10 @@ class Player final : public Creature, public Cylinder, public Bankable { void getPathSearchParams(const std::shared_ptr &creature, FindPathParams &fpp) override; void setDead(bool isDead) { - dead = isDead; + m_isDead = isDead; } - bool isDead() const { - return dead; + bool isDead() const override { + return m_isDead; } void triggerMomentum(); diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index 30345e0495b..bb8a98c6481 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -128,6 +128,10 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) } bool Bank::withdraw(std::shared_ptr player, uint64_t amount) { + if (!player) { + return false; + } + if (!debit(amount)) { return false; } diff --git a/src/game/game.cpp b/src/game/game.cpp index cac0b596ebf..3d713847d07 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1318,6 +1318,10 @@ ReturnValue Game::internalMoveCreature(std::shared_ptr creature, Direc return RETURNVALUE_NOTPOSSIBLE; } + if (creature->getBaseSpeed() == 0 && flags != FLAG_IGNORENOTMOVABLE) { + return RETURNVALUE_NOTMOVABLE; + } + creature->setLastPosition(creature->getPosition()); const Position ¤tPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); @@ -6921,7 +6925,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt if (!isEvent) { g_events().eventCreatureOnDrainHealth(target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color); - g_callbacks().executeCallback(EventCallback_t::creatureOnDrainHealth, &EventCallback::creatureOnDrainHealth, target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color); + g_callbacks().executeCallback(EventCallback_t::creatureOnDrainHealth, &EventCallback::creatureOnDrainHealth, target, attacker, std::ref(damage.primary.type), std::ref(damage.primary.value), std::ref(damage.secondary.type), std::ref(damage.secondary.value), std::ref(message.primary.color), std::ref(message.secondary.color)); } if (damage.origin != ORIGIN_NONE && attacker && damage.primary.type != COMBAT_HEALING) { damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; @@ -7116,7 +7120,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt return true; } else if (realDamage >= targetHealth) { for (const auto creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { - if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { + if (!creatureEvent->executeOnPrepareDeath(target, attacker, std::ref(realDamage))) { return false; } } diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index b6c1191aa78..7ffa4b594f4 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -15,6 +15,7 @@ #include "creatures/npcs/npc.hpp" #include "creatures/players/player.hpp" #include "utils/pugicast.hpp" +#include "kv/kv.hpp" phmap::parallel_flat_hash_map> Zone::zones = {}; phmap::parallel_flat_hash_map> Zone::zonesByID = {}; @@ -122,6 +123,10 @@ std::vector> Zone::getItems() { void Zone::removePlayers() { for (const auto &player : getPlayers()) { g_game().internalTeleport(player, getRemoveDestination(player)); + // Remove icon from player (soul war quest) + if (player->hasIcon("goshnars-hatred-damage")) { + player->removeIcon("goshnars-hatred-damage"); + } } } diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index d4a4f33774d..bb10a8d19ff 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -807,7 +807,12 @@ void IOLoginDataLoad::loadPlayerForgeHistory(std::shared_ptr player, DBR } void IOLoginDataLoad::loadPlayerBosstiary(std::shared_ptr player, DBResult_ptr result) { - if (!result || !player) { + if (!result) { + g_logger().warn("[IOLoginData::loadPlayer] - Result nullptr: {}", __FUNCTION__); + return; + } + + if (!player) { g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__); return; } diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 0090c0b3ebb..4381c5c8a31 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -249,6 +249,7 @@ std::vector IOBosstiary::getBosstiaryFinished(const std::shared_ptrflush(); - g_logger().info("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); + g_logger().debug("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); } void IOMap::parseMapDataAttributes(FileStream &stream, Map* map) { diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index 521a2c4cda7..12a36736e20 100644 --- a/src/lua/callbacks/callbacks_definitions.hpp +++ b/src/lua/callbacks/callbacks_definitions.hpp @@ -25,6 +25,7 @@ enum class EventCallback_t : uint16_t { creatureOnTargetCombat, creatureOnHear, creatureOnDrainHealth, + creatureOnCombat, // Party partyOnJoin, partyOnLeave, @@ -56,6 +57,7 @@ enum class EventCallback_t : uint16_t { playerOnCombat, playerOnInventoryUpdate, playerOnRotateItem, + playerOnThink, // Monster monsterOnDropLoot, monsterPostDropLoot, @@ -67,4 +69,5 @@ enum class EventCallback_t : uint16_t { zoneBeforeCreatureLeave, zoneAfterCreatureEnter, zoneAfterCreatureLeave, + mapOnLoad, }; diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 51c03131fa9..96d824a14d6 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -25,8 +25,16 @@ * * @see Script */ -EventCallback::EventCallback(LuaScriptInterface* scriptInterface) : - Script(scriptInterface) { +EventCallback::EventCallback(LuaScriptInterface* scriptInterface, const std::string &callbackName, bool skipDuplicationCheck) : + Script(scriptInterface), m_callbackName(callbackName), m_skipDuplicationCheck(skipDuplicationCheck) { +} + +std::string EventCallback::getName() const { + return m_callbackName; +} + +bool EventCallback::skipDuplicationCheck() const { + return m_skipDuplicationCheck; } std::string EventCallback::getScriptTypeName() const { @@ -225,6 +233,58 @@ void EventCallback::creatureOnDrainHealth(std::shared_ptr creature, st getScriptInterface()->resetScriptEnv(); } +void EventCallback::creatureOnCombat(std::shared_ptr attacker, std::shared_ptr target, CombatDamage &damage) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{} - " + "Creature {} target {}] " + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__, attacker->getName(), target->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, attacker); + LuaScriptInterface::setCreatureMetatable(L, -1, attacker); + + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + + LuaScriptInterface::pushCombatDamage(L, damage); + + if (getScriptInterface()->protectedCall(L, 7, 4) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else { + damage.primary.value = std::abs(LuaScriptInterface::getNumber(L, -4)); + damage.primary.type = LuaScriptInterface::getNumber(L, -3); + damage.secondary.value = std::abs(LuaScriptInterface::getNumber(L, -2)); + damage.secondary.type = LuaScriptInterface::getNumber(L, -1); + + lua_pop(L, 4); + if (damage.primary.type != COMBAT_HEALING) { + damage.primary.value = -damage.primary.value; + damage.secondary.value = -damage.secondary.value; + } + /* + Only EK with dealing physical damage will get elemental damage on skill + */ + if (damage.origin == ORIGIN_SPELL && attacker) { + const auto &player = attacker->getPlayer(); + if (player && player->getVocationId() != 4 && player->getVocationId() != 8) { + damage.primary.value = damage.primary.value + damage.secondary.value; + damage.secondary.type = COMBAT_NONE; + damage.secondary.value = 0; + } + } + } + + getScriptInterface()->resetScriptEnv(); +} + // Party bool EventCallback::partyOnJoin(std::shared_ptr party, std::shared_ptr player) const { if (!getScriptInterface()->reserveScriptEnv()) { @@ -852,7 +912,7 @@ void EventCallback::playerOnCombat(std::shared_ptr player, std::shared_p if (target) { LuaScriptInterface::pushUserdata(L, target); - LuaScriptInterface::setMetatable(L, -1, "Creature"); + LuaScriptInterface::setCreatureMetatable(L, -1, target); } else { lua_pushnil(L); } @@ -1011,6 +1071,26 @@ void EventCallback::playerOnStorageUpdate(std::shared_ptr player, const getScriptInterface()->callVoidFunction(5); } +void EventCallback::playerOnThink(std::shared_ptr player, uint32_t interval) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{}] player {}. Call stack overflow. Too many lua script calls being nested.", __FUNCTION__, player->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, interval); + + getScriptInterface()->callVoidFunction(2); +} + // Monster void EventCallback::monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const { if (!getScriptInterface()->reserveScriptEnv()) { @@ -1212,3 +1292,22 @@ void EventCallback::zoneAfterCreatureLeave(std::shared_ptr zone, std::shar getScriptInterface()->callVoidFunction(2); } + +void EventCallback::mapOnLoad(const std::string &mapFullPath) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{} - " + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushString(L, mapFullPath); + + getScriptInterface()->callVoidFunction(1); +} diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp index d9dc4a9b110..e5c560ce232 100644 --- a/src/lua/callbacks/event_callback.hpp +++ b/src/lua/callbacks/event_callback.hpp @@ -35,13 +35,27 @@ class EventCallback : public Script { private: EventCallback_t m_callbackType = EventCallback_t::none; ///< The type of the event callback. std::string m_scriptTypeName; ///< The name associated with the script type. + std::string m_callbackName; ///< The name of the callback. + bool m_skipDuplicationCheck = false; ///< Whether the callback is silent error for already registered log error. public: /** * @brief Constructor that initializes the EventCallback with a given script interface. * @param scriptInterface Pointer to the LuaScriptInterface object. */ - explicit EventCallback(LuaScriptInterface* scriptInterface); + explicit EventCallback(LuaScriptInterface* scriptInterface, const std::string &callbackName, bool silentAlreadyRegistered); + + /** + * @brief Retrieves the callback name. + * @return The callback name as a string. + */ + std::string getName() const; + + /** + * @brief Retrieves the skip registration status of the callback. + * @return True if the callback is true for skip duplication check and register again the event, false otherwise. + */ + bool skipDuplicationCheck() const; /** * @brief Retrieves the script type name. @@ -84,6 +98,7 @@ class EventCallback : public Script { ReturnValue creatureOnTargetCombat(std::shared_ptr creature, std::shared_ptr target) const; void creatureOnHear(std::shared_ptr creature, std::shared_ptr speaker, const std::string &words, SpeakClasses type) const; void creatureOnDrainHealth(std::shared_ptr creature, std::shared_ptr attacker, CombatType_t &typePrimary, int32_t &damagePrimary, CombatType_t &typeSecondary, int32_t &damageSecondary, TextColor_t &colorPrimary, TextColor_t &colorSecondary) const; + void creatureOnCombat(std::shared_ptr attacker, std::shared_ptr target, CombatDamage &damage) const; // Party bool partyOnJoin(std::shared_ptr party, std::shared_ptr player) const; @@ -116,6 +131,7 @@ class EventCallback : public Script { void playerOnCombat(std::shared_ptr player, std::shared_ptr target, std::shared_ptr item, CombatDamage &damage) const; void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr item, Slots_t slot, bool equip) const; bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr item, const Position &position) const; + void playerOnThink(std::shared_ptr player, uint32_t interval) const; // Monster void monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const; @@ -131,7 +147,5 @@ class EventCallback : public Script { void zoneAfterCreatureEnter(std::shared_ptr zone, std::shared_ptr creature) const; void zoneAfterCreatureLeave(std::shared_ptr zone, std::shared_ptr creature) const; - /** - * @note here end the lua binder functions } - */ + void mapOnLoad(const std::string &mapFullPath) const; }; diff --git a/src/lua/callbacks/events_callbacks.cpp b/src/lua/callbacks/events_callbacks.cpp index 4a1830f80fb..1ab64957497 100644 --- a/src/lua/callbacks/events_callbacks.cpp +++ b/src/lua/callbacks/events_callbacks.cpp @@ -12,6 +12,8 @@ #include "lua/callbacks/events_callbacks.hpp" #include "lua/callbacks/event_callback.hpp" +#include "game/game.hpp" +#include "lib/di/container.hpp" /** * @class EventsCallbacks @@ -28,22 +30,34 @@ EventsCallbacks &EventsCallbacks::getInstance() { return inject(); } -void EventsCallbacks::addCallback(const std::shared_ptr callback) { - m_callbacks.push_back(callback); +bool EventsCallbacks::isCallbackRegistered(const std::shared_ptr &callback) { + if (g_game().getGameState() == GAME_STATE_STARTUP && !callback->skipDuplicationCheck() && m_callbacks.find(callback->getName()) != m_callbacks.end()) { + return true; + } + + return false; +} + +void EventsCallbacks::addCallback(const std::shared_ptr &callback) { + if (m_callbacks.find(callback->getName()) != m_callbacks.end()) { + return; + } + + m_callbacks[callback->getName()] = callback; } -std::vector> EventsCallbacks::getCallbacks() const { +std::unordered_map> EventsCallbacks::getCallbacks() const { return m_callbacks; } -std::vector> EventsCallbacks::getCallbacksByType(EventCallback_t type) const { - std::vector> eventCallbacks; - for (auto callback : getCallbacks()) { +std::unordered_map> EventsCallbacks::getCallbacksByType(EventCallback_t type) const { + std::unordered_map> eventCallbacks; + for (auto [name, callback] : getCallbacks()) { if (callback->getType() != type) { continue; } - eventCallbacks.push_back(callback); + eventCallbacks[name] = callback; } return eventCallbacks; diff --git a/src/lua/callbacks/events_callbacks.hpp b/src/lua/callbacks/events_callbacks.hpp index 53b119f6445..7ca4679ab4f 100644 --- a/src/lua/callbacks/events_callbacks.hpp +++ b/src/lua/callbacks/events_callbacks.hpp @@ -45,24 +45,35 @@ class EventsCallbacks { */ static EventsCallbacks &getInstance(); + /** + * @brief Checks if an event callback is already registered. + * + * @details Determines if the game state is at startup and if a callback with the same name already exists. + * @details If both conditions are met, logs an error and indicates the callback is already registered. + * + * @param callback Shared pointer to the event callback being checked. + * @return True if the callback already exists during the game startup state, otherwise false. + */ + bool isCallbackRegistered(const std::shared_ptr &callback); + /** * @brief Adds a new event callback to the list. * @param callback Pointer to the EventCallback object to add. */ - void addCallback(const std::shared_ptr callback); + void addCallback(const std::shared_ptr &callback); /** * @brief Gets all registered event callbacks. * @return Vector of pointers to EventCallback objects. */ - std::vector> getCallbacks() const; + std::unordered_map> getCallbacks() const; /** * @brief Gets event callbacks by their type. * @param type The type of callbacks to retrieve. * @return Vector of pointers to EventCallback objects of the specified type. */ - std::vector> getCallbacksByType(EventCallback_t type) const; + std::unordered_map> getCallbacksByType(EventCallback_t type) const; /** * @brief Clears all registered event callbacks. @@ -77,7 +88,7 @@ class EventsCallbacks { */ template void executeCallback(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { std::apply( @@ -101,7 +112,7 @@ class EventsCallbacks { bool checkCallback(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { bool allCallbacksSucceeded = true; - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { bool callbackResult = std::apply( @@ -118,7 +129,7 @@ class EventsCallbacks { private: // Container for storing registered event callbacks. - std::vector> m_callbacks; + std::unordered_map> m_callbacks; }; constexpr auto g_callbacks = EventsCallbacks::getInstance; diff --git a/src/lua/creature/creatureevent.cpp b/src/lua/creature/creatureevent.cpp index e3ac954908f..63b77254cc0 100644 --- a/src/lua/creature/creatureevent.cpp +++ b/src/lua/creature/creatureevent.cpp @@ -233,7 +233,7 @@ bool CreatureEvent::executeOnThink(std::shared_ptr creature, uint32_t return getScriptInterface()->callFunction(2); } -bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer) const { +bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer, int realDamage) const { // onPrepareDeath(creature, killer) if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[CreatureEvent::executeOnPrepareDeath - Creature {} killer {}" @@ -259,7 +259,9 @@ bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, st lua_pushnil(L); } - return getScriptInterface()->callFunction(2); + lua_pushnumber(L, realDamage); + + return getScriptInterface()->callFunction(3); } bool CreatureEvent::executeOnDeath(std::shared_ptr creature, std::shared_ptr corpse, std::shared_ptr killer, std::shared_ptr mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) const { diff --git a/src/lua/creature/creatureevent.hpp b/src/lua/creature/creatureevent.hpp index 8a208343e11..e34bd8eb75e 100644 --- a/src/lua/creature/creatureevent.hpp +++ b/src/lua/creature/creatureevent.hpp @@ -46,7 +46,7 @@ class CreatureEvent final : public Script { bool executeOnLogin(std::shared_ptr player) const; bool executeOnLogout(std::shared_ptr player) const; bool executeOnThink(std::shared_ptr creature, uint32_t interval) const; - bool executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer) const; + bool executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer, int realDamage) const; bool executeOnDeath(std::shared_ptr creature, std::shared_ptr corpse, std::shared_ptr killer, std::shared_ptr mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) const; void executeOnKill(std::shared_ptr creature, std::shared_ptr target, bool lastHit) const; bool executeAdvance(std::shared_ptr player, skills_t, uint32_t, uint32_t) const; diff --git a/src/lua/creature/events.cpp b/src/lua/creature/events.cpp index 2917d128b8a..19c5da2fc5b 100644 --- a/src/lua/creature/events.cpp +++ b/src/lua/creature/events.cpp @@ -1159,7 +1159,7 @@ void Events::eventPlayerOnCombat(std::shared_ptr player, std::shared_ptr if (target) { LuaScriptInterface::pushUserdata(L, target); - LuaScriptInterface::setMetatable(L, -1, "Creature"); + LuaScriptInterface::setCreatureMetatable(L, -1, target); } else { lua_pushnil(L); } diff --git a/src/lua/functions/core/game/bank_functions.cpp b/src/lua/functions/core/game/bank_functions.cpp index f6732b8bf70..cc76bf4c091 100644 --- a/src/lua/functions/core/game/bank_functions.cpp +++ b/src/lua/functions/core/game/bank_functions.cpp @@ -81,6 +81,7 @@ int BankFunctions::luaBankTransferToGuild(lua_State* L) { reportErrorFunc("Source is nullptr"); return 1; } + std::shared_ptr destination = getBank(L, 2, true /* isGuild */); if (destination == nullptr) { reportErrorFunc("Destination is nullptr"); diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index ea418dede66..4b031cdd3fe 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -435,6 +435,7 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { if (g_game().placeCreature(monster, position, extended, force)) { g_events().eventMonsterOnSpawn(monster, position); g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, position); + monster->onSpawn(); const auto &mtype = monster->getMonsterType(); if (mtype && mtype->info.raceid > 0 && mtype->info.bosstiaryRace == BosstiaryRarity_t::RARITY_ARCHFOE) { for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 2e3813e0769..19e7b9219ec 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -1219,11 +1219,9 @@ void LuaEnums::initReloadTypeEnums(lua_State* L) { void LuaEnums::initCreaturesEventEnums(lua_State* L) { // Monsters - registerEnum(L, MONSTERS_EVENT_THINK); - registerEnum(L, MONSTERS_EVENT_APPEAR); - registerEnum(L, MONSTERS_EVENT_DISAPPEAR); - registerEnum(L, MONSTERS_EVENT_MOVE); - registerEnum(L, MONSTERS_EVENT_SAY); + for (auto value : magic_enum::enum_values()) { + registerMagicEnum(L, value); + } // Npcs registerEnum(L, NPCS_EVENT_THINK); diff --git a/src/lua/functions/core/game/zone_functions.cpp b/src/lua/functions/core/game/zone_functions.cpp index 5471d01895a..0f19c94f789 100644 --- a/src/lua/functions/core/game/zone_functions.cpp +++ b/src/lua/functions/core/game/zone_functions.cpp @@ -136,7 +136,7 @@ int ZoneFunctions::luaZoneGetCreatures(lua_State* L) { for (auto creature : creatures) { index++; pushUserdata(L, creature); - setMetatable(L, -1, "Creature"); + setCreatureMetatable(L, -1, creature); lua_rawseti(L, -2, index); } return 1; diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index a633afe9749..29f72078619 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -968,7 +968,7 @@ int CreatureFunctions::luaCreatureMove(lua_State* L) { lua_pushnil(L); return 1; } - lua_pushnumber(L, g_game().internalMoveCreature(creature, direction, FLAG_NOLIMIT)); + lua_pushnumber(L, g_game().internalMoveCreature(creature, direction, FLAG_IGNORENOTMOVABLE)); } else { std::shared_ptr tile = getUserdataShared(L, 2); if (!tile) { diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index 1591053c391..39836e68999 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -60,7 +60,8 @@ int MonsterFunctions::luaMonsterGetType(lua_State* L) { } int MonsterFunctions::luaMonsterSetType(lua_State* L) { - // monster:setType(name or raceid) + // monster:setType(name or raceid, restoreHealth = false) + bool restoreHealth = getBoolean(L, 3, false); std::shared_ptr monster = getUserdataShared(L, 1); if (monster) { std::shared_ptr mType = nullptr; @@ -81,8 +82,14 @@ int MonsterFunctions::luaMonsterSetType(lua_State* L) { monster->defaultOutfit = mType->info.outfit; monster->currentOutfit = mType->info.outfit; monster->skull = mType->info.skull; - monster->health = mType->info.health * mType->getHealthMultiplier(); - monster->healthMax = mType->info.healthMax * mType->getHealthMultiplier(); + if (restoreHealth) { + auto multiplier = mType->getHealthMultiplier(); + monster->health = mType->info.health * multiplier; + monster->healthMax = mType->info.healthMax * multiplier; + } else { + monster->health = monster->getHealth(); + monster->healthMax = monster->getMaxHealth(); + } monster->baseSpeed = mType->getBaseSpeed(); monster->internalLight = mType->info.light; monster->hiddenHealth = mType->info.hiddenHealth; @@ -613,3 +620,76 @@ int MonsterFunctions::luaMonsterHazardDefenseBoost(lua_State* L) { } return 1; } + +int MonsterFunctions::luaMonsterAddReflectElement(lua_State* L) { + // monster:addReflectElement(type, percent) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + CombatType_t element = getNumber(L, 2); + monster->addReflectElement(element, getNumber(L, 3)); + pushBoolean(L, true); + return 1; +} + +int MonsterFunctions::luaMonsterAddDefense(lua_State* L) { + // monster:addDefense(defense) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + monster->addDefense(getNumber(L, 2)); + pushBoolean(L, true); + return 1; +} + +int MonsterFunctions::luaMonsterGetDefense(lua_State* L) { + // monster:getDefense(defense) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + lua_pushnumber(L, monster->getDefense()); + return 1; +} + +int MonsterFunctions::luaMonsterIsDead(lua_State* L) { + // monster:isDead() + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + pushBoolean(L, monster->isDead()); + return 1; +} + +int MonsterFunctions::luaMonsterImmune(lua_State* L) { + // to get: isImmune = monster:immune() + // to set and get: newImmuneBool = monster:immune(newImmuneBool) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + if (lua_gettop(L) > 1) { + monster->setImmune(getBoolean(L, 2)); + } + + pushBoolean(L, monster->isImmune()); + return 1; +} diff --git a/src/lua/functions/creatures/monster/monster_functions.hpp b/src/lua/functions/creatures/monster/monster_functions.hpp index bf2785ae430..11ce05cb79c 100644 --- a/src/lua/functions/creatures/monster/monster_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_functions.hpp @@ -63,6 +63,13 @@ class MonsterFunctions final : LuaScriptInterface { registerMethod(L, "Monster", "hazardDamageBoost", MonsterFunctions::luaMonsterHazardDamageBoost); registerMethod(L, "Monster", "hazardDefenseBoost", MonsterFunctions::luaMonsterHazardDefenseBoost); + registerMethod(L, "Monster", "addReflectElement", MonsterFunctions::luaMonsterAddReflectElement); + registerMethod(L, "Monster", "addDefense", MonsterFunctions::luaMonsterAddDefense); + registerMethod(L, "Monster", "getDefense", MonsterFunctions::luaMonsterGetDefense); + + registerMethod(L, "Monster", "isDead", MonsterFunctions::luaMonsterIsDead); + registerMethod(L, "Monster", "immune", MonsterFunctions::luaMonsterImmune); + CharmFunctions::init(L); LootFunctions::init(L); MonsterSpellFunctions::init(L); @@ -122,6 +129,12 @@ class MonsterFunctions final : LuaScriptInterface { static int luaMonsterHazardDodge(lua_State* L); static int luaMonsterHazardDamageBoost(lua_State* L); static int luaMonsterHazardDefenseBoost(lua_State* L); + static int luaMonsterAddReflectElement(lua_State* L); + static int luaMonsterAddDefense(lua_State* L); + static int luaMonsterGetDefense(lua_State* L); + + static int luaMonsterIsDead(lua_State* L); + static int luaMonsterImmune(lua_State* L); friend class CreatureFunctions; }; diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index b937083fddb..96d7850f35c 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -1023,6 +1023,8 @@ int MonsterTypeFunctions::luaMonsterTypeEventOnCallback(lua_State* L) { // monsterType:onDisappear(callback) // monsterType:onMove(callback) // monsterType:onSay(callback) + // monsterType:onPlayerAttack(callback) + // monsterType:onSpawn(callback) const auto monsterType = getUserdataShared(L, 1); if (monsterType) { if (monsterType->loadCallback(&g_scripts().getScriptInterface())) { @@ -1570,7 +1572,7 @@ int MonsterTypeFunctions::luaMonsterTypeBossRaceId(lua_State* L) { } else { auto raceId = getNumber(L, 2, 0); monsterType->info.raceid = raceId; - g_ioBosstiary().addBosstiaryMonster(raceId, monsterType->name); + g_ioBosstiary().addBosstiaryMonster(raceId, monsterType->typeName); pushBoolean(L, true); } diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index 9a72f53b145..e4bf82053b5 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -96,6 +96,8 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "onDisappear", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "onMove", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "onSay", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); + registerMethod(L, "MonsterType", "onPlayerAttack", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); + registerMethod(L, "MonsterType", "onSpawn", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "getSummonList", MonsterTypeFunctions::luaMonsterTypeGetSummonList); registerMethod(L, "MonsterType", "addSummon", MonsterTypeFunctions::luaMonsterTypeAddSummon); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 3ba2c43841d..a6204283fbb 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -2261,10 +2261,23 @@ int PlayerFunctions::luaPlayerGetParty(lua_State* L) { } int PlayerFunctions::luaPlayerAddOutfit(lua_State* L) { - // player:addOutfit(lookType) + // player:addOutfit(lookType or name, addon = 0) std::shared_ptr player = getUserdataShared(L, 1); if (player) { - player->addOutfit(getNumber(L, 2), 0); + auto addon = getNumber(L, 3, 0); + if (lua_isnumber(L, 2)) { + player->addOutfit(getNumber(L, 2), addon); + } else if (lua_isstring(L, 2)) { + const std::string &outfitName = getString(L, 2); + const auto &outfit = Outfits::getInstance().getOutfitByName(player->getSex(), outfitName); + if (!outfit) { + reportErrorFunc("Outfit not found"); + return 1; + } + + player->addOutfit(outfit->lookType, addon); + } + pushBoolean(L, true); } else { lua_pushnil(L); @@ -4279,3 +4292,16 @@ int PlayerFunctions::luaPlayerRemoveAchievementPoints(lua_State* L) { pushBoolean(L, true); return 1; } + +int PlayerFunctions::luaPlayerSendCreatureAppear(lua_State* L) { + auto player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + bool isLogin = getBoolean(L, 2, false); + player->sendCreatureAppear(player, player->getPosition(), isLogin); + pushBoolean(L, true); + return 1; +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index fafc99ff955..996bfd76794 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -364,6 +364,8 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addAchievementPoints", PlayerFunctions::luaPlayerAddAchievementPoints); registerMethod(L, "Player", "removeAchievementPoints", PlayerFunctions::luaPlayerRemoveAchievementPoints); + registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); + GroupFunctions::init(L); GuildFunctions::init(L); MountFunctions::init(L); @@ -718,5 +720,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerAddAchievementPoints(lua_State* L); static int luaPlayerRemoveAchievementPoints(lua_State* L); + static int luaPlayerSendCreatureAppear(lua_State* L); + friend class CreatureFunctions; }; diff --git a/src/lua/functions/events/event_callback_functions.cpp b/src/lua/functions/events/event_callback_functions.cpp index 54457f4367a..ef1c348c113 100644 --- a/src/lua/functions/events/event_callback_functions.cpp +++ b/src/lua/functions/events/event_callback_functions.cpp @@ -34,7 +34,14 @@ void EventCallbackFunctions::init(lua_State* luaState) { } int EventCallbackFunctions::luaEventCallbackCreate(lua_State* luaState) { - const auto eventCallback = std::make_shared(getScriptEnv()->getScriptInterface()); + const auto &callbackName = getString(luaState, 2); + if (callbackName.empty()) { + reportErrorFunc("Invalid callback name"); + return 1; + } + + bool skipDuplicationCheck = getBoolean(luaState, 3, false); + const auto eventCallback = std::make_shared(getScriptEnv()->getScriptInterface(), callbackName, skipDuplicationCheck); pushUserdata(luaState, eventCallback); setMetatable(luaState, -1, "EventCallback"); return 1; @@ -82,6 +89,11 @@ int EventCallbackFunctions::luaEventCallbackRegister(lua_State* luaState) { return 0; } + if (g_callbacks().isCallbackRegistered(callback)) { + reportErrorFunc(fmt::format("EventCallback is duplicated for event with name: {}", callback->getName())); + return 0; + } + g_callbacks().addCallback(callback); pushBoolean(luaState, true); return 1; diff --git a/src/map/map.cpp b/src/map/map.cpp index 395e6d7d2be..55bb0fc6548 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -12,6 +12,8 @@ #include "map.hpp" #include "utils/astarnodes.hpp" +#include "lua/callbacks/event_callback.hpp" +#include "lua/callbacks/events_callbacks.hpp" #include "creatures/monsters/monster.hpp" #include "game/game.hpp" #include "game/zones/zone.hpp" @@ -97,6 +99,10 @@ void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool housefile.clear(); npcfile.clear(); } + + if (!mainMap) { + g_callbacks().executeCallback(EventCallback_t::mapOnLoad, &EventCallback::mapOnLoad, path.string()); + } } void Map::loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, bool loadZones, int customMapIndex) { @@ -192,8 +198,7 @@ std::shared_ptr Map::getTile(uint16_t x, uint16_t y, uint8_t z) { return nullptr; } - const auto tile = floor->getTile(x, y); - return tile ? tile : getOrCreateTileFromCache(floor, x, y); + return getOrCreateTileFromCache(floor, x, y); } void Map::refreshZones(uint16_t x, uint16_t y, uint8_t z) { diff --git a/src/map/mapcache.cpp b/src/map/mapcache.cpp index ede4d3fd862..32cc153318c 100644 --- a/src/map/mapcache.cpp +++ b/src/map/mapcache.cpp @@ -103,8 +103,9 @@ std::shared_ptr MapCache::createItem(const std::shared_ptr &Bas std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptr &floor, uint16_t x, uint16_t y) { const auto cachedTile = floor->getTileCache(x, y); + const auto oldTile = floor->getTile(x, y); if (!cachedTile) { - return floor->getTile(x, y); + return oldTile; } std::unique_lock l(floor->getMutex()); @@ -113,6 +114,15 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptr(this); + std::vector> oldCreatureList; + if (oldTile) { + if (CreatureVector* creatures = oldTile->getCreatures()) { + for (const auto &creature : *creatures) { + oldCreatureList.emplace_back(creature); + } + } + } + std::shared_ptr tile = nullptr; if (cachedTile->isHouse()) { const auto house = map->houses.getHouse(cachedTile->houseId); @@ -126,6 +136,10 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptrinternalAddThing(creature); + } + if (cachedTile->ground != nullptr) { tile->internalAddThing(createItem(cachedTile->ground, pos)); } From 1a07c704f9989cf7682bd3eb8a303866cb077905 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 20:47:19 -0300 Subject: [PATCH 02/60] fix: goshnar's malice damage white tiles and add zones --- .../quests/soul_war/goshnars_malice.lua | 8 -- .../scripts/lib/quests/soul-war.lua | 98 ++++++++++++++----- .../eventcallback_on_combat_taint.lua | 3 +- .../soul_war/moveevent-soul_war_entrances.lua | 17 ++-- .../quests/soul_war/soul_war_mechanics.lua | 3 +- .../world/otservbr-monster.xml | 12 +++ src/creatures/combat/combat.cpp | 4 +- src/creatures/monsters/monster.cpp | 8 +- 8 files changed, 101 insertions(+), 52 deletions(-) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index 2d71ff568b2..e0b054dc10d 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -68,14 +68,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "malicious soul", chance = 30, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/scripts/lib/quests/soul-war.lua index ba44d49943f..e4fcfe4d4bb 100644 --- a/data-otservbr-global/scripts/lib/quests/soul-war.lua +++ b/data-otservbr-global/scripts/lib/quests/soul-war.lua @@ -67,6 +67,14 @@ SoulWarQuest = { "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" }, @@ -109,6 +117,28 @@ SoulWarQuest = { { from = Position(33856, 31884, 5), to = Position(33857, 31865, 6), access = "third-floor-access", count = 70 }, }, + 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", + }, + + caustrophobicInferno = 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 }, @@ -652,6 +682,17 @@ SoulWarQuest = { -- Initialize ebb and flow zone area SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 33964, y = 31147, z = 9 }) +-- Initialize bosses access for taint check +SoulWarQuest.areaZones.caustrophobicInferno: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 }) + SoulCagePosition = Position(33709, 31596, 14) TaintDurationSeconds = 14 * 24 * 60 * 60 -- 14 days GreedbeastKills = 0 @@ -674,14 +715,6 @@ local soulWarTaints = { "taints-loss", -- Taint 5 } -SoulWarBosses = { - ["Goshnar's Malice"] = true, - ["Goshnar's Hatred"] = true, - ["Goshnar's Spite"] = true, - ["Goshnar's Cruelty"] = true, - ["Goshnar's Greed"] = true, -} - GreedMonsters = { ["Greedbeast"] = Position(33744, 31666, 14), ["Soulsnatcher"] = Position(33747, 31668, 14), @@ -707,15 +740,6 @@ function CreateGoshnarsGreedMonster(name, position) addEvent(spawnMonster, 10000) end -local soulWarSpawnMonsters = { - ["soulwars.claustrophobic-inferno"] = "Brachiodemon", - ["soulwars.mirrored-nightmare"] = "Many Faces", - ["soulwars.ebb-and-flow"] = "Bony Sea Devil", - ["soulwars.furious-crater"] = "Cloak of Terror", - ["soulwars.rotten-wasteland"] = "Branchy Crawler", - ["boss-rooms"] = "Dreadful Harvester", -} - function RemoveSoulCageAndBuffMalice() local tile = Tile(SoulCagePosition) local creatures = tile:getCreatures() or {} @@ -832,18 +856,41 @@ 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() and tile:getGround():getId() ~= 409 then - local creature = tile:getTopCreature() - if creature then - local player = creature:getPlayer() - if player then - pos:sendMagicEffect(CONST_ME_REDSMOKE) - player:addHealth(-8000, COMBAT_DEATHDAMAGE) + 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 @@ -1010,7 +1057,6 @@ function Monster:tryTeleportToPlayer(sayMessage) local spectators = Game.getSpectators(self:getPosition(), false, false, range, range, range, range) local maxDistance = 0 local farthestPlayer = nil - logger.debug("Checking teleport monster for monster {}", self:getName()) for i, spectator in ipairs(spectators) do if spectator:isPlayer() then local player = spectator:getPlayer() @@ -1200,7 +1246,7 @@ end function Player:getSoulWarZoneMonster() local zoneMonsterName = nil - for zoneName, monsterName in pairs(soulWarSpawnMonsters) do + for zoneName, monsterName in pairs(SoulWarQuest.areaZones.monsters) do local zone = Zone.getByName(zoneName) if zone and zone:isInZone(self:getPosition()) then zoneMonsterName = monsterName diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua index 01637f1aff8..3e5ec34f930 100644 --- a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua @@ -110,14 +110,13 @@ function callback.playerOnThink(player, interval) accumulatedTime[playerId] = accumulatedTime[playerId] + interval if accumulatedTime[playerId] >= 10000 then - logger.debug("Checking soul war fifth taint, interval {}", interval) 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("Removing '{}' mana and '{}' health from player {}", manaLoss, hpLoss, player:getName()) + logger.debug("Fifth taint removing '{}' mana and '{}' health from player {}", manaLoss, hpLoss, player:getName()) end accumulatedTime[playerId] = 0 diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua index e1728e3a66d..ccf56ea6a3b 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua @@ -5,8 +5,6 @@ local positionsTable = { [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 - - [Position(34022, 31091, 11)] = Position(33685, 31599, 14), -- goshnar's malice entrance } local soul_war_entrances = MoveEvent() @@ -89,22 +87,23 @@ end soul_war_megalomania_entrance:position({ x = 33611, y = 31430, z = 10 }) soul_war_megalomania_entrance:register() -local areasConfig = { +local claustrophobicInfernoTeleportPositions = { [Position(34013, 31049, 9)] = Position(34014, 31058, 9), [Position(34010, 31073, 10)] = Position(34012, 31063, 10), [Position(34009, 31038, 11)] = Position(34012, 31047, 11), + [Position(34022, 31091, 11)] = Position(33685, 31599, 14), } -local soul_war_areas_timer = MoveEvent() +local claustrophobicInfernoTeleports = MoveEvent() -function soul_war_areas_timer.onStepIn(creature, item, position, fromPosition) +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(areasConfig) do + for tablePosition, toPosition in pairs(claustrophobicInfernoTeleportPositions) do if tablePosition == position then player:teleportTo(toPosition) toPosition:sendMagicEffect(CONST_ME_TELEPORT) @@ -115,11 +114,11 @@ function soul_war_areas_timer.onStepIn(creature, item, position, fromPosition) return true end -for key, value in pairs(areasConfig) do - soul_war_areas_timer:position(key) +for key, value in pairs(claustrophobicInfernoTeleportPositions) do + claustrophobicInfernoTeleports:position(key) end -soul_war_areas_timer:register() +claustrophobicInfernoTeleports:register() local goshnarSpiteEntrance = MoveEvent() diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 8daa70f3060..2bbb37b0a6a 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -117,9 +117,10 @@ local bossesDeath = CreatureEvent("SoulWarBossesDeath") function bossesDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) local bossName = creature:getName() - if SoulWarBosses[bossName] then + 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 diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 84419e700e7..0a876191095 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -162049,6 +162049,9 @@ + + + @@ -162078,6 +162081,12 @@ + + + + + + @@ -162143,6 +162152,9 @@ + + + diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 7d6971c0038..244128435f8 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -584,9 +584,9 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< targetPlayer = target->getPlayer(); } - g_logger().debug("[{}] (old) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + g_logger().trace("[{}] (old) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); g_callbacks().executeCallback(EventCallback_t::creatureOnCombat, &EventCallback::creatureOnCombat, caster, target, std::ref(damage)); - g_logger().debug("[{}] (new) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + g_logger().trace("[{}] (new) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); if (attackerPlayer) { std::shared_ptr item = attackerPlayer->getWeapon(); diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 9d24b9cc4f3..2bf7edd75ac 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -113,19 +113,19 @@ void Monster::addReflectElement(CombatType_t combatType, int32_t percent) { int32_t Monster::getDefense() const { auto mtypeDefense = mType->info.defense; if (mtypeDefense != 0) { - g_logger().debug("[{}] old defense {}", __FUNCTION__, mtypeDefense); + g_logger().trace("[{}] old defense {}", __FUNCTION__, mtypeDefense); } mtypeDefense += m_defense; if (mtypeDefense != 0) { - g_logger().debug("[{}] new defense {}", __FUNCTION__, mtypeDefense); + g_logger().trace("[{}] new defense {}", __FUNCTION__, mtypeDefense); } return mtypeDefense * getDefenseMultiplier(); } void Monster::addDefense(int32_t defense) { - g_logger().debug("[{}] adding defense {}", __FUNCTION__, defense); + g_logger().trace("[{}] adding defense {}", __FUNCTION__, defense); m_defense += defense; - g_logger().debug("[{}] new defense {}", __FUNCTION__, m_defense); + g_logger().trace("[{}] new defense {}", __FUNCTION__, m_defense); } uint32_t Monster::getHealingCombatValue(CombatType_t healingType) const { From 715f127488dd74732617d37f10e9ba09d62f7310 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 22:27:31 -0300 Subject: [PATCH 03/60] fix: ebb and flow fixed map --- .../scripts/lib/quests/soul-war.lua | 5 +- .../globalevent-ebb_and_flow_change_maps.lua | 3 + .../soul_war/ebb_and_flow/ebb-flow-house.xml | 2 + .../ebb_and_flow/ebb-flow-monster.xml | 541 ++++++++++++++++++ .../soul_war/ebb_and_flow/ebb-flow-npc.xml | 2 + .../soul_war/ebb_and_flow/ebb-flow-zones.xml | 2 + .../quest/soul_war/ebb_and_flow/ebb-flow.otbm | Bin 0 -> 181554 bytes 7 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/scripts/lib/quests/soul-war.lua index e4fcfe4d4bb..167d33eddc8 100644 --- a/data-otservbr-global/scripts/lib/quests/soul-war.lua +++ b/data-otservbr-global/scripts/lib/quests/soul-war.lua @@ -344,6 +344,7 @@ SoulWarQuest = { 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 @@ -1060,12 +1061,12 @@ function Monster:tryTeleportToPlayer(sayMessage) for i, spectator in ipairs(spectators) do if spectator:isPlayer() then local player = spectator:getPlayer() - if player:getTaintNameByNumber(1) ~= nil then + if player:getTaintNameByNumber(1) then local distance = self:getPosition():getDistance(player:getPosition()) if distance > maxDistance then maxDistance = distance farthestPlayer = player - logger.debug("Found player {} to teleport", player:getName()) + logger.trace("Found player {} to teleport", player:getName()) end end end diff --git a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua index 7bc644448b3..7d2a64ef19c 100644 --- a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua +++ b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua @@ -108,7 +108,9 @@ local function loadMapInundate() end local loadEmptyMap = GlobalEvent("SoulWarQuest.ebbAndFlow") + function loadEmptyMap.onStartup() + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.ebbFlow) loadMapEmpty() SoulWarQuest.ebbAndFlow.updateZonePlayers() end @@ -116,6 +118,7 @@ end loadEmptyMap:register() local eddAndFlowInundate = GlobalEvent("eddAndFlowInundate") + function eddAndFlowInundate.onThink(interval, lastExecution) if SoulWarQuest.ebbAndFlow.isLoadedEmptyMap() then logger.debug("Map change to empty in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml new file mode 100644 index 00000000000..e5a6b86118e --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml new file mode 100644 index 00000000000..a476474dd74 --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml new file mode 100644 index 00000000000..af7984e2faf --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml new file mode 100644 index 00000000000..a9224bd3c2d --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm new file mode 100644 index 0000000000000000000000000000000000000000..7fbac6bb391d7259a4e6c818564a58b83570a320 GIT binary patch literal 181554 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 literal 0 HcmV?d00001 From 535503bb419741d624c56b79ae3236ad50d32db6 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 22:48:04 -0300 Subject: [PATCH 04/60] fix: simplify some functions, logs to trace and map fixes --- .../scripts/lib/quests/soul-war.lua | 81 ++++++------------ .../quests/soul_war/soul_war_mechanics.lua | 43 +++------- .../ebb_and_flow/ebb-flow-empty-house.xml | 2 + .../ebb_and_flow/ebb-flow-empty-monster.xml | 2 + .../ebb_and_flow/ebb-flow-empty-npc.xml | 2 + .../soul_war/ebb_and_flow/ebb-flow-empty.otbm | Bin 129535 -> 129560 bytes .../ebb_and_flow/ebb-flow-inundate-house.xml | 2 + .../ebb-flow-inundate-monster.xml | 2 + .../ebb_and_flow/ebb-flow-inundate-npc.xml | 2 + .../ebb_and_flow/ebb-flow-inundate.otbm | Bin 323339 -> 322942 bytes .../quest/soul_war/ebb_and_flow/ebb-flow.otbm | Bin 181554 -> 181546 bytes .../monsters/spawns/spawn_monster.cpp | 2 +- 12 files changed, 49 insertions(+), 89 deletions(-) create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/scripts/lib/quests/soul-war.lua index 167d33eddc8..4a8a47ee131 100644 --- a/data-otservbr-global/scripts/lib/quests/soul-war.lua +++ b/data-otservbr-global/scripts/lib/quests/soul-war.lua @@ -242,7 +242,7 @@ SoulWarQuest = { timeToFightAgain = 20 * 60 * 60, -- 20 hours onUseExtra = function(player) SoulWarQuest.kvBurning:set("time", 180) - logger.debug("Goshnar's Hatred burning change form time set to: {}", 180) + logger.trace("Goshnar's Hatred burning change form time set to: {}", 180) player:resetGoshnarSymbolTormentCounter() end, }, @@ -378,7 +378,7 @@ SoulWarQuest = { SoulWarQuest.ebbAndFlow.reloadZone() local players = SoulWarQuest.ebbAndFlow.getZone():getPlayers() for _, player in ipairs(players) do - logger.debug("Updating player: {}", player:getName()) + logger.trace("Updating player: {}", player:getName()) player:sendCreatureAppear() end end @@ -731,7 +731,7 @@ function CreateGoshnarsGreedMonster(name, position) local function spawnMonster() Game.createMonster(name, position, true, false) - logger.debug("Spawning {} in position {}", name, position:toString()) + logger.trace("Spawning {} in position {}", name, position:toString()) end for i = 7, 9 do @@ -742,39 +742,13 @@ function CreateGoshnarsGreedMonster(name, position) end function RemoveSoulCageAndBuffMalice() - local tile = Tile(SoulCagePosition) - local creatures = tile:getCreatures() or {} - local soulCage - for i, creature in ipairs(creatures) do - if creature:getName() == "Soul Cage" then - soulCage = creature - logger.debug("Removing Soul Cage, the players not be able to kill him") - break - end - end - - local rangeX = 20 - local rangeY = 20 - local spectators = Game.getSpectators(Position(33709, 31599, 14), false, false, rangeX, rangeX, rangeY, rangeY) + local soulCage = Creature("Soul Cage") if soulCage then - local malice - for i = 1, #spectators do - logger.debug("Specs found {}", i) - if spectators[i]:isMonster() then - logger.debug("Malice Spectators {}", spectators[i]:getName()) - if spectators[i]:getName() == "Goshnar's Malice" then - logger.debug("Found malice") - malice = Monster(spectators[i]) - break - end - end - end - soulCage:remove() addEvent(SpawnSoulCage, 23000) - + local malice = Creature("Goshnar's Malice") if malice then - logger.debug("Found malice, try adding reflect and defense") + logger.trace("Found malice, try adding reflect and defense") for elementType, reflectPercent in pairs(SoulWarReflectDamageMap) do malice:addReflectElement(elementType, reflectPercent) end @@ -786,17 +760,10 @@ end function SpawnSoulCage() local tile = Tile(SoulCagePosition) local creatures = tile:getCreatures() or {} - local soulCage - for i, creature in ipairs(creatures) do - if creature:getName() == "Soul Cage" then - soulCage = true - break - end - end - + local soulCage = Creature("Soul Cage") if not soulCage then Game.createMonster("Soul Cage", SoulCagePosition, true, true) - logger.debug("Spawning Soul Cage in position {}", SoulCagePosition:toString()) + logger.trace("Spawning Soul Cage in position {}", SoulCagePosition:toString()) addEvent(RemoveSoulCageAndBuffMalice, 40000) end end @@ -966,7 +933,7 @@ function Monster:generateBagYouDesireLoot(player) -- Calculates the chances based on the number of taints local totalChance = SoulWarQuest.baseBagYouDesireChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) - logger.debug("Player {} killed {} and has {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) + logger.trace("Player {} killed {} and has {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) -- Generate loot local loot = {} if math.random(1, 100) <= totalChance then @@ -1075,14 +1042,14 @@ function Monster:tryTeleportToPlayer(sayMessage) if farthestPlayer and math.random(100) <= 10 then local playerPosition = farthestPlayer:getPosition() if TaintTeleportCooldown[farthestPlayer:getId()] then - logger.debug("Cooldown is active to player {}", farthestPlayer:getName()) + logger.trace("Cooldown is active to player {}", farthestPlayer:getName()) return end if not TaintTeleportCooldown[farthestPlayer:getId()] then TaintTeleportCooldown[farthestPlayer:getId()] = true - logger.debug("Scheduling player {} to teleport", farthestPlayer:getName()) + logger.trace("Scheduling player {} to teleport", farthestPlayer:getName()) self:getPosition():sendMagicEffect(CONST_ME_MORTAREA) farthestPlayer:getPosition():sendMagicEffect(CONST_ME_MORTAREA) addEvent(function(playerId, monsterId) @@ -1105,7 +1072,7 @@ function Monster:tryTeleportToPlayer(sayMessage) return end - logger.debug("Cleaning player cooldown") + logger.trace("Cleaning player cooldown") TaintTeleportCooldown[playerEvent:getId()] = nil end, 10000, farthestPlayer:getId()) end @@ -1138,7 +1105,7 @@ function Position:increaseNecromaticMegalomaniaStrength() if boss then boss:increaseHatredDamageMultiplier(5) item:remove() - logger.debug("Necromantic remains strength increased") + logger.trace("Necromantic remains strength increased") end end end @@ -1170,14 +1137,14 @@ function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetwee end lastExecutionTime = interval - logger.debug("Icon time count {}", 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() if tormentCounter <= maxLimit then player:increaseGoshnarSymbolTormentCounter(maxLimit) - logger.debug("Player {} has {} damage counter", player:getName(), tormentCounter) + logger.trace("Player {} has {} damage counter", player:getName(), tormentCounter) if tormentCounter > 0 then local damage = tormentCounter * 35 @@ -1185,7 +1152,7 @@ function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetwee damage = damageTable[tormentCounter - 23] end - logger.debug("Final damage {}", damage) + logger.trace("Final damage {}", damage) player:addHealth(-damage, COMBAT_DEATHDAMAGE) player:getPosition():sendMagicEffect(CONST_ME_PINK_ENERGY_SPARK) end @@ -1209,19 +1176,19 @@ function Monster:increaseAspectOfPowerDeathCount() local bossKV = self:getSoulWarKV() local aspectDeathCount = bossKV:get("aspect-of-power-death-count") or 0 local newCount = aspectDeathCount + 1 - logger.debug("Aspect of Power death count {}", newCount) + 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) - logger.debug("Aspect of Power defeated all and Megalomania is now vulnerable, reseting death count.") + 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.debug("Megalomania is now immune again") + logger.trace("Megalomania is now immune again") end end, SoulWarQuest.timeToReturnImmuneMegalomania * 1000) end @@ -1233,15 +1200,15 @@ function Monster:goshnarsDefenseIncrease(kvName) 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 - logger.debug("{} old defense {}", self:getName(), self:getDefense()) + logger.trace("{} old defense {}", self:getName(), self:getDefense()) self:addDefense(SoulWarQuest.goshnarsCrueltyDefenseChange) - logger.debug("{} new defense {}", self:getName(), self:getDefense()) + logger.trace("{} new defense {}", self:getName(), self:getDefense()) --- 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.debug("{} skips increase cooldown due to recent item use.", self:getName()) + logger.trace("{} skips increase cooldown due to recent item use.", self:getName()) end end @@ -1267,11 +1234,11 @@ function Player:isInBoatSpot() groundId = tile:getGround():getId() end if zone and zone:isInZone(self:getPosition()) and tile and groundId == SoulWarQuest.ebbAndFlow.boatId then - logger.debug("Player {} is in boat spot", self:getName()) + logger.trace("Player {} is in boat spot", self:getName()) return true end - logger.debug("Player {} is not in boat spot", self:getName()) + logger.trace("Player {} is not in boat spot", self:getName()) return false end diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 2bbb37b0a6a..8c0c802aa33 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -358,23 +358,14 @@ function weepingSoulCorpse.onStepIn(creature, item, position, fromPosition) return end - local zone = Zone.getByName("boss.goshnar's-spite") - if not zone then - logger.error("Goshnar's Spite zone not found. Check the lever boss register.") - return - end - - local monsters = zone:getMonsters() - for _, monster in ipairs(monsters) do - if monster:getName() == "Goshnar's Spite" 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 - break + 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 @@ -392,19 +383,10 @@ local function removeSearingFire(position) if tile then local fire = tile:getItemById(SoulWarQuest.searingFireId) if fire then - local zone = Zone.getByName("boss.goshnar's-spite") - if not zone then - logger.error("Goshnar's Spite zone not found. Check the lever boss register.") - return - end - - local monsters = zone:getMonsters() - for _, monster in ipairs(monsters) do - if monster:getName() == "Goshnar's Spite" then - monster:addDefense(SoulWarQuest.goshnarsSpiteIncreaseDefense) - logger.debug("Found Goshnar's Spite on boss zone, adding defense.") - break - end + 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 @@ -583,7 +565,6 @@ local burningHatredMonsters = { local goshnarsHatredSorrow = Action() function goshnarsHatredSorrow.onUse(player, item, fromPosition, target, toPosition, isHotkey) - logger.debug("Player {} used the item {} on target {}.", player:getName(), item:getId(), target:getName()) if not target then return end diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml new file mode 100644 index 00000000000..e5a6b86118e --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml new file mode 100644 index 00000000000..8704e6da3b2 --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml new file mode 100644 index 00000000000..af7984e2faf --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm index 7892efac0906d82faaa89ea3905180de70e1abcc..6d37ae4fb880709f5c9facf23687f08eb776fb95 100644 GIT binary patch delta 38 wcmV+>0NMZl^9Pvp2awJdL;ynv{sp7`{{9A?2MC0OEqMt4|Nnu@waWoC`EryKjQ{`u delta 15 XcmbR7h5i3$_6g@2pKpE6X!sKVN=XSC diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml new file mode 100644 index 00000000000..e5a6b86118e --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml new file mode 100644 index 00000000000..8704e6da3b2 --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml new file mode 100644 index 00000000000..af7984e2faf --- /dev/null +++ b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml @@ -0,0 +1,2 @@ + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate.otbm index 98992c326dd4532fb48d78bcc3c2c083cff1266e..01f462c2f7a821fca0e31dab752b4087eb2b84e3 100644 GIT binary patch delta 907 zcmYk4O(;ZB6vu~mFj9t$qQ=*T5{g0+7KRzDl%1v0SWLpgD80np|2ETMafRWNSm&f>^7SXiyA7lx-wWpMYSsG zpTd61pg|07_CggOJWLQJcp%8?5)NzYf0U=EK(I~lM&Q(i!@N4^BJDGRSE`I63=pgk+##^+4LHx{Y0oiC%iTlhAeca4JA2@h zxLw3Bx0gYaR4Fz%IY<9ia^;ir)?$GQ?cxYp(U+~hZ1v@T-yH{;M_$IkV*)EY28R<0mr delta 1308 zcmZXTJ1j$C7=~NtBVAmAL8J)=mq-Z)mjRoigGj_mB9cmKa7zr$iD-L-hNOo63sINN zAe!=rh}A}2V(37`V31fOq)ST&=YKzEeBOTF^S|dmr6S*!=#xdqOFE@aKo@{7<@y1)KEJVsP8A2 zDlp0@#3;e&6-ky3(4t*w)(25~!RVb)r3WVmkmRC=9-T@$?8VU~Mn{aEkYwQ$^^(#I zO@l`2)3l-0=Uzqby8GDW<`Oq1qH%^|?6xfn23yFAw;4Lse)VE|mNv~2vTcssB_;}g z06K|7MyUX`s>z}tPIe*O@DV#1`546*Js?TPK3x^5rgz79zCDiA!e|UhZp6u}DNXtW7G=XJb!k2D zfAp{72a@}U{A-pZ%n}hkqaJkV7Kd(e=>Hs_XQZo=^XKF?NnV~)aXB6k39$mdB_oC< jSz(FzzNTk%L@kb}#S!I>Xp+{aa=YzAK zwc97NechV{vm4`jO1GFT0sSSnm{S3SFt;Cs0dO9-lA-})8@Kqq0b47#R_OuQFSjun G0s*XBbuFy` delta 120 zcmZ40#l5MEd&0Jj=TEgTvrJ6gJhN5YfHhkJ#N4#?wmMj>_wH;~Fhk>Ul^2MyeUUEX wUsW(`elTN;0hp!S!dM^&W=&qim@ExuecQ_ztqo=czGS?i4`yjfGBK_J0A%DXV*mgE diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 39fb08327ba..d95d24d9a54 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -180,7 +180,7 @@ bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const return false; } } else { - g_logger().debug("[SpawnMonster] Spawning {} at {}", monsterType->name, sb.pos.toString()); + g_logger().trace("[SpawnMonster] Spawning {} at {}", monsterType->name, sb.pos.toString()); if (!g_game().placeCreature(monster, sb.pos, false, true)) { return false; } From 446718becbbc9d2b4e5b510ff9ee466884950d63 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 22:49:49 -0300 Subject: [PATCH 05/60] fix: more logs to debug --- .../soul_war/globalevent-ebb_and_flow_change_maps.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua index 7d2a64ef19c..d13b5cf2cd6 100644 --- a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua +++ b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua @@ -26,7 +26,7 @@ local function loadMapEmpty() local teleportPosition = player:getPosition() teleportPosition.z = 9 player:teleportTo(teleportPosition) - logger.debug("Teleporting player to down.") + logger.trace("Teleporting player to down.") end player:sendCreatureAppear() end @@ -80,10 +80,10 @@ local function loadMapInundate() if player:isInBoatSpot() then local nearestCenterPosition = findNearestRoomPosition(playerPosition) player:teleportTo(nearestCenterPosition) - logger.debug("Teleporting player to the near center position room and updating tile.") + logger.trace("Teleporting player to the near center position room and updating tile.") else player:teleportTo(SoulWarQuest.ebbAndFlow.waitPosition) - logger.debug("Teleporting player to wait position and updating tile.") + logger.trace("Teleporting player to wait position and updating tile.") end playerPosition:sendMagicEffect(CONST_ME_TELEPORT) end @@ -121,10 +121,10 @@ local eddAndFlowInundate = GlobalEvent("eddAndFlowInundate") function eddAndFlowInundate.onThink(interval, lastExecution) if SoulWarQuest.ebbAndFlow.isLoadedEmptyMap() then - logger.debug("Map change to empty in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + logger.trace("Map change to empty in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) loadMapInundate() elseif SoulWarQuest.ebbAndFlow.isActive() then - logger.debug("Map change to inundate in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + logger.trace("Map change to inundate in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) loadMapEmpty() end From be209b301524a82360fc91f2d0df10d42f570beb Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 10 Apr 2024 22:58:35 -0300 Subject: [PATCH 06/60] improve: "/pos" talkaction better handling --- data/scripts/talkactions/gm/position.lua | 43 ++++++++---------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/data/scripts/talkactions/gm/position.lua b/data/scripts/talkactions/gm/position.lua index 1869b109c5f..dc95552d5c2 100644 --- a/data/scripts/talkactions/gm/position.lua +++ b/data/scripts/talkactions/gm/position.lua @@ -1,23 +1,5 @@ local position = TalkAction("/pos", "!pos") -local function extractCoordinates(input) - local patterns = { - -- table format - "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", - -- Position format - "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", - -- x, y, z format - "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", - } - - for _, pattern in ipairs(patterns) do - local x, y, z = string.match(input, pattern) - if x and y and z then - return tonumber(x), tonumber(y), tonumber(z) - end - end -end - function position.onSay(player, words, param) -- create log logCommand(player, words, param) @@ -28,18 +10,21 @@ function position.onSay(player, words, param) return end - local x, y, z = extractCoordinates(param) - if x and y and z then - local teleportPosition = Position(x, y, z) - local tile = Tile(teleportPosition) - if not tile then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid tile or position. Send a valid position.") - return - end - - player:teleportTo(teleportPosition) - else + local teleportPosition = param:toPosition() + if not teleportPosition then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid position format. Use one of the following formats: \n/pos {x = ..., y = ..., z = ...}\n/pos Position(..., ..., ...)\n/pos x, y, z.") + return + end + + local tile = Tile(teleportPosition) + if not tile then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid tile or position. Send a valid position.") + return + end + + player:teleportTo(teleportPosition) + if not player:isInGhostMode() then + teleportPosition:sendMagicEffect(CONST_ME_TELEPORT) end end From 55edd29bae8df7ab8de7f009b8bb26083cfe6a14 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 11 Apr 2024 14:42:18 -0300 Subject: [PATCH 07/60] fix: miniBosses table --- data-otservbr-global/npc/flickering_soul.lua | 2 +- .../scripts/quests/soul_war/moveevent-soul_war_entrances.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data-otservbr-global/npc/flickering_soul.lua b/data-otservbr-global/npc/flickering_soul.lua index a6e11c311c6..92828d77396 100644 --- a/data-otservbr-global/npc/flickering_soul.lua +++ b/data-otservbr-global/npc/flickering_soul.lua @@ -168,7 +168,7 @@ local function playerSayCallback(npc, player, type, message) npcHandler:say("I despised my followers for their petty agendas and for their limited vision of my own goals and personality.", npc, player) elseif MsgContains(message, "shards") then local bossesYetToDefeat = {} - for bossName, _ in pairs(SoulWarBosses) do + for bossName, _ in pairs(SoulWarQuest.miniBosses) do if not soulWarQuest:get(bossName) then table.insert(bossesYetToDefeat, bossName) end diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua index ccf56ea6a3b..a6792315882 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua @@ -67,7 +67,7 @@ function soul_war_megalomania_entrance.onStepIn(creature, item, position, fromPo local text = "" local soulWarCount = 0 - for bossName, completed in pairs(SoulWarBosses) do + for bossName, completed in pairs(SoulWarQuest.miniBosses) do if soulWarQuest:get(bossName) == completed then soulWarCount = soulWarCount + 1 else From f9e664e3be98eadfcd89f3ddd992ee6f358956b5 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 11 Apr 2024 16:35:29 -0300 Subject: [PATCH 08/60] fix: some bugs --- .../soul_war/goshnar's_megalomania_green.lua | 1 - .../soul_war/goshnar's_megalomania_purple.lua | 1 - .../quests/soul_war/goshnars_spite.lua | 8 ----- .../quests/soul_war/soul_war_mechanics.lua | 14 ++++---- .../world/otservbr-monster.xml | 34 +++++++++++++------ 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index 5c6170d3f92..8a8d3d0d43f 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -25,7 +25,6 @@ monster.race = "undead" monster.corpse = 33889 monster.speed = 250 monster.manaCost = 0 -monster.maxSummons = 4 monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua index 6b7b12968e5..c2530a43087 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua @@ -14,7 +14,6 @@ monster.race = "undead" monster.corpse = 6028 monster.speed = 250 monster.manaCost = 0 -monster.maxSummons = 4 monster.changeTarget = { interval = 4000, diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 0e41373cc8e..40817c335b0 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -67,14 +67,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 50, interval = 1000, count = 2 }, - { name = "spiteful spitter", chance = 50, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 8c0c802aa33..49a67775a96 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -727,26 +727,26 @@ function pulsatingEnergy.onStepIn(creature, item, position, fromPosition) local secondFloorAccess = kv:get("second-floor-access") or false local thirdFloorAccess = kv:get("third-floor-access") or false if thirdFloorAccess then - player:sendTextMessage(MESSAGE_INFO_DESCR, "You've already gained access to fight with the Goshnar's Cruelty.") + 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_INFO_DESCR, "You've gained access to the first floor. Continue collecting Pulsating Energies to gain further access.") + 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_INFO_DESCR, "You've gained access to the second floor. Continue collecting Pulsating Energies to gain further access.") + 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_INFO_DESCR, "You've gained access to the third floor. You can now fight with the Goshnar's Cruelty.") + 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() @@ -771,7 +771,7 @@ function pulsatingEnergyTeleportAccess.onStepIn(creature, item, position, fromPo local energyCount = kv:get("access-counter") or 0 local energiesNeeded = posData.count - energyCount if not hasAccess then - player:sendTextMessage(MESSAGE_INFO_DESCR, "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: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 @@ -860,7 +860,7 @@ function greedyMaw.onUse(player, item, fromPosition, target, toPosition, isHotke SoulWarQuest.kvSoulWar:set("greedy-maw-action", currentTime + timeToIncreaseDefense) target:getPosition():sendMagicEffect(CONST_ME_DRAWBLOOD) item:remove() - player:sendTextMessage(MESSAGE_INFO_DESCR, "Use the item again within " .. timeToIncreaseDefense .. " seconds, or the monster's defense will increase by 2 every " .. timeToIncreaseDefense .. " seconds.") + 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() @@ -962,7 +962,7 @@ function cleansedSanity.onUse(player, item, fromPosition, target, toPosition, is SoulWarQuest.kvSoulWar:set("cleansed-sanity-action", currentTime + timeToIncreaseDefense) target:getPosition():sendMagicEffect(CONST_ME_DRAWBLOOD) item:remove() - player:sendTextMessage(MESSAGE_INFO_DESCR, "Use the item again within " .. timeToIncreaseDefense .. " seconds, or the monster's defense will increase by 2 every " .. timeToIncreaseDefense .. " seconds.") + 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 boss = Creature("Goshnar's Megalomania") if boss then local mtype = boss:getType() diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 0a876191095..40eb0ee4534 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -96137,15 +96137,15 @@ + + + - - - @@ -96730,6 +96730,9 @@ + + + @@ -96737,9 +96740,6 @@ - - - @@ -118901,12 +118901,12 @@ - - - + + + @@ -162231,6 +162231,9 @@ + + + @@ -162243,9 +162246,15 @@ + + + + + + @@ -162257,10 +162266,10 @@ - + - + @@ -162294,6 +162303,9 @@ + + + From 89f1744f78183f5fb9e1ab530e41537f31f8d65e Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 11 Apr 2024 16:48:57 -0300 Subject: [PATCH 09/60] feat: add mirror item to summon mirror image --- .../quests/soul_war/soul_war_mechanics.lua | 22 +++++++++++++++++++ data/items/items.xml | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 49a67775a96..ebf390e6c7b 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -142,6 +142,28 @@ 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) diff --git a/data/items/items.xml b/data/items/items.xml index c4ba2b3a93b..822caa3a9d2 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -62885,7 +62885,10 @@ hands of its owner. Granted by TibiaRoyal.com"/> - + + + + From 8319f29ad4c62d70587eb7f9c1d073e4d55219c0 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 11 Apr 2024 19:20:56 -0300 Subject: [PATCH 10/60] fix: map paths --- .../quests/soul_war/soul_war_mechanics.lua | 16 ++++++++-------- src/map/map.cpp | 6 +----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index ebf390e6c7b..488183fa94e 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -322,21 +322,21 @@ setTaint:separator(" ") setTaint:groupType("god") setTaint:register() -local setTaint = TalkAction("/changeflowmap") +local changeMap = TalkAction("/changeflowmap") -function setTaint.onSay(player, words, param) +function changeMap.onSay(player, words, param) if param == "empty" then - Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm") + Game.loadMap(SoulWarQuest.mapsPath.empty) elseif param == "inundate" then - Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/inundate.otbm") + Game.loadMap(SoulWarQuest.mapsPath.inundate) elseif param == "ebb" then - Game.loadMap("data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb_and_flow.otbm") + Game.loadMap(SoulWarQuest.mapsPath.ebbFlow) end end -setTaint:separator(" ") -setTaint:groupType("god") -setTaint:register() +changeMap:separator(" ") +changeMap:groupType("god") +changeMap:register() local hazardousPhantomDeath = CreatureEvent("HazardousPhantomDeath") diff --git a/src/map/map.cpp b/src/map/map.cpp index 55bb0fc6548..d5a510c33a7 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -27,11 +27,7 @@ void Map::load(const std::string &identifier, const Position &pos) { path = identifier; IOMap::loadMap(this, pos); } catch (const std::exception &e) { - throw IOMapException(fmt::format( - "\n[Map::load] - The map in folder {} is missing or corrupted" - "\n - {}", - identifier, e.what() - )); + g_logger().warn("[Map::load] - The map in folder {} is missing or corrupted", identifier); } } From b1fb702eccb1368853bf77d13832a175d5ab521d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 15 Apr 2024 18:45:29 -0300 Subject: [PATCH 11/60] fix: add condition for taint icon --- .../scripts/lib/quests/soul-war.lua | 42 ++++++++++++------- .../quests/soul_war/soul_war_mechanics.lua | 8 +++- .../scripts/talkactions/god/add_condition.lua | 10 +++++ src/creatures/combat/condition.cpp | 23 +++++++++- src/creatures/creatures_definitions.hpp | 6 +-- src/lua/functions/core/game/lua_enums.cpp | 34 ++------------- src/utils/utils_definitions.hpp | 10 ++--- 7 files changed, 76 insertions(+), 57 deletions(-) create mode 100644 data/scripts/talkactions/god/add_condition.lua diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/scripts/lib/quests/soul-war.lua index 4a8a47ee131..7ebe42b0441 100644 --- a/data-otservbr-global/scripts/lib/quests/soul-war.lua +++ b/data-otservbr-global/scripts/lib/quests/soul-war.lua @@ -970,11 +970,11 @@ end TaintTeleportCooldown = {} -function Player:getTaintNameByNumber(taintNumber) +function Player:getTaintNameByNumber(taintNumber, skipKvCheck) local haveTaintName = nil local soulWarQuest = self:soulWarQuestKV() local taintName = soulWarTaints[taintNumber] - if taintName and soulWarQuest:get(taintName) then + if skipKvCheck or taintName and soulWarQuest:get(taintName) then haveTaintName = taintName end @@ -983,15 +983,29 @@ end function Player:addNextTaint() local soulWarQuest = self:soulWarQuestKV() - for _, taint in ipairs(soulWarTaints) do - if not soulWarQuest:get(taint) then - soulWarQuest:set(taint, true) - self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have gained the " .. taint .. ".") + 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() @@ -1004,17 +1018,17 @@ function Player:getTaintLevel() return taintLevel end -function Player:resetTaints() +function Player:resetTaints(skipCheckTime) local soulWarQuest = self:soulWarQuestKV() local firstTaintTime = soulWarQuest:get("firstTaintTime") - if firstTaintTime and os.time() >= (firstTaintTime + TaintDurationSeconds) then - -- Reset all taints - for _, taint in ipairs(soulWarTaints) do - if soulWarQuest:get(taint) then - soulWarQuest:remove(taint) + 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") self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your goshnar's taints have been reset. You didn't finish the quest in 14 days") end @@ -1028,7 +1042,7 @@ function Monster:tryTeleportToPlayer(sayMessage) for i, spectator in ipairs(spectators) do if spectator:isPlayer() then local player = spectator:getPlayer() - if player:getTaintNameByNumber(1) then + if player:getTaintNameByNumber(1, true) then local distance = self:getPosition():getDistance(player:getPosition()) if distance > maxDistance then maxDistance = distance diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 488183fa94e..7a7103fa08f 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -44,6 +44,10 @@ login:register() 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 @@ -261,11 +265,13 @@ function setTaint.onSay(player, words, param) end local taintLevel = split[2]:trim():lower() - local taintName = player:getTaintNameByNumber(tonumber(taintLevel)) + 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 diff --git a/data/scripts/talkactions/god/add_condition.lua b/data/scripts/talkactions/god/add_condition.lua new file mode 100644 index 00000000000..7d92cbc0a09 --- /dev/null +++ b/data/scripts/talkactions/god/add_condition.lua @@ -0,0 +1,10 @@ +local talkaction = TalkAction("/testtaintconditions") + +function talkaction.onSay(player, words, param) + player:setTaintIcon() + return false +end + +talkaction:separator(" ") +talkaction:groupType("god") +talkaction:register() diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 9d0f6962884..74eded14e1f 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -242,6 +242,8 @@ std::shared_ptr Condition::createCondition(ConditionId_t id, Conditio case CONDITION_YELLTICKS: case CONDITION_PACIFIED: return std::make_shared(id, type, ticks, buff, subId); + case CONDITION_GOSHNARTAINT: + return std::make_shared(id, type, ticks, buff, subId); default: return nullptr; @@ -390,7 +392,26 @@ uint32_t ConditionGeneric::getIcons() const { case CONDITION_ROOTED: icons |= ICON_ROOTED; break; - + case CONDITION_GOSHNARTAINT: + switch (subId) { + case 1: + icons = ICON_GOSHNARTAINT_1; + break; + case 2: + icons = ICON_GOSHNARTAINT_2; + break; + case 3: + icons = ICON_GOSHNARTAINT_3; + break; + case 4: + icons = ICON_GOSHNARTAINT_4; + break; + case 5: + icons = ICON_GOSHNARTAINT_5; + break; + default: + break; + } default: break; } diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index f1929a633c1..b66293c594a 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -112,11 +112,7 @@ enum ConditionType_t : uint8_t { CONDITION_LESSERHEX = 31, CONDITION_INTENSEHEX = 32, CONDITION_GREATERHEX = 33, - CONDITION_GOSHNAR1 = 34, - CONDITION_GOSHNAR2 = 35, - CONDITION_GOSHNAR3 = 36, - CONDITION_GOSHNAR4 = 37, - CONDITION_GOSHNAR5 = 38, + CONDITION_GOSHNARTAINT = 34, // Need the last ever CONDITION_COUNT = 39 diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 19e7b9219ec..46335d2fde7 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -316,37 +316,9 @@ void LuaEnums::initFactionEnums(lua_State* L) { } void LuaEnums::initConditionEnums(lua_State* L) { - registerEnum(L, CONDITION_NONE); - registerEnum(L, CONDITION_POISON); - registerEnum(L, CONDITION_FIRE); - registerEnum(L, CONDITION_ENERGY); - registerEnum(L, CONDITION_BLEEDING); - registerEnum(L, CONDITION_HASTE); - registerEnum(L, CONDITION_PARALYZE); - registerEnum(L, CONDITION_OUTFIT); - registerEnum(L, CONDITION_INVISIBLE); - registerEnum(L, CONDITION_LIGHT); - registerEnum(L, CONDITION_MANASHIELD); - registerEnum(L, CONDITION_INFIGHT); - registerEnum(L, CONDITION_DRUNK); - registerEnum(L, CONDITION_EXHAUST); - registerEnum(L, CONDITION_REGENERATION); - registerEnum(L, CONDITION_SOUL); - registerEnum(L, CONDITION_DROWN); - registerEnum(L, CONDITION_MUTED); - registerEnum(L, CONDITION_CHANNELMUTEDTICKS); - registerEnum(L, CONDITION_YELLTICKS); - registerEnum(L, CONDITION_ATTRIBUTES); - registerEnum(L, CONDITION_FREEZING); - registerEnum(L, CONDITION_DAZZLED); - registerEnum(L, CONDITION_CURSED); - registerEnum(L, CONDITION_EXHAUST_COMBAT); - registerEnum(L, CONDITION_EXHAUST_HEAL); - registerEnum(L, CONDITION_PACIFIED); - registerEnum(L, CONDITION_SPELLCOOLDOWN); - registerEnum(L, CONDITION_SPELLGROUPCOOLDOWN); - registerEnum(L, CONDITION_ROOTED); - registerEnum(L, CONDITION_FEARED); + for (auto value : magic_enum::enum_values()) { + registerMagicEnum(L, value); + } } void LuaEnums::initConditionIdEnums(lua_State* L) { diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp index af2c40ba533..e2992379f0c 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -32,11 +32,11 @@ enum Icons_t { ICON_GREATERHEX = 1 << 18, ICON_ROOTED = 1 << 19, ICON_FEARED = 1 << 20, - ICON_GOSHNAR1 = 1 << 21, - ICON_GOSHNAR2 = 1 << 22, - ICON_GOSHNAR3 = 1 << 23, - ICON_GOSHNAR4 = 1 << 24, - ICON_GOSHNAR5 = 1 << 25, + ICON_GOSHNARTAINT_1 = 1 << 21, + ICON_GOSHNARTAINT_2 = 1 << 22, + ICON_GOSHNARTAINT_3 = 1 << 23, + ICON_GOSHNARTAINT_4 = 1 << 24, + ICON_GOSHNARTAINT_5 = 1 << 25, ICON_NEWMANASHIELD = 1 << 26, }; From f01418a4844b1553a92fd4ff4d1225d1ac1c00bc Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 15 Apr 2024 19:21:42 -0300 Subject: [PATCH 12/60] fix: cloak of terror mechanic --- .../scripts/quests/soul_war/soul_war_mechanics.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 7a7103fa08f..9f8c5c4f031 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -828,7 +828,13 @@ function cloakOfTerrorHealthLoss.onHealthChange(creature, attacker, primaryDamag end if attacker:getPlayer() and primaryDamage > 0 or secondaryDamage > 0 then - Game.createItem(SoulWarQuest.theBloodOfCloakTerrorIds[1], 1, creature:getPosition()) + 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 @@ -856,6 +862,8 @@ function theBloodOfCloakStep.onStepIn(creature, item, position, fromPosition) monster:addHealth(healAmount) end + item:remove() + return true end From 316b668ebf8384777c27abd58816a6e6c92453c9 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 16 Apr 2024 10:10:18 -0300 Subject: [PATCH 13/60] fix: change soul war table to lib folder --- data-otservbr-global/lib/quests/quest.lua | 1 + .../{scripts/lib/quests/soul-war.lua => lib/quests/soul_war.lua} | 0 2 files changed, 1 insertion(+) rename data-otservbr-global/{scripts/lib/quests/soul-war.lua => lib/quests/soul_war.lua} (100%) diff --git a/data-otservbr-global/lib/quests/quest.lua b/data-otservbr-global/lib/quests/quest.lua index 21b1744fec6..392367c9d94 100644 --- a/data-otservbr-global/lib/quests/quest.lua +++ b/data-otservbr-global/lib/quests/quest.lua @@ -5,5 +5,6 @@ dofile(DATA_DIRECTORY .. "/lib/quests/killing_in_the_name_of.lua") dofile(DATA_DIRECTORY .. "/lib/quests/svargrond_arena.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_cursed_crystal.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_queen_of_the_banshees.lua") +dofile(DATA_DIRECTORY .. "/lib/quests/soul_war.lua") dofile(DATA_DIRECTORY .. "/lib/quests/their_masters_voice.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_primal_ordeal.lua") diff --git a/data-otservbr-global/scripts/lib/quests/soul-war.lua b/data-otservbr-global/lib/quests/soul_war.lua similarity index 100% rename from data-otservbr-global/scripts/lib/quests/soul-war.lua rename to data-otservbr-global/lib/quests/soul_war.lua From c3bbf05b1561e004e0fcde45f8c27f4e60081fd3 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 16 Apr 2024 10:21:48 -0300 Subject: [PATCH 14/60] fix: no removable conditions on death --- src/creatures/combat/condition.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 74eded14e1f..cb39c9c79d2 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -324,7 +324,14 @@ bool Condition::isRemovableOnDeath() const { return false; } - if (conditionType == CONDITION_SPELLCOOLDOWN || conditionType == CONDITION_SPELLGROUPCOOLDOWN || conditionType == CONDITION_MUTED) { + static const std::unordered_set nonRemovableConditions = { + CONDITION_SPELLCOOLDOWN, + CONDITION_SPELLGROUPCOOLDOWN, + CONDITION_MUTED, + CONDITION_GOSHNARTAINT + }; + + if (nonRemovableConditions.find(conditionType) != nonRemovableConditions.end()) { return false; } From cd7a82c894fc5543db733c4672bce5bb6f862c57 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 16 Apr 2024 11:22:39 -0300 Subject: [PATCH 15/60] fix: nil value with boss levers --- data-otservbr-global/lib/quests/soul_war.lua | 33 +++++++++++++++++++ .../quests/soul_war/soul_war_mechanics.lua | 31 ----------------- data/scripts/lib/quests.lua | 2 ++ 3 files changed, 35 insertions(+), 31 deletions(-) create mode 100644 data/scripts/lib/quests.lua diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 7ebe42b0441..aa71fc36d95 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -680,6 +680,39 @@ SoulWarQuest = { }, } +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 }) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 9f8c5c4f031..bc6d6a7e920 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -1,34 +1,3 @@ --- 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()) - local login = CreatureEvent("SoulWarLogin") function login.onLogin(player) diff --git a/data/scripts/lib/quests.lua b/data/scripts/lib/quests.lua new file mode 100644 index 00000000000..08005e34ae2 --- /dev/null +++ b/data/scripts/lib/quests.lua @@ -0,0 +1,2 @@ +-- We need to register the variables beforehand to avoid accessing null values. +RegisterSoulWarBossesLevers() From ce7c611dc2eb2ca4b27e157a2e538f58866853f9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 29 Apr 2024 17:38:18 +0000 Subject: [PATCH 16/60] Code format - (Clang-format) --- src/lua/callbacks/callbacks_definitions.hpp | 2 +- src/lua/callbacks/event_callback.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index ff8c05e7fab..6c19cc809c9 100644 --- a/src/lua/callbacks/callbacks_definitions.hpp +++ b/src/lua/callbacks/callbacks_definitions.hpp @@ -58,7 +58,7 @@ enum class EventCallback_t : uint16_t { playerOnInventoryUpdate, playerOnRotateItem, playerOnWalk, - playerOnThink, + playerOnThink, // Monster monsterOnDropLoot, monsterPostDropLoot, diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp index f95bbce0b12..9e65480c246 100644 --- a/src/lua/callbacks/event_callback.hpp +++ b/src/lua/callbacks/event_callback.hpp @@ -131,7 +131,7 @@ class EventCallback : public Script { void playerOnCombat(std::shared_ptr player, std::shared_ptr target, std::shared_ptr item, CombatDamage &damage) const; void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr item, Slots_t slot, bool equip) const; bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr item, const Position &position) const; - void playerOnWalk(std::shared_ptr player, Direction &dir) const; + void playerOnWalk(std::shared_ptr player, Direction &dir) const; void playerOnThink(std::shared_ptr player, uint32_t interval) const; // Monster From 164e093b22df25c90b19e3a57e2302183eb6a25d Mon Sep 17 00:00:00 2001 From: Pedro Henrique Alves Cruz Date: Sun, 12 May 2024 02:36:22 -0300 Subject: [PATCH 17/60] fix: mechanics and npc flickering soul changes: - Added taints removal with words "taints" and "penalties" to npc flickering soul - Fixed soul war mechanics that should not be applied when in safe places (before hunt teleport) - Removed bag you desire as loot of soul war monsters --- data-otservbr-global/lib/quests/soul_war.lua | 35 +++++++++++++++++-- .../normal_monsters/bony_sea_devil.lua | 1 - .../soul_war/normal_monsters/brachiodemon.lua | 1 - .../normal_monsters/branchy_crawler.lua | 1 - .../normal_monsters/capricious_phantom.lua | 1 - .../normal_monsters/distorted_phantom.lua | 1 - .../normal_monsters/druid's_apparition.lua | 1 - .../furious_crater/cloak_of_terror.lua | 1 - .../furious_crater/courage_leech.lua | 1 - .../furious_crater/vibrant_phantom.lua | 1 - .../normal_monsters/infernal_demon.lua | 1 - .../normal_monsters/infernal_phantom.lua | 1 - .../normal_monsters/knight's_apparition.lua | 1 - .../soul_war/normal_monsters/many_faces.lua | 1 - .../normal_monsters/mould_phantom.lua | 1 - .../normal_monsters/paladin's_apparition.lua | 1 - .../soul_war/normal_monsters/rotten_golem.lua | 1 - .../normal_monsters/sorcerer's_apparition.lua | 1 - .../normal_monsters/turbulent_elemental.lua | 1 - .../monster/undeads/hazardous_phantom.lua | 1 - data-otservbr-global/npc/flickering_soul.lua | 4 +++ .../eventcallback_on_combat_taint.lua | 2 +- .../quests/soul_war/soul_war_mechanics.lua | 6 ++-- 23 files changed, 40 insertions(+), 26 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index aa71fc36d95..987d65d5816 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -132,7 +132,15 @@ SoulWarQuest = { ["boss.goshnar's-megalomania-purple"] = "Dreadful Harvester", }, - caustrophobicInferno = Zone("zone.claustrophobic-inferno"), + safe = { + claustrophobicInferno = Zone("safezone.claustrophobic-inferno"), + mirroredNightmare = Zone("safezone.mirrored-nightmare"), + ebbAndFlow = Zone("safezone.ebb-and-flow"), + furiousCrater = Zone("safezone.furious-crater"), + rottenWasteland = Zone("safezone.rotten-wasteland"), + }, + + claustrophobicInferno = Zone("zone.claustrophobic-inferno"), mirroredNightmare = Zone("zone.mirrored-nightmare"), ebbAndFlow = Zone("zone.ebb-and-flow"), furiousCrater = Zone("zone.furious-crater"), @@ -716,8 +724,19 @@ end -- Initialize ebb and flow zone area SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 33964, y = 31147, z = 9 }) +-- Initialize safe areas (should not spawn monster, teleport, take damage from taint, etc) +SoulWarQuest.areaZones.safe.ebbAndFlow:addArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) + +SoulWarQuest.areaZones.safe.claustrophobicInferno:addArea({ x = 34002, y = 31008, z = 9 }, { x = 34019, y = 31019, z = 9 }) + +SoulWarQuest.areaZones.safe.furiousCrater:addArea({ x = 33854, y = 31828, z = 3 }, { x = 33869, y = 31834, z = 3 }) + +SoulWarQuest.areaZones.safe.rottenWasteland:addArea({ x = 33967, y = 31037, z = 11 }, { x = 33977, y = 31051, z = 11 }) + +SoulWarQuest.areaZones.safe.mirroredNightmare:addArea({ x = 33884, y = 31181, z = 10 }, { x = 33892, y = 31198, z = 10 }) + -- Initialize bosses access for taint check -SoulWarQuest.areaZones.caustrophobicInferno:addArea({ x = 33982, y = 30981, z = 9 }, { x = 34051, y = 31110, z = 11 }) +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 }) @@ -1075,7 +1094,7 @@ function Monster:tryTeleportToPlayer(sayMessage) for i, spectator in ipairs(spectators) do if spectator:isPlayer() then local player = spectator:getPlayer() - if player:getTaintNameByNumber(1, true) then + if player:getTaintNameByNumber(1, true) and not player:isInSafeZone() then local distance = self:getPosition():getDistance(player:getPosition()) if distance > maxDistance then maxDistance = distance @@ -1259,6 +1278,16 @@ function Monster:goshnarsDefenseIncrease(kvName) end end +function Player:isInSafeZone() + for zoneName, zone in pairs(SoulWarQuest.areaZones.safe) do + if zone and zone:isInZone(self:getPosition()) then + return true + end + end + + return false +end + function Player:getSoulWarZoneMonster() local zoneMonsterName = nil for zoneName, monsterName in pairs(SoulWarQuest.areaZones.monsters) do diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua index 2737bd3a098..95120bfb0d2 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua @@ -100,7 +100,6 @@ monster.loot = { { name = "goblet of gloom", chance = 880 }, { name = "glacier kilt", chance = 880 }, { name = "glacial rod", chance = 1210 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua index 378a15814c6..dfb94760041 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua @@ -103,7 +103,6 @@ monster.loot = { { name = "mastermind shield", chance = 420 }, { name = "assassin dagger", chance = 340 }, { name = "alloy legs", chance = 170 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua index 0798c9f482f..87a491a29d9 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua @@ -98,7 +98,6 @@ monster.loot = { { name = "twiceslicer", chance = 420 }, { name = "crystalline sword", chance = 390 }, { name = "ruthless axe", chance = 330 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua index 6a3b5ea0bbc..15221cdaf0a 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua @@ -97,7 +97,6 @@ monster.loot = { { id = 23542, chance = 1180 }, -- collar of blue plasma { name = "glacial rod", chance = 940 }, { name = "ornate crossbow", chance = 940 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua index a27ae569343..12d78f77654 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua @@ -96,7 +96,6 @@ monster.loot = { { name = "spellbook of warding", chance = 2890 }, { id = 23531, chance = 1930 }, -- ring of green plasma { name = "glacial rod", chance = 1290 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua index 6948a58fb53..c0436e734a3 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua @@ -94,7 +94,6 @@ monster.loot = { { name = "platinum amulet", chance = 1750 }, { name = "glacier robe", chance = 880 }, { id = 23544, chance = 440 }, -- collar of red plasma - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua index 4048e0fd69e..c5e440ef02e 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua @@ -98,7 +98,6 @@ monster.loot = { { name = "blue gem", chance = 1490 }, { name = "brooch of embracement", chance = 1490 }, { name = "wand of defiance", chance = 990 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua index 8e4a61b6ef0..4df38a95cc7 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua @@ -96,7 +96,6 @@ monster.loot = { { name = "stone skin amulet", chance = 910 }, { name = "nightmare blade", chance = 1190 }, { name = "demonrage sword", chance = 600 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua index b6d4b89f024..f4ab16a5307 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua @@ -98,7 +98,6 @@ monster.loot = { { name = "violet crystal shard", chance = 1080 }, { id = 23529, chance = 1080 }, -- ring of blue plasma { name = "green gem", chance = 1080 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua index 8760d3b1757..9fa157fa7a0 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua @@ -98,7 +98,6 @@ monster.loot = { { name = "giant sword", chance = 2860 }, { name = "magma boots", chance = 2290 }, { name = "stone skin amulet", chance = 570 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua index e8ef7b231f1..1704c7338e0 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua @@ -99,7 +99,6 @@ monster.loot = { { name = "crystal mace", chance = 1610 }, { name = "war axe", chance = 1410 }, { name = "warrior's axe", chance = 1410 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua index 86893bfa01c..b9be9209d0e 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua @@ -92,7 +92,6 @@ monster.loot = { { name = "giant sword", chance = 1720 }, { name = "stone skin amulet", chance = 1500 }, { name = "crown shield", chance = 640 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua index 627f1dc8052..ad924df93ff 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua @@ -99,7 +99,6 @@ monster.loot = { { name = "glacier robe", chance = 2130 }, { name = "gruesome fan", chance = 610 }, { name = "glacial rod", chance = 610 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua index 54f3794360b..a2b38a76b90 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua @@ -97,7 +97,6 @@ monster.loot = { { id = 23529, chance = 1040 }, -- ring of blue plasma { name = "ornate crossbow", chance = 840 }, { name = "crystal crossbow", chance = 620 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua index 5162f5d1808..ab9db4fe484 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua @@ -95,7 +95,6 @@ monster.loot = { { name = "stone skin amulet", chance = 1560 }, { id = 23542, chance = 1250 }, -- collar of blue plasma { id = 23529, chance = 1250 }, -- ring of blue plasma - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua index bf9ea5b7555..f49c96d8b7d 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua @@ -95,7 +95,6 @@ monster.loot = { { name = "stone skin amulet", chance = 740 }, { name = "terra mantle", chance = 510 }, { name = "rubber cap", chance = 430 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua index c054a0d584f..63faa04b271 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua @@ -96,7 +96,6 @@ monster.loot = { { name = "wand of starstorm", chance = 1310 }, { name = "stone skin amulet", chance = 1310 }, { name = "alloy legs", chance = 440 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua index feaf706ae97..57cacb3ee76 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua @@ -95,7 +95,6 @@ monster.loot = { { name = "crystalline armor", chance = 710 }, { name = "rubber cap", chance = 710 }, { name = "stone skin amulet", chance = 470 }, - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/hazardous_phantom.lua b/data-otservbr-global/monster/undeads/hazardous_phantom.lua index 4d3eb338b19..f1ba2b8c335 100644 --- a/data-otservbr-global/monster/undeads/hazardous_phantom.lua +++ b/data-otservbr-global/monster/undeads/hazardous_phantom.lua @@ -82,7 +82,6 @@ monster.loot = { { id = 282, chance = 1570 }, -- giant shimmering pearl { name = "wand of everblazing", chance = 790 }, { id = 23542, chance = 790 }, -- collar of blue plasma - { id = 34109, chance = 20 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/npc/flickering_soul.lua b/data-otservbr-global/npc/flickering_soul.lua index 92828d77396..a00df70e338 100644 --- a/data-otservbr-global/npc/flickering_soul.lua +++ b/data-otservbr-global/npc/flickering_soul.lua @@ -181,6 +181,10 @@ local function playerSayCallback(npc, player, type, message) message = "You have defeated all the Goshnar's Bosses. Your soul shines brighter with each victory." end npcHandler:say(message, npc, player) + elseif MsgContains(message, "taints") or MsgContains(message, "penalties") then + if player:getTaintLevel() == nil then + player:resetTaints() + end end return true end diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua index 3e5ec34f930..936ab8502aa 100644 --- a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua @@ -111,7 +111,7 @@ function callback.playerOnThink(player, interval) if accumulatedTime[playerId] >= 10000 then local soulWarQuest = player:soulWarQuestKV() - if player:getSoulWarZoneMonster() ~= nil and player:getTaintNameByNumber(5) ~= nil then + if player:getSoulWarZoneMonster() ~= nil and not player:isInSafeZone() 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) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index bc6d6a7e920..dc7197176fa 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -301,11 +301,11 @@ local changeMap = TalkAction("/changeflowmap") function changeMap.onSay(player, words, param) if param == "empty" then - Game.loadMap(SoulWarQuest.mapsPath.empty) + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.empty) elseif param == "inundate" then - Game.loadMap(SoulWarQuest.mapsPath.inundate) + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.inundate) elseif param == "ebb" then - Game.loadMap(SoulWarQuest.mapsPath.ebbFlow) + Game.loadMap(SoulWarQuest.ebbAndFlowmapsPath.ebbFlow) end end From b9ce9ff22660014a8c60233c1f3f089a7ac4b742 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 12 May 2024 12:46:47 +0000 Subject: [PATCH 18/60] Code format - (Clang-format) --- src/lua/functions/creatures/player/player_functions.cpp | 6 +++--- src/lua/functions/creatures/player/player_functions.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 6027dad7615..d4eb3eec79a 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4302,8 +4302,8 @@ int PlayerFunctions::luaPlayerAddBadge(lua_State* L) { reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); return 1; } - - player->badge()->add(getNumber(L, 2, 0)); + + player->badge()->add(getNumber(L, 2, 0)); pushBoolean(L, true); return 1; } @@ -4373,4 +4373,4 @@ int PlayerFunctions::luaPlayerSendCreatureAppear(lua_State* L) { player->sendCreatureAppear(player, player->getPosition(), isLogin); pushBoolean(L, true); return 1; -} \ No newline at end of file +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index f483b9f9b6f..e9114a708e7 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -371,8 +371,8 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addTitle", PlayerFunctions::luaPlayerAddTitle); registerMethod(L, "Player", "getTitles", PlayerFunctions::luaPlayerGetTitles); registerMethod(L, "Player", "setCurrentTitle", PlayerFunctions::luaPlayerSetCurrentTitle); - - registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); + + registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); GroupFunctions::init(L); GuildFunctions::init(L); @@ -734,7 +734,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerGetTitles(lua_State* L); static int luaPlayerSetCurrentTitle(lua_State* L); - static int luaPlayerSendCreatureAppear(lua_State* L); - + static int luaPlayerSendCreatureAppear(lua_State* L); + friend class CreatureFunctions; }; From beb3dba61d382ca865dd746686eea867cbe7997d Mon Sep 17 00:00:00 2001 From: Pedro Henrique Alves Cruz Date: Sun, 12 May 2024 10:48:42 -0300 Subject: [PATCH 19/60] feat: claustrophobic inferno boss access mechanics changes: - Added raid system to access boss on claustrophobic inferno --- data-otservbr-global/lib/quests/soul_war.lua | 118 +++++++++++++++--- .../moveevent-claustrophobic-inferno-raid.lua | 85 +++++++++++++ 2 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 987d65d5816..06ecc85fe04 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -117,6 +117,98 @@ SoulWarQuest = { { from = Position(33856, 31884, 5), to = Position(33857, 31865, 6), access = "third-floor-access", count = 70 }, }, + safeZones = { + claustrophobicInferno = Zone("safe.claustrophobic-inferno"), + mirroredNightmare = Zone("safe.mirrored-nightmare"), + ebbAndFlow = Zone("safe.ebb-and-flow"), + furiousCrater = Zone("safe.furious-crater"), + rottenWasteland = Zone("safe.rotten-wasteland"), + }, + + raids = { + [1] = { + timerStarted = false, + 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 = Position(34009, 31083, 9), + endEvent = nil, + kickEvent = nil, + spawnEvent = nil, + getZone = function() + return SoulWarQuest.raids[1].zone + end, + toggleTimer = function() + SoulWarQuest.raids[1].timerStarted = not SoulWarQuest.raids[1].timerStarted + end, + }, + [2] = { + timerStarted = false, + 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(34005, 31052, 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 = Position(34011, 31028, 10), + endEvent = nil, + kickEvent = nil, + spawnEvent = nil, + getZone = function() + return SoulWarQuest.raids[2].zone + end, + toggleTimer = function() + SoulWarQuest.raids[2].timerStarted = not SoulWarQuest.raids[2].timerStarted + end, + }, + [3] = { + timerStarted = false, + 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(33995, 31055, 11), + Position(34001, 31069, 11), Position(33999, 31068, 11), Position(34016, 31068, 11), Position(34029, 31071, 11), + Position(34030, 31070, 11), Position(34038, 31066, 11), Position(34038, 31051, 11), Position(34033, 31051, 11), + Position(34025, 31048, 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 = Position(34014, 31085, 11), + endEvent = nil, + kickEvent = nil, + spawnEvent = nil, + getZone = function() + return SoulWarQuest.raids[3].zone + end, + toggleTimer = function() + SoulWarQuest.raids[3].timerStarted = not SoulWarQuest.raids[3].timerStarted + end, + }, + spawnTime = 10, -- seconds + suriviveTime = 2 * 60, -- 2 minutes + timeToKick = 5, -- seconds + }, + areaZones = { monsters = { ["zone.claustrophobic-inferno"] = "Brachiodemon", @@ -132,14 +224,6 @@ SoulWarQuest = { ["boss.goshnar's-megalomania-purple"] = "Dreadful Harvester", }, - safe = { - claustrophobicInferno = Zone("safezone.claustrophobic-inferno"), - mirroredNightmare = Zone("safezone.mirrored-nightmare"), - ebbAndFlow = Zone("safezone.ebb-and-flow"), - furiousCrater = Zone("safezone.furious-crater"), - rottenWasteland = Zone("safezone.rotten-wasteland"), - }, - claustrophobicInferno = Zone("zone.claustrophobic-inferno"), mirroredNightmare = Zone("zone.mirrored-nightmare"), ebbAndFlow = Zone("zone.ebb-and-flow"), @@ -724,16 +808,22 @@ 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 + +SoulWarQuest.raids[1].zone:addArea({ x = 33985, y = 31053, z = 9}, { x = 34045, y = 31077, z = 9}) +SoulWarQuest.raids[2].zone:addArea({ x = 33988, y = 31042, z = 10}, { x = 34043, y = 31068, z = 10}) +SoulWarQuest.raids[3].zone:addArea({ x = 33987, y = 31043, z = 11}, { x = 34044, y = 31076, z = 11}) + -- Initialize safe areas (should not spawn monster, teleport, take damage from taint, etc) -SoulWarQuest.areaZones.safe.ebbAndFlow:addArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) +SoulWarQuest.safeZones.ebbAndFlow:addArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) -SoulWarQuest.areaZones.safe.claustrophobicInferno:addArea({ x = 34002, y = 31008, z = 9 }, { x = 34019, y = 31019, z = 9 }) +SoulWarQuest.safeZones.claustrophobicInferno:addArea({ x = 34002, y = 31008, z = 9 }, { x = 34019, y = 31019, z = 9 }) -SoulWarQuest.areaZones.safe.furiousCrater:addArea({ x = 33854, y = 31828, z = 3 }, { x = 33869, y = 31834, z = 3 }) +SoulWarQuest.safeZones.furiousCrater:addArea({ x = 33854, y = 31828, z = 3 }, { x = 33869, y = 31834, z = 3 }) -SoulWarQuest.areaZones.safe.rottenWasteland:addArea({ x = 33967, y = 31037, z = 11 }, { x = 33977, y = 31051, z = 11 }) +SoulWarQuest.safeZones.rottenWasteland:addArea({ x = 33967, y = 31037, z = 11 }, { x = 33977, y = 31051, z = 11 }) -SoulWarQuest.areaZones.safe.mirroredNightmare:addArea({ x = 33884, y = 31181, z = 10 }, { x = 33892, y = 31198, z = 10 }) +SoulWarQuest.safeZones.mirroredNightmare:addArea({ x = 33884, y = 31181, z = 10 }, { x = 33892, y = 31198, z = 10 }) -- Initialize bosses access for taint check SoulWarQuest.areaZones.claustrophobicInferno:addArea({ x = 33982, y = 30981, z = 9 }, { x = 34051, y = 31110, z = 11 }) @@ -1279,7 +1369,7 @@ function Monster:goshnarsDefenseIncrease(kvName) end function Player:isInSafeZone() - for zoneName, zone in pairs(SoulWarQuest.areaZones.safe) do + for zoneName, zone in pairs(SoulWarQuest.safeZones) do if zone and zone:isInZone(self:getPosition()) then return true end diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua new file mode 100644 index 00000000000..ea1d004b99a --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -0,0 +1,85 @@ +local firstRaid = MoveEvent() +local secondRaid = MoveEvent() +local thirdRaid = MoveEvent() + +local spawnMonsterName = "Brachiodemon" + +local firstRaidNumber = 1 +local secondRaidNumber = 2 +local thirdRaidNumber = 3 + +local function createTeleportEffect(position) + position:sendMagicEffect(CONST_ME_TELEPORT) +end + +local function spawnMonsters(raidNumber) + if not SoulWarQuest.raids[raidNumber].timerStarted then return end + for _, spawnPosition in pairs(SoulWarQuest.raids[raidNumber].spawns) do + addEvent(createTeleportEffect, 1000, spawnPosition) + addEvent(createTeleportEffect, 2000, spawnPosition) + addEvent(createTeleportEffect, 3000, spawnPosition) + addEvent(Game.createMonster, 4000, spawnMonsterName, spawnPosition, true, true) + end + SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) +end + +local function kickPlayers(zone, raidNumber) + SoulWarQuest.raids[raidNumber].toggleTimer() + for _, player in pairs(zone:getPlayers()) do + player:teleportTo(SoulWarQuest.raids[raidNumber].exitPosition) + end +end + +local function endRaid(zone, raidNumber) + if SoulWarQuest.raids[raidNumber].spawnEvent then stopEvent(SoulWarQuest.raids[raidNumber].spawnEvent) end + for _, monster in pairs(zone:getMonsters()) do + if not monster:getMaster() then + monster:getPosition():sendMagicEffect(CONST_ME_POFF) + monster:remove() + end + end + SoulWarQuest.raids[raidNumber].kickEvent = addEvent(kickPlayers, SoulWarQuest.raids.timeToKick * 1000, zone, raidNumber) + logger.debug("Claustrophobic Inferno Raid #{} ended", raidNumber) +end + +local function raid(zone, raidNumber) + if SoulWarQuest.raids[raidNumber].timerStarted then return end + logger.debug("Claustrophobic Inferno Raid #{} started", raidNumber) + SoulWarQuest.raids[raidNumber].toggleTimer() + SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) + SoulWarQuest.raids[raidNumber].endEvent = addEvent(endRaid, SoulWarQuest.raids.suriviveTime * 1000, zone, raidNumber) +end + +function firstRaid.onStepIn(creature, item, position, fromPosition) + if not creature:getPlayer() then return true end + raid(SoulWarQuest.raids[firstRaidNumber].getZone(), firstRaidNumber) + return true +end + +function secondRaid.onStepIn(creature, item, position, fromPosition) + if not creature:getPlayer() then return true end + raid(SoulWarQuest.raids[secondRaidNumber].getZone(), secondRaidNumber) + return true +end + +function thirdRaid.onStepIn(creature, item, position, fromPosition) + if not creature:getPlayer() then return true end + raid(SoulWarQuest.raids[thirdRaidNumber].getZone(), thirdRaidNumber) + return true +end + +for _, pos in pairs(SoulWarQuest.raids[firstRaidNumber].sandTimerPositions) do + firstRaid:position(pos) +end + +for _, pos in pairs(SoulWarQuest.raids[secondRaidNumber].sandTimerPositions) do + secondRaid:position(pos) +end + +for _, position in pairs(SoulWarQuest.raids[thirdRaidNumber].sandTimerPositions) do + thirdRaid:position(position) +end + +firstRaid:register() +secondRaid:register() +thirdRaid:register() From 19346e2b601ed8322ba59524e871d4981880e0b9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 12 May 2024 13:51:00 +0000 Subject: [PATCH 20/60] Lua code format - (Stylua) --- data-otservbr-global/lib/quests/soul_war.lua | 60 ++++++++++++++----- .../moveevent-claustrophobic-inferno-raid.lua | 24 ++++++-- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 06ecc85fe04..942609d61bb 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -136,9 +136,18 @@ SoulWarQuest = { }, 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), + 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 = Position(34009, 31083, 9), endEvent = nil, @@ -160,9 +169,18 @@ SoulWarQuest = { }, zone = Zone("raid.second-claustrophobic-inferno"), spawns = { - Position(33999, 31046, 10), Position(34011, 31047, 10), Position(34005, 31052, 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), + Position(33999, 31046, 10), + Position(34011, 31047, 10), + Position(34005, 31052, 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 = Position(34011, 31028, 10), endEvent = nil, @@ -187,11 +205,25 @@ SoulWarQuest = { }, zone = Zone("raid.third-claustrophobic-inferno"), spawns = { - Position(34005, 31049, 11), Position(33999, 31051, 11), Position(33995, 31055, 11), Position(33995, 31055, 11), - Position(34001, 31069, 11), Position(33999, 31068, 11), Position(34016, 31068, 11), Position(34029, 31071, 11), - Position(34030, 31070, 11), Position(34038, 31066, 11), Position(34038, 31051, 11), Position(34033, 31051, 11), - Position(34025, 31048, 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), + Position(34005, 31049, 11), + Position(33999, 31051, 11), + Position(33995, 31055, 11), + Position(33995, 31055, 11), + Position(34001, 31069, 11), + Position(33999, 31068, 11), + Position(34016, 31068, 11), + Position(34029, 31071, 11), + Position(34030, 31070, 11), + Position(34038, 31066, 11), + Position(34038, 31051, 11), + Position(34033, 31051, 11), + Position(34025, 31048, 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 = Position(34014, 31085, 11), endEvent = nil, @@ -810,9 +842,9 @@ SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 3396 -- Initialize claustrophobic inferno raid zones -SoulWarQuest.raids[1].zone:addArea({ x = 33985, y = 31053, z = 9}, { x = 34045, y = 31077, z = 9}) -SoulWarQuest.raids[2].zone:addArea({ x = 33988, y = 31042, z = 10}, { x = 34043, y = 31068, z = 10}) -SoulWarQuest.raids[3].zone:addArea({ x = 33987, y = 31043, z = 11}, { x = 34044, y = 31076, z = 11}) +SoulWarQuest.raids[1].zone:addArea({ x = 33985, y = 31053, z = 9 }, { x = 34045, y = 31077, z = 9 }) +SoulWarQuest.raids[2].zone:addArea({ x = 33988, y = 31042, z = 10 }, { x = 34043, y = 31068, z = 10 }) +SoulWarQuest.raids[3].zone:addArea({ x = 33987, y = 31043, z = 11 }, { x = 34044, y = 31076, z = 11 }) -- Initialize safe areas (should not spawn monster, teleport, take damage from taint, etc) SoulWarQuest.safeZones.ebbAndFlow:addArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua index ea1d004b99a..944bd0dbda8 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -13,7 +13,9 @@ local function createTeleportEffect(position) end local function spawnMonsters(raidNumber) - if not SoulWarQuest.raids[raidNumber].timerStarted then return end + if not SoulWarQuest.raids[raidNumber].timerStarted then + return + end for _, spawnPosition in pairs(SoulWarQuest.raids[raidNumber].spawns) do addEvent(createTeleportEffect, 1000, spawnPosition) addEvent(createTeleportEffect, 2000, spawnPosition) @@ -31,7 +33,9 @@ local function kickPlayers(zone, raidNumber) end local function endRaid(zone, raidNumber) - if SoulWarQuest.raids[raidNumber].spawnEvent then stopEvent(SoulWarQuest.raids[raidNumber].spawnEvent) end + if SoulWarQuest.raids[raidNumber].spawnEvent then + stopEvent(SoulWarQuest.raids[raidNumber].spawnEvent) + end for _, monster in pairs(zone:getMonsters()) do if not monster:getMaster() then monster:getPosition():sendMagicEffect(CONST_ME_POFF) @@ -43,7 +47,9 @@ local function endRaid(zone, raidNumber) end local function raid(zone, raidNumber) - if SoulWarQuest.raids[raidNumber].timerStarted then return end + if SoulWarQuest.raids[raidNumber].timerStarted then + return + end logger.debug("Claustrophobic Inferno Raid #{} started", raidNumber) SoulWarQuest.raids[raidNumber].toggleTimer() SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) @@ -51,19 +57,25 @@ local function raid(zone, raidNumber) end function firstRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then return true end + if not creature:getPlayer() then + return true + end raid(SoulWarQuest.raids[firstRaidNumber].getZone(), firstRaidNumber) return true end function secondRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then return true end + if not creature:getPlayer() then + return true + end raid(SoulWarQuest.raids[secondRaidNumber].getZone(), secondRaidNumber) return true end function thirdRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then return true end + if not creature:getPlayer() then + return true + end raid(SoulWarQuest.raids[thirdRaidNumber].getZone(), thirdRaidNumber) return true end From 5c7f1214fcb055a9dd82b419d8f5fa27e4253ed5 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Alves Cruz Date: Mon, 13 May 2024 05:42:05 -0300 Subject: [PATCH 21/60] fix: dont start raid when player exits the raid zones changes: - Added checkage to not start raid when player pass on sandtimer from raid zones --- .../moveevent-claustrophobic-inferno-raid.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua index 944bd0dbda8..53684b1052e 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -46,11 +46,14 @@ local function endRaid(zone, raidNumber) logger.debug("Claustrophobic Inferno Raid #{} ended", raidNumber) end -local function raid(zone, raidNumber) +local function raid(zone, raidNumber, position, fromPosition) + 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 if SoulWarQuest.raids[raidNumber].timerStarted then return end - logger.debug("Claustrophobic Inferno Raid #{} started", raidNumber) + logger.warn("Claustrophobic Inferno Raid #{} started", raidNumber) SoulWarQuest.raids[raidNumber].toggleTimer() SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) SoulWarQuest.raids[raidNumber].endEvent = addEvent(endRaid, SoulWarQuest.raids.suriviveTime * 1000, zone, raidNumber) @@ -60,7 +63,7 @@ function firstRaid.onStepIn(creature, item, position, fromPosition) if not creature:getPlayer() then return true end - raid(SoulWarQuest.raids[firstRaidNumber].getZone(), firstRaidNumber) + raid(SoulWarQuest.raids[firstRaidNumber].getZone(), firstRaidNumber, position, fromPosition) return true end @@ -68,7 +71,7 @@ function secondRaid.onStepIn(creature, item, position, fromPosition) if not creature:getPlayer() then return true end - raid(SoulWarQuest.raids[secondRaidNumber].getZone(), secondRaidNumber) + raid(SoulWarQuest.raids[secondRaidNumber].getZone(), secondRaidNumber, position, fromPosition) return true end @@ -76,7 +79,7 @@ function thirdRaid.onStepIn(creature, item, position, fromPosition) if not creature:getPlayer() then return true end - raid(SoulWarQuest.raids[thirdRaidNumber].getZone(), thirdRaidNumber) + raid(SoulWarQuest.raids[thirdRaidNumber].getZone(), thirdRaidNumber, position, fromPosition) return true end From 4c7402db7217887a7d0a2ea630eddb679db063c9 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Alves Cruz Date: Sun, 26 May 2024 17:10:01 -0300 Subject: [PATCH 22/60] improvement: claustrophobic inferno raids and safe zones --- data-otservbr-global/lib/quests/soul_war.lua | 79 ++++------- .../eventcallback_on_combat_taint.lua | 2 +- .../moveevent-claustrophobic-inferno-raid.lua | 133 ++++++------------ .../soul_war/moveevent-soul_war_entrances.lua | 3 - src/game/game.cpp | 1 + 5 files changed, 77 insertions(+), 141 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 942609d61bb..b70f42aa85e 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -117,15 +117,7 @@ SoulWarQuest = { { from = Position(33856, 31884, 5), to = Position(33857, 31865, 6), access = "third-floor-access", count = 70 }, }, - safeZones = { - claustrophobicInferno = Zone("safe.claustrophobic-inferno"), - mirroredNightmare = Zone("safe.mirrored-nightmare"), - ebbAndFlow = Zone("safe.ebb-and-flow"), - furiousCrater = Zone("safe.furious-crater"), - rottenWasteland = Zone("safe.rotten-wasteland"), - }, - - raids = { + claustrophobicInfernoRaids = { [1] = { timerStarted = false, sandTimerPositions = { @@ -149,15 +141,12 @@ SoulWarQuest = { Position(34039, 31065, 9), Position(34032, 31072, 9), }, - exitPosition = Position(34009, 31083, 9), - endEvent = nil, - kickEvent = nil, - spawnEvent = nil, + exitPosition = { x = 34009, y = 31083, z = 9 }, getZone = function() - return SoulWarQuest.raids[1].zone + return SoulWarQuest.claustrophobicInfernoRaids[1].zone end, toggleTimer = function() - SoulWarQuest.raids[1].timerStarted = not SoulWarQuest.raids[1].timerStarted + SoulWarQuest.claustrophobicInfernoRaids[1].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[1].timerStarted end, }, [2] = { @@ -182,15 +171,12 @@ SoulWarQuest = { Position(33998, 31061, 10), Position(34005, 31052, 10), }, - exitPosition = Position(34011, 31028, 10), - endEvent = nil, - kickEvent = nil, - spawnEvent = nil, + exitPosition = { x = 34011, y = 31028, z = 10 }, getZone = function() - return SoulWarQuest.raids[2].zone + return SoulWarQuest.claustrophobicInfernoRaids[2].zone end, toggleTimer = function() - SoulWarQuest.raids[2].timerStarted = not SoulWarQuest.raids[2].timerStarted + SoulWarQuest.claustrophobicInfernoRaids[2].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[2].timerStarted end, }, [3] = { @@ -225,15 +211,12 @@ SoulWarQuest = { Position(34007, 31063, 11), Position(34004, 31059, 11), }, - exitPosition = Position(34014, 31085, 11), - endEvent = nil, - kickEvent = nil, - spawnEvent = nil, + exitPosition = { x = 34014, y = 31085, z = 11 }, getZone = function() - return SoulWarQuest.raids[3].zone + return SoulWarQuest.claustrophobicInfernoRaids[3].zone end, toggleTimer = function() - SoulWarQuest.raids[3].timerStarted = not SoulWarQuest.raids[3].timerStarted + SoulWarQuest.claustrophobicInfernoRaids[3].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[3].timerStarted end, }, spawnTime = 10, -- seconds @@ -842,20 +825,15 @@ SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 3396 -- Initialize claustrophobic inferno raid zones -SoulWarQuest.raids[1].zone:addArea({ x = 33985, y = 31053, z = 9 }, { x = 34045, y = 31077, z = 9 }) -SoulWarQuest.raids[2].zone:addArea({ x = 33988, y = 31042, z = 10 }, { x = 34043, y = 31068, z = 10 }) -SoulWarQuest.raids[3].zone:addArea({ x = 33987, y = 31043, z = 11 }, { x = 34044, y = 31076, z = 11 }) - --- Initialize safe areas (should not spawn monster, teleport, take damage from taint, etc) -SoulWarQuest.safeZones.ebbAndFlow:addArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) - -SoulWarQuest.safeZones.claustrophobicInferno:addArea({ x = 34002, y = 31008, z = 9 }, { x = 34019, y = 31019, z = 9 }) +SoulWarQuest.claustrophobicInfernoRaids[1].zone:addArea({ x = 33985, y = 31053, z = 9 }, { x = 34045, y = 31077, z = 9 }) +SoulWarQuest.claustrophobicInfernoRaids[2].zone:addArea({ x = 33988, y = 31042, z = 10 }, { x = 34043, y = 31068, z = 10 }) +SoulWarQuest.claustrophobicInfernoRaids[3].zone:addArea({ x = 33987, y = 31043, z = 11 }, { x = 34044, y = 31076, z = 11 }) -SoulWarQuest.safeZones.furiousCrater:addArea({ x = 33854, y = 31828, z = 3 }, { x = 33869, y = 31834, z = 3 }) +-- Add remove destination -SoulWarQuest.safeZones.rottenWasteland:addArea({ x = 33967, y = 31037, z = 11 }, { x = 33977, y = 31051, z = 11 }) - -SoulWarQuest.safeZones.mirroredNightmare:addArea({ x = 33884, y = 31181, z = 10 }, { x = 33892, y = 31198, z = 10 }) +SoulWarQuest.claustrophobicInfernoRaids[1].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[1].exitPosition) +SoulWarQuest.claustrophobicInfernoRaids[2].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[2].exitPosition) +SoulWarQuest.claustrophobicInfernoRaids[3].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[3].exitPosition) -- Initialize bosses access for taint check SoulWarQuest.areaZones.claustrophobicInferno:addArea({ x = 33982, y = 30981, z = 9 }, { x = 34051, y = 31110, z = 11 }) @@ -868,6 +846,17 @@ SoulWarQuest.areaZones.rottenWasteland:addArea({ x = 33980, y = 30986, z = 11 }, 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 @@ -1216,7 +1205,7 @@ function Monster:tryTeleportToPlayer(sayMessage) for i, spectator in ipairs(spectators) do if spectator:isPlayer() then local player = spectator:getPlayer() - if player:getTaintNameByNumber(1, true) and not player:isInSafeZone() then + if player:getTaintNameByNumber(1, true) and player:getSoulWarZoneMonster() ~= nil then local distance = self:getPosition():getDistance(player:getPosition()) if distance > maxDistance then maxDistance = distance @@ -1400,16 +1389,6 @@ function Monster:goshnarsDefenseIncrease(kvName) end end -function Player:isInSafeZone() - for zoneName, zone in pairs(SoulWarQuest.safeZones) do - if zone and zone:isInZone(self:getPosition()) then - return true - end - end - - return false -end - function Player:getSoulWarZoneMonster() local zoneMonsterName = nil for zoneName, monsterName in pairs(SoulWarQuest.areaZones.monsters) do diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua index 936ab8502aa..3e5ec34f930 100644 --- a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua @@ -111,7 +111,7 @@ function callback.playerOnThink(player, interval) if accumulatedTime[playerId] >= 10000 then local soulWarQuest = player:soulWarQuestKV() - if player:getSoulWarZoneMonster() ~= nil and not player:isInSafeZone() and player:getTaintNameByNumber(5) ~= nil then + 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) diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua index 53684b1052e..4f0cac5a3f0 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -4,97 +4,56 @@ local thirdRaid = MoveEvent() local spawnMonsterName = "Brachiodemon" -local firstRaidNumber = 1 -local secondRaidNumber = 2 -local thirdRaidNumber = 3 - -local function createTeleportEffect(position) - position:sendMagicEffect(CONST_ME_TELEPORT) -end - -local function spawnMonsters(raidNumber) - if not SoulWarQuest.raids[raidNumber].timerStarted then - return - end - for _, spawnPosition in pairs(SoulWarQuest.raids[raidNumber].spawns) do - addEvent(createTeleportEffect, 1000, spawnPosition) - addEvent(createTeleportEffect, 2000, spawnPosition) - addEvent(createTeleportEffect, 3000, spawnPosition) - addEvent(Game.createMonster, 4000, spawnMonsterName, spawnPosition, true, true) - end - SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) -end - -local function kickPlayers(zone, raidNumber) - SoulWarQuest.raids[raidNumber].toggleTimer() - for _, player in pairs(zone:getPlayers()) do - player:teleportTo(SoulWarQuest.raids[raidNumber].exitPosition) - end -end - -local function endRaid(zone, raidNumber) - if SoulWarQuest.raids[raidNumber].spawnEvent then - stopEvent(SoulWarQuest.raids[raidNumber].spawnEvent) - end - for _, monster in pairs(zone:getMonsters()) do - if not monster:getMaster() then - monster:getPosition():sendMagicEffect(CONST_ME_POFF) - monster:remove() +-- 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 - end - SoulWarQuest.raids[raidNumber].kickEvent = addEvent(kickPlayers, SoulWarQuest.raids.timeToKick * 1000, zone, raidNumber) - logger.debug("Claustrophobic Inferno Raid #{} ended", raidNumber) -end - -local function raid(zone, raidNumber, position, fromPosition) - 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 - if SoulWarQuest.raids[raidNumber].timerStarted then - return - end - logger.warn("Claustrophobic Inferno Raid #{} started", raidNumber) - SoulWarQuest.raids[raidNumber].toggleTimer() - SoulWarQuest.raids[raidNumber].spawnEvent = addEvent(spawnMonsters, SoulWarQuest.raids.spawnTime * 1000, raidNumber) - SoulWarQuest.raids[raidNumber].endEvent = addEvent(endRaid, SoulWarQuest.raids.suriviveTime * 1000, zone, raidNumber) -end - -function firstRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then + 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 - raid(SoulWarQuest.raids[firstRaidNumber].getZone(), firstRaidNumber, position, fromPosition) - return true -end -function secondRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then - return true + for _, pos in pairs(raid.sandTimerPositions) do + raidMoveEvent:position(pos) end - raid(SoulWarQuest.raids[secondRaidNumber].getZone(), secondRaidNumber, position, fromPosition) - return true -end -function thirdRaid.onStepIn(creature, item, position, fromPosition) - if not creature:getPlayer() then - return true - end - raid(SoulWarQuest.raids[thirdRaidNumber].getZone(), thirdRaidNumber, position, fromPosition) - return true + raidMoveEvent:register() end - -for _, pos in pairs(SoulWarQuest.raids[firstRaidNumber].sandTimerPositions) do - firstRaid:position(pos) -end - -for _, pos in pairs(SoulWarQuest.raids[secondRaidNumber].sandTimerPositions) do - secondRaid:position(pos) -end - -for _, position in pairs(SoulWarQuest.raids[thirdRaidNumber].sandTimerPositions) do - thirdRaid:position(position) -end - -firstRaid:register() -secondRaid:register() -thirdRaid:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua index a6792315882..0d2bd4fffec 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua @@ -88,9 +88,6 @@ soul_war_megalomania_entrance:position({ x = 33611, y = 31430, z = 10 }) soul_war_megalomania_entrance:register() local claustrophobicInfernoTeleportPositions = { - [Position(34013, 31049, 9)] = Position(34014, 31058, 9), - [Position(34010, 31073, 10)] = Position(34012, 31063, 10), - [Position(34009, 31038, 11)] = Position(34012, 31047, 11), [Position(34022, 31091, 11)] = Position(33685, 31599, 14), } diff --git a/src/game/game.cpp b/src/game/game.cpp index 11c3e2bf966..2dbcafe69b1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1128,6 +1128,7 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = size_t i = 0; for (const auto &spectator : playersSpectators) { if (const auto &player = spectator->getPlayer()) { + player->sendMagicEffect(tilePosition, CONST_ME_POFF); player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } From f84006e869cb18768ac8168043d5cc8bf50529f6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 26 May 2024 20:11:27 +0000 Subject: [PATCH 23/60] Lua code format - (Stylua) --- .../moveevent-claustrophobic-inferno-raid.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua index 4f0cac5a3f0..0502d53e365 100644 --- a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -10,19 +10,21 @@ for raidNumber, raid in ipairs(SoulWarQuest.claustrophobicInfernoRaids) do local raidName = string.format("Claustrophobic Inferno Raid %d", raidNumber) local encounter = Encounter(raidName, { zone = raid.getZone(), - timeToSpawnMonsters = "3s" + timeToSpawnMonsters = "3s", }) - local spawnTimes = (SoulWarQuest.claustrophobicInfernoRaids.suriviveTime) / SoulWarQuest.claustrophobicInfernoRaids.spawnTime + 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) + encounter + :addSpawnMonsters({ + { + name = spawnMonsterName, + positions = raid.spawns, + }, + }) + :autoAdvance(SoulWarQuest.claustrophobicInfernoRaids.spawnTime * 1000) end function encounter:onReset(position) From b4152a537d273230251aa50b60e8f9e15cff5618 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Alves Cruz Date: Sun, 26 May 2024 22:21:50 -0300 Subject: [PATCH 24/60] fix: removed duplicated spawns and some improvements --- data-otservbr-global/lib/quests/soul_war.lua | 45 ++++++++------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index b70f42aa85e..899038c3fe3 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -119,7 +119,10 @@ SoulWarQuest = { claustrophobicInfernoRaids = { [1] = { - timerStarted = false, + 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 }, @@ -145,12 +148,12 @@ SoulWarQuest = { getZone = function() return SoulWarQuest.claustrophobicInfernoRaids[1].zone end, - toggleTimer = function() - SoulWarQuest.claustrophobicInfernoRaids[1].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[1].timerStarted - end, }, [2] = { - timerStarted = false, + 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 }, @@ -160,7 +163,6 @@ SoulWarQuest = { spawns = { Position(33999, 31046, 10), Position(34011, 31047, 10), - Position(34005, 31052, 10), Position(34015, 31052, 10), Position(34021, 31044, 10), Position(34029, 31054, 10), @@ -175,12 +177,12 @@ SoulWarQuest = { getZone = function() return SoulWarQuest.claustrophobicInfernoRaids[2].zone end, - toggleTimer = function() - SoulWarQuest.claustrophobicInfernoRaids[2].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[2].timerStarted - end, }, [3] = { - timerStarted = false, + 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 }, @@ -194,16 +196,12 @@ SoulWarQuest = { Position(34005, 31049, 11), Position(33999, 31051, 11), Position(33995, 31055, 11), - Position(33995, 31055, 11), - Position(34001, 31069, 11), Position(33999, 31068, 11), Position(34016, 31068, 11), - Position(34029, 31071, 11), Position(34030, 31070, 11), Position(34038, 31066, 11), Position(34038, 31051, 11), Position(34033, 31051, 11), - Position(34025, 31048, 11), Position(34025, 31049, 11), Position(34013, 31058, 11), Position(34021, 31059, 11), @@ -215,9 +213,6 @@ SoulWarQuest = { getZone = function() return SoulWarQuest.claustrophobicInfernoRaids[3].zone end, - toggleTimer = function() - SoulWarQuest.claustrophobicInfernoRaids[3].timerStarted = not SoulWarQuest.claustrophobicInfernoRaids[3].timerStarted - end, }, spawnTime = 10, -- seconds suriviveTime = 2 * 60, -- 2 minutes @@ -823,17 +818,13 @@ 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 - -SoulWarQuest.claustrophobicInfernoRaids[1].zone:addArea({ x = 33985, y = 31053, z = 9 }, { x = 34045, y = 31077, z = 9 }) -SoulWarQuest.claustrophobicInfernoRaids[2].zone:addArea({ x = 33988, y = 31042, z = 10 }, { x = 34043, y = 31068, z = 10 }) -SoulWarQuest.claustrophobicInfernoRaids[3].zone:addArea({ x = 33987, y = 31043, z = 11 }, { x = 34044, y = 31076, z = 11 }) +-- Initialize claustrophobic inferno raid zones and add remove destination --- Add remove destination - -SoulWarQuest.claustrophobicInfernoRaids[1].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[1].exitPosition) -SoulWarQuest.claustrophobicInfernoRaids[2].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[2].exitPosition) -SoulWarQuest.claustrophobicInfernoRaids[3].zone:setRemoveDestination(SoulWarQuest.claustrophobicInfernoRaids[3].exitPosition) +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 }) From d37fdff1482d2e510c07dc9a25771c99ff4de3e1 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 14 Jun 2024 22:36:39 -0300 Subject: [PATCH 25/60] fix: eventcallback and flickering soul --- data-otservbr-global/npc/flickering_soul.lua | 8 ++++++-- data/scripts/talkactions/gm/afk.lua | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data-otservbr-global/npc/flickering_soul.lua b/data-otservbr-global/npc/flickering_soul.lua index a00df70e338..2010198da6e 100644 --- a/data-otservbr-global/npc/flickering_soul.lua +++ b/data-otservbr-global/npc/flickering_soul.lua @@ -182,9 +182,13 @@ local function playerSayCallback(npc, player, type, message) end npcHandler:say(message, npc, player) elseif MsgContains(message, "taints") or MsgContains(message, "penalties") then - if player:getTaintLevel() == nil then - player:resetTaints() + if player:getTaintLevel() ~= nil then + player:resetTaints(true) + npcHandler:say("I have cleansed you from the taints that you carried with you. You are now free from the burden that you should not have to bear.", npc, player) + return end + + npcHandler:say("You are not tainted by the darkness of the world. You are pure and free from the burdens that others carry.", npc, player) end return true end diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua index 6167a2b6068..f5342362113 100644 --- a/data/scripts/talkactions/gm/afk.lua +++ b/data/scripts/talkactions/gm/afk.lua @@ -69,7 +69,7 @@ afkEffect:interval(5000) afkEffect:register() ------------------ Stop AFK Message when moves ------------------ -local callback = EventCallback() +local callback = EventCallback("PlayerOnWalk") function callback.playerOnWalk(player, creature, creaturePos, toPos) local isAfk = checkIsAFK(player:getId()) if isAfk.afk then From 9fc0f114987de36712acee93550ce96c8730c312 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 15 Jun 2024 00:01:56 -0300 Subject: [PATCH 26/60] fix: megalomania drop for bag you desire --- data-otservbr-global/lib/quests/soul_war.lua | 41 ++++++++++++------- .../soul_war/goshnar's_megalomania_blue.lua | 1 - .../soul_war/goshnar's_megalomania_green.lua | 1 - .../quests/soul_war/goshnars_cruelty.lua | 1 - .../quests/soul_war/goshnars_greed.lua | 1 - .../quests/soul_war/goshnars_hatred.lua | 1 - .../quests/soul_war/goshnars_malice.lua | 1 - .../quests/soul_war/goshnars_spite.lua | 1 - 8 files changed, 26 insertions(+), 22 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 899038c3fe3..319d731faf7 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -51,13 +51,12 @@ SoulWarQuest = { "Sorcerer's Apparition", "Turbulent Elemental", "Vibrant Phantom.", - }, - bagYouDesireBosses = { "Goshnar's Cruelty", "Goshnar's Spite", "Goshnar's Malice", "Goshnar's Hatred", "Goshnar's Greed", + "Goshnar's Megalomania", }, -- Goshnar's Cruelty pulsating energy monsters @@ -1068,33 +1067,45 @@ end function Monster:generateBagYouDesireLoot(player) local playerTaintLevel = player:getTaintLevel() - if not playerTaintLevel then + if not playerTaintLevel or playerTaintLevel == 0 then return {} end local monsterName = self:getName() - local isMonsterValid = false - for _, monster in ipairs(SoulWarQuest.bagYouDesireMonsters) do - if monsterName == monster then - isMonsterValid = true - break - end - end - + local isMonsterValid = table.contains(SoulWarQuest.bagYouDesireMonsters, monsterName) if not isMonsterValid then return {} end - -- Calculates the chances based on the number of taints - local totalChance = SoulWarQuest.baseBagYouDesireChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) - logger.trace("Player {} killed {} and has {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) - -- Generate loot local loot = {} + local totalChance = SoulWarQuest.baseBagYouDesireChance + 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 + totalChance = totalChance + megalomaniaKills * SoulWarQuest.bagYouDesireChancePerTaint + else + -- General handling for other monsters (bosses and non-bosses) + totalChance = totalChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) + end + + logger.trace("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) + if math.random(1, 100) <= totalChance then local itemType = ItemType(SoulWarQuest.bagYouDesireItemId) if itemType then loot[itemType:getId()] = { count = 1 } logger.debug("Player {} killed {} and got a bag you desire with drop chance {}", player:getName(), monsterName, totalChance) + if monsterName == "Goshnar's Megalomania" then + -- Reset kill count on successful drop + soulWarQuest:scoped("megalomania-kills"):set("count", 0) + end + end + else + if monsterName == "Goshnar's Megalomania" then + -- Increment kill count for unsuccessful attempts + soulWarQuest:scoped("megalomania-kills"):set("count", megalomaniaKills + 1) end end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 69bd51a6923..232018a285e 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -94,7 +94,6 @@ monster.loot = { { name = "figurine of megalomania", chance = 400 }, { name = "megalomania's skull", chance = 400 }, { name = "megalomania's essence", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index 8a8d3d0d43f..08fed30648c 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -99,7 +99,6 @@ monster.loot = { { name = "figurine of megalomania", chance = 400 }, { name = "megalomania's skull", chance = 400 }, { name = "megalomania's essence", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 90a0235225b..2142f4527ec 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -95,7 +95,6 @@ monster.loot = { { name = "figurine of cruelty", chance = 400 }, { name = "spectral saddle", chance = 400 }, { name = "spectral horse tack", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index bd0e064a6b4..2c68f56770d 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -100,7 +100,6 @@ monster.loot = { { name = "greed's arm", chance = 25000, maxCount = 1 }, { name = "figurine of greed", chance = 400 }, { name = "the skull of a beast", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index 09970df24fe..023dadbd2d4 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -96,7 +96,6 @@ monster.loot = { { name = "spectral horseshoe", chance = 400 }, { name = "spectral horse tack", chance = 400 }, { name = "bracelet of strengthening", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index e0b054dc10d..6931add8bd5 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -97,7 +97,6 @@ monster.loot = { { name = "spectral horseshoe", chance = 400 }, { name = "the skull of a beast", chance = 400 }, { name = "figurine of malice", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 40817c335b0..9e04448fa19 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -93,7 +93,6 @@ monster.loot = { { name = "the skull of a beast", chance = 400 }, { name = "figurine of spite", chance = 400 }, { name = "spite's spirit", chance = 400 }, - { name = "bag you desire", chance = 100 }, } monster.attacks = { From 43afc714758260cf940e6169eb7d1e3b8058ab29 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 26 Jun 2024 17:42:46 +0000 Subject: [PATCH 27/60] Code format - (Clang-format) --- src/creatures/monsters/monster.cpp | 8 ++++---- src/lua/callbacks/event_callback.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 8e43f752bf9..0ffe2309caf 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -363,8 +363,8 @@ void Monster::onAttackedByPlayer(std::shared_ptr attackerPlayer) { LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " - "script calls being nested.", - getName(), this->getName()); + "script calls being nested.", + getName(), this->getName()); return; } @@ -390,8 +390,8 @@ void Monster::onSpawn() { LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " - "script calls being nested.", - getName(), this->getName()); + "script calls being nested.", + getName(), this->getName()); return; } diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 10796abd8e0..8280bad6d2b 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -236,9 +236,9 @@ void EventCallback::creatureOnDrainHealth(std::shared_ptr creature, st void EventCallback::creatureOnCombat(std::shared_ptr attacker, std::shared_ptr target, CombatDamage &damage) const { if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[{} - " - "Creature {} target {}] " - "Call stack overflow. Too many lua script calls being nested.", - __FUNCTION__, attacker->getName(), target->getName()); + "Creature {} target {}] " + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__, attacker->getName(), target->getName()); return; } @@ -270,7 +270,7 @@ void EventCallback::creatureOnCombat(std::shared_ptr attacker, std::sh damage.secondary.value = -damage.secondary.value; } /* - Only EK with dealing physical damage will get elemental damage on skill + Only EK with dealing physical damage will get elemental damage on skill */ if (damage.origin == ORIGIN_SPELL && attacker) { const auto &player = attacker->getPlayer(); @@ -1319,8 +1319,8 @@ void EventCallback::zoneAfterCreatureLeave(std::shared_ptr zone, std::shar void EventCallback::mapOnLoad(const std::string &mapFullPath) const { if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[{} - " - "Call stack overflow. Too many lua script calls being nested.", - __FUNCTION__); + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__); return; } From 4e2e53709b7f2bc9febe4c743b90961359f4c2c7 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Tue, 2 Jul 2024 15:43:21 -0300 Subject: [PATCH 28/60] fix build error. --- src/lua/callbacks/events_callbacks.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua/callbacks/events_callbacks.hpp b/src/lua/callbacks/events_callbacks.hpp index c479398fdcc..4d3758405a6 100644 --- a/src/lua/callbacks/events_callbacks.hpp +++ b/src/lua/callbacks/events_callbacks.hpp @@ -110,7 +110,7 @@ class EventsCallbacks { template ReturnValue checkCallbackWithReturnValue(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { ReturnValue res = RETURNVALUE_NOERROR; - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { ReturnValue callbackResult = std::apply( From d364e3279116fb1c8955427bd5f24a55ae3cb018 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 18 Jul 2024 12:05:24 +0000 Subject: [PATCH 29/60] Code format - (Clang-format) --- .../functions/creatures/player/player_functions.cpp | 2 +- .../functions/creatures/player/player_functions.hpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 9a830134737..702676ddd76 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4418,4 +4418,4 @@ int PlayerFunctions::luaPlayerSendCreatureAppear(lua_State* L) { player->sendCreatureAppear(player, player->getPosition(), isLogin); pushBoolean(L, true); return 1; -} \ No newline at end of file +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index f59f92fe1c4..550ecd76539 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -371,12 +371,12 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addTitle", PlayerFunctions::luaPlayerAddTitle); registerMethod(L, "Player", "getTitles", PlayerFunctions::luaPlayerGetTitles); registerMethod(L, "Player", "setCurrentTitle", PlayerFunctions::luaPlayerSetCurrentTitle); - - registerMethod(L, "Player", "createTransactionSummary", PlayerFunctions::luaPlayerCreateTransactionSummary); + + registerMethod(L, "Player", "createTransactionSummary", PlayerFunctions::luaPlayerCreateTransactionSummary); registerMethod(L, "Player", "takeScreenshot", PlayerFunctions::luaPlayerTakeScreenshot); - - registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); + + registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); GroupFunctions::init(L); GuildFunctions::init(L); @@ -741,8 +741,8 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerCreateTransactionSummary(lua_State* L); static int luaPlayerTakeScreenshot(lua_State* L); - - static int luaPlayerSendCreatureAppear(lua_State* L); + + static int luaPlayerSendCreatureAppear(lua_State* L); friend class CreatureFunctions; }; From 95f1c17622b91ecbdd1d214ce9c390457a0f14b9 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 1 Aug 2024 19:05:30 -0300 Subject: [PATCH 30/60] fix: crash related to lua callback --- src/creatures/combat/combat.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 7a19a292728..a0c5fb81ec6 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1222,7 +1222,9 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin if (CreatureVector* creatures = tile->getCreatures()) { const std::shared_ptr topCreature = tile->getTopCreature(); - for (auto &creature : *creatures) { + // A copy of the tile's creature list is made because modifications to this vector, such as adding or removing creatures through a Lua callback, may occur during the iteration within the for loop. + CreatureVector creaturesCopy = *creatures; + for (auto &creature : creaturesCopy) { if (params.targetCasterOrTopMost) { if (caster && caster->getTile() == tile) { if (creature != caster) { From be1179e9561899d55f55bc3e0d02dc87981b9b6b Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 2 Aug 2024 18:36:29 -0300 Subject: [PATCH 31/60] fix: addCallback check --- src/lua/callbacks/events_callbacks.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lua/callbacks/events_callbacks.cpp b/src/lua/callbacks/events_callbacks.cpp index 1ab64957497..4b147d4dd6f 100644 --- a/src/lua/callbacks/events_callbacks.cpp +++ b/src/lua/callbacks/events_callbacks.cpp @@ -39,10 +39,13 @@ bool EventsCallbacks::isCallbackRegistered(const std::shared_ptr } void EventsCallbacks::addCallback(const std::shared_ptr &callback) { - if (m_callbacks.find(callback->getName()) != m_callbacks.end()) { + if (m_callbacks.find(callback->getName()) != m_callbacks.end() && !callback->skipDuplicationCheck()) { + g_logger().error("Event callback already registered: {}", callback->getName()); return; } + g_logger().trace("Registering event callback: {}", callback->getName()); + m_callbacks[callback->getName()] = callback; } From 2e89beb6be82939e8098ff276c200080b98b506a Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 7 Aug 2024 19:52:04 -0300 Subject: [PATCH 32/60] fix: ZoneEvent callback --- data/libs/functions/boss_lever.lua | 5 +-- data/libs/systems/zones.lua | 2 +- data/scripts/talkactions/god/test.lua | 44 ++++++++++++++++++++++++++ src/lua/callbacks/events_callbacks.hpp | 1 + 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/data/libs/functions/boss_lever.lua b/data/libs/functions/boss_lever.lua index cbecfc26f8c..9cd577ee911 100644 --- a/data/libs/functions/boss_lever.lua +++ b/data/libs/functions/boss_lever.lua @@ -174,14 +174,15 @@ function BossLever:onUse(player) return true end - if creature:getLevel() < self.requiredLevel then + local isAccountNormal = creature:getAccountType() == ACCOUNT_TYPE_NORMAL + if isAccountNormal and creature:getLevel() < self.requiredLevel then local message = "All players need to be level " .. self.requiredLevel .. " or higher." creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) return false end - if creature:getGroup():getId() < GROUP_TYPE_GOD and self:lastEncounterTime(creature) > os.time() then + if creature:getGroup():getId() < GROUP_TYPE_GOD and isAccountNormal and self:lastEncounterTime(creature) > os.time() then local infoPositions = lever:getInfoPositions() for _, posInfo in pairs(infoPositions) do local currentPlayer = posInfo.creature diff --git a/data/libs/systems/zones.lua b/data/libs/systems/zones.lua index 1406242517a..698a464fe87 100644 --- a/data/libs/systems/zones.lua +++ b/data/libs/systems/zones.lua @@ -148,7 +148,7 @@ function ZoneEvent:register() end if self.onSpawn then - local afterEnter = EventCallback("ZoneEventAfterEnter", true) + local afterEnter = EventCallback("ZoneEventAfterEnterOnSpawn", true) function afterEnter.zoneAfterCreatureEnter(zone, creature) if zone ~= self.zone then return true diff --git a/data/scripts/talkactions/god/test.lua b/data/scripts/talkactions/god/test.lua index 25b2fd49da2..f22f86ff921 100644 --- a/data/scripts/talkactions/god/test.lua +++ b/data/scripts/talkactions/god/test.lua @@ -36,3 +36,47 @@ end testLog:separator(" ") testLog:groupType("god") testLog:register() + +local testIcons = TalkAction("/testicons") + +local function convertIconsToBitValue(iconList) + local bitObj = NewBit(0) + for icon in string.gmatch(iconList, "%d+") do + icon = tonumber(icon) + if icon then + local flag = bit.lshift(1, icon - 1) + bitObj:updateFlag(flag) + end + end + return bitObj:getNumber() +end + +--[[Usage: +/testicons 1 +/testicons 2 +/testicons 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +]] +function testIcons.onSay(player, words, param) + if param == "" then + player:sendCancelMessage("Icon required.") + logger.error("[testIcons.onSay] - Icon required") + return true + end + + function Player:sendIconsTest() + local msg = NetworkMessage() + msg:addByte(0xA2) + local icons = convertIconsToBitValue(param) + msg:addU32(icons) + msg:addByte(0) + msg:sendToPlayer(self) + end + + player:sendIconsTest() + return true +end + +testIcons:separator(" ") +testIcons:setDescription("[Usage]: /seticons {icon1}, {icon2}, {icon3}, ...") +testIcons:groupType("god") +testIcons:register() diff --git a/src/lua/callbacks/events_callbacks.hpp b/src/lua/callbacks/events_callbacks.hpp index 4d3758405a6..dff5cec30c5 100644 --- a/src/lua/callbacks/events_callbacks.hpp +++ b/src/lua/callbacks/events_callbacks.hpp @@ -97,6 +97,7 @@ class EventsCallbacks { }, argsCopy ); + g_logger().trace("Executed callback: {}", name); } } } From fb9a4a15517f6d5510a8a3a13a46dc3ef23b6404 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 7 Aug 2024 22:52:47 +0000 Subject: [PATCH 33/60] Lua code format - (Stylua) --- data/scripts/talkactions/god/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/talkactions/god/test.lua b/data/scripts/talkactions/god/test.lua index f22f86ff921..cd5e7e1aec9 100644 --- a/data/scripts/talkactions/god/test.lua +++ b/data/scripts/talkactions/god/test.lua @@ -45,7 +45,7 @@ local function convertIconsToBitValue(iconList) icon = tonumber(icon) if icon then local flag = bit.lshift(1, icon - 1) - bitObj:updateFlag(flag) + bitObj:updateFlag(flag) end end return bitObj:getNumber() From 7b2444ddac97396286c39c4c01fce4293cc6b351 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 7 Aug 2024 21:12:39 -0300 Subject: [PATCH 34/60] fix: maps bugs --- .../soul_war/ebb_and_flow/ebb-flow-empty.otbm | Bin 129560 -> 129452 bytes .../ebb_and_flow/ebb-flow-monster.xml | 3 +-- .../quest/soul_war/ebb_and_flow/ebb-flow.otbm | Bin 181546 -> 181627 bytes 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm index 6d37ae4fb880709f5c9facf23687f08eb776fb95..9d3fa897425709c4d254f1ac0b598f26e2cbe0bd 100644 GIT binary patch delta 100 zcmbR7g?-Iu_6@A(H_M;Ti{HLnk8zXM^x{ZH_385?8AG@KjAVQuuJy|I|39{Fj{pDv ou~tv`$MzYCJMkaeTVJrmBoJ#lZxQ1SPLR6kAnx`bMT`pV0Po;4jQ{`u delta 211 zcmZ4UnSI6=_6@A(rT+h8VQ68fvX-QRTN6>oIQ9 zLQ+uDjH+O|Y9ymN%Rjbmj_KZ!jA3Bb_63oQ55-NoIe^x%R!#iJ_SzT3s-6H+24(@p e-unKBb0@*M(^nTV-VlaMOovNs? - - + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm index b8ba1414d09e6bdea0cf36d80dfe9f1b39ab8e17..5a8d5d16fefe7f43e2bd163cb1f86eac75068fc0 100644 GIT binary patch delta 435 zcmZ40#r?aByI~9CpwQ_#{b(V zIWUPcdw^`2%Bc-@X=INB?8urxC)DAeAtWR!!s7L2#y@ab~ishnNO& z`!uK@rUPlP+dZ4V2)1X|9@<{(^Vaq z^q86&rbjt2=`v|IPVaPJ(%!z)fk~WM8Sb#DoZ1MtOye{}aHdPRFxh3p9WoWkA(hjR zIn%+MK8ORRfE}O(;Yg(;;*F8!Zf!50BG=* Al>h($ From 60e6102b9e486b76e6a36424d954eb46f4f57f4a Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 8 Aug 2024 00:19:55 -0300 Subject: [PATCH 35/60] fix: some things --- data-otservbr-global/world/otservbr-house.xml | 3 +- .../world/otservbr-monster.xml | 6 -- data-otservbr-global/world/otservbr-npc.xml | 60 +++++++++---------- data-otservbr-global/world/otservbr-zones.xml | 2 +- src/creatures/combat/combat.cpp | 4 +- src/lua/callbacks/events_callbacks.cpp | 2 +- 6 files changed, 37 insertions(+), 40 deletions(-) diff --git a/data-otservbr-global/world/otservbr-house.xml b/data-otservbr-global/world/otservbr-house.xml index f7e5cd52370..6acaadf5bd9 100644 --- a/data-otservbr-global/world/otservbr-house.xml +++ b/data-otservbr-global/world/otservbr-house.xml @@ -284,7 +284,7 @@ - + @@ -984,4 +984,5 @@ + diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 8441ed918a1..bde660e0c60 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -96181,7 +96181,6 @@ - @@ -118064,11 +118063,6 @@ - - - - - diff --git a/data-otservbr-global/world/otservbr-npc.xml b/data-otservbr-global/world/otservbr-npc.xml index 271d28c46ab..8bc05fa7f15 100644 --- a/data-otservbr-global/world/otservbr-npc.xml +++ b/data-otservbr-global/world/otservbr-npc.xml @@ -840,6 +840,9 @@ + + + @@ -1551,6 +1554,9 @@ + + + @@ -1932,6 +1938,9 @@ + + + @@ -2106,6 +2115,9 @@ + + + @@ -2115,6 +2127,9 @@ + + + @@ -2331,6 +2346,18 @@ + + + + + + + + + + + + @@ -2379,18 +2406,6 @@ - - - - - - - - - - - - @@ -2848,6 +2863,9 @@ + + + @@ -2983,22 +3001,4 @@ - - - - - - - - - - - - - - - - - - diff --git a/data-otservbr-global/world/otservbr-zones.xml b/data-otservbr-global/world/otservbr-zones.xml index 4740d50385c..44cff897a42 100644 --- a/data-otservbr-global/world/otservbr-zones.xml +++ b/data-otservbr-global/world/otservbr-zones.xml @@ -1,4 +1,4 @@ - + diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index a0c5fb81ec6..d65d10e4e6f 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1167,7 +1167,9 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin if (CreatureVector* creatures = tile->getCreatures()) { const std::shared_ptr topCreature = tile->getTopCreature(); - for (auto &creature : *creatures) { + // A copy of the tile's creature list is made because modifications to this vector, such as adding or removing creatures through a Lua callback, may occur during the iteration within the for loop. + CreatureVector creaturesCopy = *creatures; + for (auto &creature : creaturesCopy) { if (params.targetCasterOrTopMost) { if (caster && caster->getTile() == tile) { if (creature != caster) { diff --git a/src/lua/callbacks/events_callbacks.cpp b/src/lua/callbacks/events_callbacks.cpp index 4b147d4dd6f..13a42baa15a 100644 --- a/src/lua/callbacks/events_callbacks.cpp +++ b/src/lua/callbacks/events_callbacks.cpp @@ -40,7 +40,7 @@ bool EventsCallbacks::isCallbackRegistered(const std::shared_ptr void EventsCallbacks::addCallback(const std::shared_ptr &callback) { if (m_callbacks.find(callback->getName()) != m_callbacks.end() && !callback->skipDuplicationCheck()) { - g_logger().error("Event callback already registered: {}", callback->getName()); + g_logger().trace("Event callback already registered: {}", callback->getName()); return; } From 16c20870bdf25ba0c4cb36f385cff52123374559 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 8 Aug 2024 00:29:53 -0300 Subject: [PATCH 36/60] fix: remove house --- data-otservbr-global/world/otservbr-house.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/data-otservbr-global/world/otservbr-house.xml b/data-otservbr-global/world/otservbr-house.xml index 6acaadf5bd9..7eff23b4606 100644 --- a/data-otservbr-global/world/otservbr-house.xml +++ b/data-otservbr-global/world/otservbr-house.xml @@ -984,5 +984,4 @@ - From 5c5470a02144cb42304927d774885902625a8fa9 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 8 Aug 2024 01:42:18 -0300 Subject: [PATCH 37/60] fix: change bag you desire chance --- data-otservbr-global/lib/quests/soul_war.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 319d731faf7..100788d9acb 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -30,7 +30,7 @@ SoulWarQuest = { timeToReturnImmuneMegalomania = 70, -- In seconds - baseBagYouDesireChance = 1, -- 1% base chance + baseBagYouDesireChance = 500, -- 1000 = 1% chance, 500 = 0.5% chance bagYouDesireChancePerTaint = 1, -- Increases 1% per taint bagYouDesireMonsters = { "Bony Sea Devil", @@ -1092,7 +1092,7 @@ function Monster:generateBagYouDesireLoot(player) logger.trace("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) - if math.random(1, 100) <= totalChance then + if math.random(1, 100000) <= totalChance then local itemType = ItemType(SoulWarQuest.bagYouDesireItemId) if itemType then loot[itemType:getId()] = { count = 1 } From 784f6719f3a5b31dc33aa840a110c63484d4540c Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 8 Aug 2024 09:18:53 -0300 Subject: [PATCH 38/60] feat: add new icon --- data/scripts/talkactions/god/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/talkactions/god/test.lua b/data/scripts/talkactions/god/test.lua index cd5e7e1aec9..ac6ca921483 100644 --- a/data/scripts/talkactions/god/test.lua +++ b/data/scripts/talkactions/god/test.lua @@ -68,7 +68,7 @@ function testIcons.onSay(player, words, param) msg:addByte(0xA2) local icons = convertIconsToBitValue(param) msg:addU32(icons) - msg:addByte(0) + msg:addByte(tonumber(param)) msg:sendToPlayer(self) end From e337caeed3f1df7170d3599f43d098ffa15d9491 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 9 Aug 2024 02:07:50 -0300 Subject: [PATCH 39/60] fix: goshnar's greed bugs --- data-otservbr-global/lib/quests/soul_war.lua | 2 +- .../quests/soul_war/goshnars_greed.lua | 5 +-- .../monster/quests/soul_war/powerful_soul.lua | 2 +- .../monster/quests/soul_war/soul_sphere.lua | 41 ++++++++++++------- src/game/game.cpp | 2 +- .../creatures/creature_functions.cpp | 2 +- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 100788d9acb..5df73dc6115 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -37,7 +37,7 @@ SoulWarQuest = { "Brachiodemon", "Branchy Crawler", "Capricious Phantom", - "Cloak Of Terror", + "Cloak of Terror", "Courage Leech", "Distorted Phantom", "Druid's Apparition", diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 2c68f56770d..4f197ad9541 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -68,10 +68,7 @@ monster.light = { } monster.summon = { - maxSummons = 1, - summons = { - { name = "dreadful harvester", chance = 10, interval = 1000, count = 1 }, - }, + maxSummons = 1 } monster.voices = { diff --git a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua index cee228f7185..4174abd5413 100644 --- a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua @@ -101,7 +101,7 @@ mType.onThink = function(monster, interval) boss:addReflectElement(elementType, reflectPercent) end boss:addDefense(10) - boss:setMaxHealth(boss:getHealth() + 10000) + boss:setMaxHealth(boss:getMaxHealth() + 10000) boss:addHealth(10000) end transformTimeCount = 0 diff --git a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua index c0142e3e611..5cf6560132f 100644 --- a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua +++ b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua @@ -84,26 +84,39 @@ monster.immunities = { } local moveTimeCount = 0 +local stop = false mType.onThink = function(monster, interval) + if stop then + return + end + moveTimeCount = moveTimeCount + interval - if moveTimeCount == 3000 then - monster:move(DIRECTION_WEST) - moveTimeCount = 0 - local monsterPos = monster:getPosition() - local nextPos = Position(monsterPos.x - 1, monsterPos.y, monsterPos.z) - local tile = Tile(nextPos) - if not tile then - return - end + if moveTimeCount >= 3000 then + local currentPos = monster:getPosition() + local newPos = Position(currentPos.x - 1, currentPos.y, currentPos.z) - for _, creatureId in pairs(tile:getCreatures()) do - local monster = Monster(creatureId) - if monster and monster:getName() == "Goshnar's Greed" then - monster:setHealth(monster:getMaxHealth()) - break + local nextTile = Tile(newPos) + if nextTile then + for _, creatureId in pairs(nextTile:getCreatures()) do + local greedMonster = Monster(creatureId) + if greedMonster and greedMonster:getName() == "Goshnar's Greed" then + greedMonster:setHealth(greedMonster:getMaxHealth()) + stop = true + return + end end end + + if not stop then + monster:teleportTo(newPos, true) + moveTimeCount = 0 + end end end +mType.onSpawn = function(monster) + moveTimeCount = 0 + stop = false +end + mType:register(monster) diff --git a/src/game/game.cpp b/src/game/game.cpp index f3415b0cb7e..ee49b9cc7cc 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1487,7 +1487,7 @@ ReturnValue Game::internalMoveCreature(std::shared_ptr creature, Direc return RETURNVALUE_NOTPOSSIBLE; } - if (creature->getBaseSpeed() == 0 && flags != FLAG_IGNORENOTMOVABLE) { + if (creature->getBaseSpeed() == 0) { return RETURNVALUE_NOTMOVABLE; } diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index da935d05fdc..6b09945971a 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -968,7 +968,7 @@ int CreatureFunctions::luaCreatureMove(lua_State* L) { lua_pushnil(L); return 1; } - lua_pushnumber(L, g_game().internalMoveCreature(creature, direction, FLAG_IGNORENOTMOVABLE)); + lua_pushnumber(L, g_game().internalMoveCreature(creature, direction, FLAG_NOLIMIT)); } else { std::shared_ptr tile = getUserdataShared(L, 2); if (!tile) { From bfbcf19504603b0bf43b57e981102ccac7060a3f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 9 Aug 2024 05:08:29 +0000 Subject: [PATCH 40/60] Lua code format - (Stylua) --- data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 4f197ad9541..2ec3a528eb5 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -68,7 +68,7 @@ monster.light = { } monster.summon = { - maxSummons = 1 + maxSummons = 1, } monster.voices = { From 46da501195b92d62188550fdd5eb254d91cd1320 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 9 Aug 2024 10:46:32 -0300 Subject: [PATCH 41/60] fix: soul ghosts looktype --- data-otservbr-global/monster/quests/soul_war/powerful_soul.lua | 2 +- data-otservbr-global/monster/quests/soul_war/strong_soul.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua index 4174abd5413..0ece602eab5 100644 --- a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua @@ -4,7 +4,7 @@ local monster = {} monster.description = "a powerful soul" monster.experience = 0 monster.outfit = { - lookType = 48, + lookType = 568, lookHead = 0, lookBody = 0, lookLegs = 0, diff --git a/data-otservbr-global/monster/quests/soul_war/strong_soul.lua b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua index 3fb844d1f97..ae3615b6eb4 100644 --- a/data-otservbr-global/monster/quests/soul_war/strong_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua @@ -4,7 +4,7 @@ local monster = {} monster.description = "a strong soul" monster.experience = 0 monster.outfit = { - lookType = 48, + lookType = 566, lookHead = 0, lookBody = 0, lookLegs = 0, From a55a2b5381151727b4e837fc45adfcbb4e965ccc Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 9 Aug 2024 14:17:45 -0300 Subject: [PATCH 42/60] fix: final conflicts --- data/scripts/talkactions/god/test.lua | 44 --------------------------- src/enums/player_icons.hpp | 10 +++--- 2 files changed, 5 insertions(+), 49 deletions(-) diff --git a/data/scripts/talkactions/god/test.lua b/data/scripts/talkactions/god/test.lua index ac6ca921483..25b2fd49da2 100644 --- a/data/scripts/talkactions/god/test.lua +++ b/data/scripts/talkactions/god/test.lua @@ -36,47 +36,3 @@ end testLog:separator(" ") testLog:groupType("god") testLog:register() - -local testIcons = TalkAction("/testicons") - -local function convertIconsToBitValue(iconList) - local bitObj = NewBit(0) - for icon in string.gmatch(iconList, "%d+") do - icon = tonumber(icon) - if icon then - local flag = bit.lshift(1, icon - 1) - bitObj:updateFlag(flag) - end - end - return bitObj:getNumber() -end - ---[[Usage: -/testicons 1 -/testicons 2 -/testicons 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -]] -function testIcons.onSay(player, words, param) - if param == "" then - player:sendCancelMessage("Icon required.") - logger.error("[testIcons.onSay] - Icon required") - return true - end - - function Player:sendIconsTest() - local msg = NetworkMessage() - msg:addByte(0xA2) - local icons = convertIconsToBitValue(param) - msg:addU32(icons) - msg:addByte(tonumber(param)) - msg:sendToPlayer(self) - end - - player:sendIconsTest() - return true -end - -testIcons:separator(" ") -testIcons:setDescription("[Usage]: /seticons {icon1}, {icon2}, {icon3}, ...") -testIcons:groupType("god") -testIcons:register() diff --git a/src/enums/player_icons.hpp b/src/enums/player_icons.hpp index c289144bd7b..7878d9e5037 100644 --- a/src/enums/player_icons.hpp +++ b/src/enums/player_icons.hpp @@ -35,11 +35,11 @@ enum class PlayerIcon : uint8_t { GreaterHex = 18, Rooted = 19, Feared = 20, - Goshnar1 = 21, - Goshnar2 = 22, - Goshnar3 = 23, - Goshnar4 = 24, - Goshnar5 = 25, + GoshnarTaint1 = 21, + GoshnarTaint2 = 22, + GoshnarTaint3 = 23, + GoshnarTaint4 = 24, + GoshnarTaint5 = 25, NewManaShield = 26, Agony = 27, From f4a8e547fc28cf20d6bc56ee48c91b35465da664 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 9 Aug 2024 14:55:35 -0300 Subject: [PATCH 43/60] fix: register missing stairs --- data/items/items.xml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/data/items/items.xml b/data/items/items.xml index c898c235f8d..a4750514e26 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -74771,12 +74771,31 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + + + + + + + + + @@ -74811,6 +74830,22 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + + + + + + @@ -74829,6 +74864,15 @@ Granted by TibiaGoals.com"/> + + + + + + + + + From a9b5c9529beaee5264146766189bab05c24b73b8 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 00:07:14 -0300 Subject: [PATCH 44/60] fix: goshnar's cruelty and greed mechanic --- data-otservbr-global/lib/quests/soul_war.lua | 6 ++++-- .../quests/soul_war/goshnars_cruelty.lua | 18 +++++++++++----- .../quests/soul_war/goshnars_greed.lua | 16 ++++++++++++-- .../monster/quests/soul_war/soul_sphere.lua | 6 +++--- .../quests/soul_war/soul_war_mechanics.lua | 21 +++++++++++++++++++ src/config/configmanager.cpp | 2 +- 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 5df73dc6115..009fe7f31ba 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -371,6 +371,7 @@ SoulWarQuest = { 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, }, @@ -1379,9 +1380,10 @@ function Monster:goshnarsDefenseIncrease(kvName) 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 - logger.trace("{} old defense {}", self:getName(), self:getDefense()) self:addDefense(SoulWarQuest.goshnarsCrueltyDefenseChange) - logger.trace("{} new defense {}", self:getName(), self:getDefense()) + -- 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) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 2142f4527ec..300a47184e3 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -15,6 +15,7 @@ monster.outfit = { monster.events = { "SoulWarBossesDeath", + "GoshnarsCrueltyBuff", } monster.health = 300000 @@ -134,16 +135,23 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster) - monster:goshnarsDefenseIncrease("greedy-maw-action") +local firstTime = 0 +mType.onThink = function(monster, interval) + firstTime = firstTime + interval + -- Run only 15 seconds before creation + if firstTime >= 15000 then + monster:goshnarsDefenseIncrease("greedy-maw-action") + end end mType.onAppear = function(monster, creature) end -mType.onSpawn = function(monster) - if monster:getType():isRewardBoss() then - monster:setReward(true) +mType.onSpawn = function(monsterCallback) + if monsterCallback:getType():isRewardBoss() then + monsterCallback:setReward(true) end + + firstTime = 0 end mType.onDisappear = function(monster, creature) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 2ec3a528eb5..7c6110f4592 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -138,7 +138,7 @@ local immuneTimeCount = 0 local isImmune = nil local createdSoulSphere = nil mType.onThink = function(monsterCallback, interval) - if GreedbeastKills == 5 and isImmune == nil then + if GreedbeastKills >= 5 and isImmune == nil then isImmune = monsterCallback:immune(false) monsterCallback:teleportTo(Position(33741, 31659, 14)) monsterCallback:setSpeed(0) @@ -166,12 +166,24 @@ mType.onSpawn = function(monster) monster:setReward(true) end + isImmune = nil monster:immune(true) immuneTimeCount = 0 GreedbeastKills = 0 end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Greedbeast" then + logger.debug("GreedbeastKills {}", GreedbeastKills) + end + if creature:getName() == "Goshnar's Greed" then + logger.debug("Killed goshnar's greed") + if createdSoulSphere then + logger.debug("Found soul sphere, remove it") + createdSoulSphere:remove() + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua index 5cf6560132f..160879ba9db 100644 --- a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua +++ b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua @@ -98,9 +98,9 @@ mType.onThink = function(monster, interval) local nextTile = Tile(newPos) if nextTile then for _, creatureId in pairs(nextTile:getCreatures()) do - local greedMonster = Monster(creatureId) - if greedMonster and greedMonster:getName() == "Goshnar's Greed" then - greedMonster:setHealth(greedMonster:getMaxHealth()) + local tileMonster = Monster(creatureId) + if tileMonster and tileMonster:getName() == "Goshnar's Greed" then + tileMonster:setHealth(tileMonster:getMaxHealth()) stop = true return end diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index dc7197176fa..f326455fdd9 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -880,6 +880,11 @@ function greedyMaw.onUse(player, item, fromPosition, target, toPosition, isHotke 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 @@ -1066,3 +1071,19 @@ for _, pos in pairs(teleportPositions) do 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/src/config/configmanager.cpp b/src/config/configmanager.cpp index 6390d0699b3..80126ddac93 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -379,7 +379,7 @@ bool ConfigManager::reload() { } void ConfigManager::missingConfigWarning(const char* identifier) { - g_logger().warn("[{}]: Missing configuration for identifier: {}", __FUNCTION__, identifier); + g_logger().debug("[{}]: Missing configuration for identifier: {}", __FUNCTION__, identifier); } std::string ConfigManager::loadStringConfig(lua_State* L, const ConfigKey_t &key, const char* identifier, const std::string &defaultValue) { From 7622cfdfe4bf7df76ca555fb3dcdf45dc66f97c0 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 00:07:29 -0300 Subject: [PATCH 45/60] fix: reset taint correctly --- data-otservbr-global/lib/quests/soul_war.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 009fe7f31ba..0c92ddc0c7d 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1196,7 +1196,15 @@ function Player:resetTaints(skipCheckTime) end self:resetTaintConditions() soulWarQuest:remove("firstTaintTime") - self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your goshnar's taints have been reset. You didn't finish the quest in 14 days") + 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 From 3a836e9be6be076c0845c0545517dfb8caa1382d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 00:21:06 -0300 Subject: [PATCH 46/60] fix: remove duplicated script --- .../actions_portal_reward_soulwar.lua | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua b/data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua deleted file mode 100644 index 7aaff112056..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua +++ /dev/null @@ -1,23 +0,0 @@ -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() From 3aab8a31794986ed2b3f1d015b495b72a31a81cf Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 00:30:47 -0300 Subject: [PATCH 47/60] fix: revert conflict --- data-otservbr-global/startup/tables/item.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/startup/tables/item.lua b/data-otservbr-global/startup/tables/item.lua index 790090f417b..e2f94a88d18 100644 --- a/data-otservbr-global/startup/tables/item.lua +++ b/data-otservbr-global/startup/tables/item.lua @@ -1811,7 +1811,7 @@ ItemUnique = { -- Path: data\scripts\actions\quests\the_rookie_guard\mission06_run_like_wolf.lua -- Poacher corpse [40044] = { - itemId = 3204, + itemId = 111, itemPos = { x = 32135, y = 32133, z = 8 }, }, -- War wolf corpse From 631c6c31e7e23379c23340b2412f385b5bb02a6d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 10 Sep 2024 03:35:01 +0000 Subject: [PATCH 48/60] fix: indent --- src/lua/functions/creatures/player/player_functions.cpp | 1 + src/lua/functions/creatures/player/player_functions.hpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 111f2a12c99..421ceefcb0d 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4411,6 +4411,7 @@ int PlayerFunctions::luaPlayerTakeScreenshot(lua_State* L) { pushBoolean(L, true); return 1; } + int PlayerFunctions::luaPlayerSendIconBakragore(lua_State* L) { // player:sendIconBakragore() const auto &player = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index d75cc8aa29f..aa4db5857f7 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -372,11 +372,12 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "getTitles", PlayerFunctions::luaPlayerGetTitles); registerMethod(L, "Player", "setCurrentTitle", PlayerFunctions::luaPlayerSetCurrentTitle); + // Store Summary registerMethod(L, "Player", "createTransactionSummary", PlayerFunctions::luaPlayerCreateTransactionSummary); registerMethod(L, "Player", "takeScreenshot", PlayerFunctions::luaPlayerTakeScreenshot); registerMethod(L, "Player", "sendIconBakragore", PlayerFunctions::luaPlayerSendIconBakragore); - registerMethod(L, "Player", "removeIconBakragore", PlayerFunctions::luaPlayerRemoveIconBakragore); + registerMethod(L, "Player", "removeIconBakragore", PlayerFunctions::luaPlayerRemoveIconBakragore); registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); GroupFunctions::init(L); From 4b9816c73809dba5cd0483b1dd8d928c8f27ddc6 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 09:30:02 -0300 Subject: [PATCH 49/60] fix: mirror image change to table and remove xml files --- .../monster/quests/soul_war/mirror_image.lua | 12 +- .../world/otservbr-monster.xml | 15 + .../ebb_and_flow/ebb-flow-empty-house.xml | 2 - .../ebb_and_flow/ebb-flow-empty-monster.xml | 2 - .../ebb_and_flow/ebb-flow-empty-npc.xml | 2 - .../ebb_and_flow/ebb-flow-empty-zones.xml | 2 - .../soul_war/ebb_and_flow/ebb-flow-house.xml | 2 - .../ebb_and_flow/ebb-flow-inundate-house.xml | 2 - .../ebb-flow-inundate-monster.xml | 2 - .../ebb_and_flow/ebb-flow-inundate-npc.xml | 2 - .../ebb_and_flow/ebb-flow-inundate-zones.xml | 2 - .../ebb_and_flow/ebb-flow-monster.xml | 540 ------------------ .../soul_war/ebb_and_flow/ebb-flow-npc.xml | 2 - .../soul_war/ebb_and_flow/ebb-flow-zones.xml | 2 - 14 files changed, 18 insertions(+), 571 deletions(-) delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml diff --git a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua index ceacde54c26..600550d3d78 100644 --- a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua +++ b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua @@ -114,12 +114,6 @@ mType.onPlayerAttack = function(monster, attackerPlayer) logger.info("Player {}, attacking monster {}", attackerPlayer:getName(), monster:getName()) local apparitionType = "" - local apparitionTypes = { - "Druid's Apparition", - "Knight's Apparition", - "Paladin's Apparition", - "Sorcerer's Apparition", - } local sameVocationProbability = 70 -- 70% chance for create monster of first player attack vocation if attackerPlayer:isDruid() then @@ -134,9 +128,9 @@ mType.onPlayerAttack = function(monster, attackerPlayer) if math.random(100) > sameVocationProbability then repeat - local randomIndex = math.random(#apparitionTypes) - if apparitionTypes[randomIndex] ~= apparitionType then - apparitionType = apparitionTypes[randomIndex] + local randomIndex = math.random(#SoulWarQuest.apparitionNames) + if SoulWarQuest.apparitionNames[randomIndex] ~= apparitionType then + apparitionType = SoulWarQuest.apparitionNames[randomIndex] break end until false diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index ef003deee1a..308d9631af9 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -162156,6 +162156,9 @@ + + + @@ -162168,12 +162171,18 @@ + + + + + + @@ -162185,6 +162194,9 @@ + + + @@ -162201,6 +162213,9 @@ + + + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml deleted file mode 100644 index e5a6b86118e..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-house.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml deleted file mode 100644 index 8704e6da3b2..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-monster.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml deleted file mode 100644 index af7984e2faf..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-npc.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml deleted file mode 100644 index a9224bd3c2d..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty-zones.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml deleted file mode 100644 index e5a6b86118e..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-house.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml deleted file mode 100644 index e5a6b86118e..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-house.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml deleted file mode 100644 index 8704e6da3b2..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-monster.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml deleted file mode 100644 index af7984e2faf..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-npc.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml deleted file mode 100644 index a9224bd3c2d..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate-zones.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml deleted file mode 100644 index af4787bae03..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-monster.xml +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml deleted file mode 100644 index af7984e2faf..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-npc.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml deleted file mode 100644 index a9224bd3c2d..00000000000 --- a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-zones.xml +++ /dev/null @@ -1,2 +0,0 @@ - - From 5a0f4a7e356f62738830ebf801b2cdae3bae382b Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 09:30:28 -0300 Subject: [PATCH 50/60] fix: remove weeping soul corpses on boss spawn --- data-otservbr-global/lib/quests/soul_war.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 0c92ddc0c7d..c739d181764 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -267,6 +267,22 @@ SoulWarQuest = { 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 }, From 419579832f063cdf4d7213dae019f53886bae982 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 09:58:36 -0300 Subject: [PATCH 51/60] fix: remove outfit massive fire elemental --- data-otservbr-global/lib/quests/soul_war.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index c739d181764..25c6981df6b 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1503,6 +1503,8 @@ function Zone:getRandomPlayer() return players[randomIndex] end +local conditionOutfit = Condition(CONDITION_OUTFIT) + local function delayedCastSpell(cid, var, combat, targetId) local creature = Creature(cid) if not creature then @@ -1512,6 +1514,7 @@ local function delayedCastSpell(cid, var, combat, targetId) local target = Player(targetId) if target then combat:execute(creature, positionToVariant(target:getPosition())) + target:removeCondition(conditionOutfit) end end @@ -1532,10 +1535,10 @@ function Creature:applyZoneEffect(var, combat, zoneName) return true end - local condition = Condition(CONDITION_OUTFIT) - condition:setTicks(outfitConfig.time) - condition:setOutfit(outfitConfig.outfit) - target:addCondition(condition) + + 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()) From a231525e940027b87abed11ab18455aaad65c972 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 09:58:44 -0300 Subject: [PATCH 52/60] fix spawns --- .../scripts/quests/soul_war/soul_war_mechanics.lua | 2 +- data-otservbr-global/world/otservbr-monster.xml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index f326455fdd9..8f91f72c0d2 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -972,7 +972,7 @@ function cleansedSanity.onUse(player, item, fromPosition, target, toPosition, is 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 by 2 every " .. timeToIncreaseDefense .. " seconds.") + 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() diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 308d9631af9..529c6b470ac 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -34097,6 +34097,9 @@ + + + @@ -34129,6 +34132,9 @@ + + + @@ -34214,6 +34220,9 @@ + + + From 5d9e835937119373e13ae656cb0847d4647f81c9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 10 Sep 2024 12:59:31 +0000 Subject: [PATCH 53/60] fix: stop mechanics on bosses death --- data-otservbr-global/lib/quests/soul_war.lua | 18 ++++++++++++++-- .../quests/soul_war/goshnars_cruelty.lua | 9 +++++++- .../quests/soul_war/goshnars_hatred.lua | 11 +++++++++- .../quests/soul_war/soul_war_mechanics.lua | 11 ++-------- .../world/otservbr-monster.xml | 21 +++++++++++++++++++ 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 25c6981df6b..c6cc517d48a 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -436,6 +436,13 @@ SoulWarQuest = { { 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 @@ -1293,7 +1300,7 @@ end function Monster:increaseHatredDamageMultiplier(multiplierCount) local attackMultiplier = self:getHatredDamageMultiplier() - self:getSoulWarKV():set("burning-hatred-empowered", attackMultiplier + multiplierCount or 10) + self:getSoulWarKV():set("burning-hatred-empowered", attackMultiplier + multiplierCount) end function Monster:resetHatredDamageMultiplier() @@ -1346,6 +1353,12 @@ function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetwee for i = 1, #spectators do local player = spectators[i] local tormentCounter = player:getGoshnarSymbolTormentCounter() + local goshnarsHatred = Creature("Goshnar's Hatred") + 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) @@ -1373,6 +1386,8 @@ function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetwee elseif tormentCounter == 36 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The dread's torment is now lethal!") end + + ::continue:: end end @@ -1535,7 +1550,6 @@ function Creature:applyZoneEffect(var, combat, zoneName) return true end - conditionOutfit:setTicks(outfitConfig.time) conditionOutfit:setOutfit(outfitConfig.outfit) target:addCondition(conditionOutfit) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 300a47184e3..f42d09e89dd 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -154,7 +154,14 @@ mType.onSpawn = function(monsterCallback) firstTime = 0 end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Goshnar's Cruelty" then + local eyeCreature = Creature("A Greedy Eye") + if eyeCreature then + eyeCreature:remove() + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index 023dadbd2d4..89f00c899eb 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -145,7 +145,16 @@ mType.onSpawn = function(monster) monster:resetHatredDamageMultiplier() end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Goshnar's Hatred" then + for _, monsterName in pairs(SoulWarQuest.burningHatredMonsters) do + local ashesCreature = Creature(monsterName) + if ashesCreature then + ashesCreature:remove() + end + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 8f91f72c0d2..5f8fd9276fe 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -552,13 +552,6 @@ end goshnarsHatredAccess:position(SoulWarQuest.goshnarsHatredAccessPosition.from) goshnarsHatredAccess:register() -local burningHatredMonsters = { - "Ashes of Burning Hatred", - "Spark of Burning Hatred", - "Flame of Burning Hatred", - "Blaze of Burning Hatred", -} - local goshnarsHatredSorrow = Action() function goshnarsHatredSorrow.onUse(player, item, fromPosition, target, toPosition, isHotkey) @@ -566,7 +559,7 @@ function goshnarsHatredSorrow.onUse(player, item, fromPosition, target, toPositi return end - if not table.contains(burningHatredMonsters, target:getName()) then + 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 @@ -610,7 +603,7 @@ function burningChangeForm.onThink(creature) local boss = Creature("Goshnar's Hatred") if boss then logger.debug("Increasing hatred damage multiplier.") - boss:increaseHatredDamageMultiplier() + boss:increaseHatredDamageMultiplier(10) end logger.debug("Beginning of the burning transformation cycle.") end diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 529c6b470ac..60450b7142b 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -162010,6 +162010,9 @@ + + + @@ -162036,6 +162039,9 @@ + + + @@ -162045,6 +162051,12 @@ + + + + + + @@ -162059,6 +162071,9 @@ + + + @@ -162071,6 +162086,9 @@ + + + @@ -162092,6 +162110,9 @@ + + + From 84e76484b144950f616c64c76965e370f156e638 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 11:39:31 -0300 Subject: [PATCH 54/60] fix: some goshnar's megalomania mechanics --- data-otservbr-global/lib/quests/soul_war.lua | 19 ++++++++++++++++++- .../soul_war/goshnar's_megalomania_blue.lua | 4 ++++ .../soul_war/goshnar's_megalomania_green.lua | 4 ++++ .../burning_hatred/symbol_of_hatred.lua | 2 +- .../quests/soul_war/soul_war_mechanics.lua | 3 +-- .../world/otservbr-monster.xml | 13 +++++++++++++ 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index c6cc517d48a..e21c23900cc 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1353,7 +1353,7 @@ function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetwee for i = 1, #spectators do local player = spectators[i] local tormentCounter = player:getGoshnarSymbolTormentCounter() - local goshnarsHatred = Creature("Goshnar's Hatred") + local goshnarsHatred = Creature(bossName or "Goshnar's Megalomania") if not goshnarsHatred then player:resetGoshnarSymbolTormentCounter() goto continue @@ -1432,6 +1432,23 @@ function Monster:goshnarsDefenseIncrease(kvName) end end +function Monster:removeGoshnarsMegalomaniaMonsters(monsterName) + if self:getName() ~= "Goshnar's Megalomania" then + return + end + + local zone = Zone.getByName("boss.goshnar's-megalomania-purple") + if zone then + logger.info("Removing all monsters from Goshnar's Megalomania zone") + 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 diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 232018a285e..2cd7f55239c 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -144,4 +144,8 @@ mType.onThink = function(monsterCallback, interval) monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") end +mType.onDisappear = function(monster, creature) + creature:removeGoshnarsMegalomaniaMonsters() +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index 08fed30648c..02067499b27 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -155,4 +155,8 @@ mType.onThink = function(monsterCallback, interval) monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") end +mType.onDisappear = function(monster, creature) + creature:removeGoshnarsMegalomaniaMonsters() +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua index 9df86b773a9..76d403fab1a 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua @@ -78,7 +78,7 @@ monster.immunities = { local intervalBetweenExecutions = 3000 mType.onThink = function(monsterCallback, interval) - monsterCallback:onThinkGoshnarTormentCounter(interval, 30, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsHatred.boss.position) + monsterCallback:onThinkGoshnarTormentCounter(interval, 30, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsHatred.boss.position, "Goshnar's Hatred") end mType:register(monster) diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua index 5f8fd9276fe..6a60195277a 100644 --- a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -922,11 +922,10 @@ 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!") - item:remove() return true end diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 60450b7142b..41bb280696e 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -162201,6 +162201,10 @@ + + + + @@ -162224,6 +162228,12 @@ + + + + + + @@ -162236,6 +162246,9 @@ + + + From 5b403e221aa1534bd7b4c56a5154eb1a39481c46 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 11:44:05 -0300 Subject: [PATCH 55/60] fix: zone param --- data-otservbr-global/lib/quests/soul_war.lua | 4 +--- .../monster/quests/soul_war/goshnar's_megalomania_blue.lua | 2 +- .../monster/quests/soul_war/goshnar's_megalomania_green.lua | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index e21c23900cc..1cacf4de08f 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1432,14 +1432,12 @@ function Monster:goshnarsDefenseIncrease(kvName) end end -function Monster:removeGoshnarsMegalomaniaMonsters(monsterName) +function Monster:removeGoshnarsMegalomaniaMonsters(zone) if self:getName() ~= "Goshnar's Megalomania" then return end - local zone = Zone.getByName("boss.goshnar's-megalomania-purple") if zone then - logger.info("Removing all monsters from Goshnar's Megalomania zone") local creatures = zone:getCreatures() for _, creature in ipairs(creatures) do if creature:getMonster() then diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 2cd7f55239c..93ecb6c4230 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -145,7 +145,7 @@ mType.onThink = function(monsterCallback, interval) end mType.onDisappear = function(monster, creature) - creature:removeGoshnarsMegalomaniaMonsters() + creature:removeGoshnarsMegalomaniaMonsters(zone) end mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index 02067499b27..6c7b16bd707 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -156,7 +156,7 @@ mType.onThink = function(monsterCallback, interval) end mType.onDisappear = function(monster, creature) - creature:removeGoshnarsMegalomaniaMonsters() + creature:removeGoshnarsMegalomaniaMonsters(zone) end mType:register(monster) From 89f2ad84f5cb9b666b60034d28313f743348c139 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 11:57:57 -0300 Subject: [PATCH 56/60] fix: move event location --- data-otservbr-global/lib/quests/soul_war.lua | 1 + .../monster/quests/soul_war/goshnar's_megalomania_purple.lua | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 1cacf4de08f..fa96ebd774a 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1401,6 +1401,7 @@ function Monster:increaseAspectOfPowerDeathCount() 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") diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua index c2530a43087..79914af4935 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua @@ -110,8 +110,6 @@ mType.onSpawn = function(monster) stopEvent(SoulWarQuest.changePurpleEvent) end - SoulWarQuest.changeBlueEvent = addEvent(SoulWarQuest.changeMegalomaniaBlue, 6 * 60 * 1000) - local bossKV = monster:getSoulWarKV() bossKV:set("aspect-of-power-death-count", 0) monster:resetHatredDamageMultiplier() From 2df6acee7ccbd7a779a51c134cc8b7c87dfecdd4 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 12:57:28 -0300 Subject: [PATCH 57/60] fix: add missing spawns --- data-otservbr-global/world/otservbr-monster.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index 41bb280696e..80c5f5d4ff3 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -162007,6 +162007,9 @@ + + + @@ -162039,6 +162042,12 @@ + + + + + + @@ -162110,6 +162119,9 @@ + + + From b5a9e79e948c6e00ff713ffdbafbec737d6e7bef Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 14:37:15 -0300 Subject: [PATCH 58/60] fix: bag you desire drop --- data-otservbr-global/lib/quests/soul_war.lua | 26 +++++++------------ .../soul_war/goshnar's_megalomania_blue.lua | 1 + .../soul_war/goshnar's_megalomania_green.lua | 1 + .../quests/soul_war/goshnars_cruelty.lua | 1 + .../quests/soul_war/goshnars_greed.lua | 1 + .../quests/soul_war/goshnars_hatred.lua | 1 + .../quests/soul_war/goshnars_malice.lua | 1 + .../quests/soul_war/goshnars_spite.lua | 1 + .../normal_monsters/bony_sea_devil.lua | 1 + .../soul_war/normal_monsters/brachiodemon.lua | 1 + .../normal_monsters/branchy_crawler.lua | 1 + .../normal_monsters/capricious_phantom.lua | 1 + .../normal_monsters/distorted_phantom.lua | 1 + .../normal_monsters/druid's_apparition.lua | 1 + .../furious_crater/cloak_of_terror.lua | 1 + .../furious_crater/courage_leech.lua | 1 + .../furious_crater/poor_soul.lua | 1 - .../furious_crater/vibrant_phantom.lua | 1 + .../normal_monsters/infernal_demon.lua | 1 + .../normal_monsters/infernal_phantom.lua | 1 + .../normal_monsters/knight's_apparition.lua | 1 + .../soul_war/normal_monsters/many_faces.lua | 1 + .../normal_monsters/mould_phantom.lua | 1 + .../normal_monsters/paladin's_apparition.lua | 1 + .../soul_war/normal_monsters/rotten_golem.lua | 1 + .../normal_monsters/sorcerer's_apparition.lua | 1 + .../normal_monsters/turbulent_elemental.lua | 1 + .../monster/undeads/hazardous_phantom.lua | 1 + ...ntcallback_on_drop_loot_bag_you_desire.lua | 16 ------------ data/libs/functions/monster.lua | 4 +-- data/libs/functions/monstertype.lua | 7 ++++- .../monster/ondroploot__base.lua | 2 +- .../monster/ondroploot_boosted.lua | 2 +- .../monster/ondroploot_hazard.lua | 2 +- .../monster/ondroploot_prey.lua | 2 +- .../monster/ondroploot_wealth_duplex.lua | 2 +- data/scripts/systems/reward_chest.lua | 4 +-- 37 files changed, 51 insertions(+), 43 deletions(-) delete mode 100644 data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index fa96ebd774a..1072ee519ab 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -30,8 +30,7 @@ SoulWarQuest = { timeToReturnImmuneMegalomania = 70, -- In seconds - baseBagYouDesireChance = 500, -- 1000 = 1% chance, 500 = 0.5% chance - bagYouDesireChancePerTaint = 1, -- Increases 1% per taint + bagYouDesireChancePerTaint = 10, -- Increases % per taint bagYouDesireMonsters = { "Bony Sea Devil", "Brachiodemon", @@ -1089,7 +1088,7 @@ function Monster:createSoulWarWhiteTiles(centerRoomPosition, zonePositions, exec addEvent(revertTilesAndApplyDamage, executeInterval or 3000, zonePositions) end -function Monster:generateBagYouDesireLoot(player) +function MonsterType:calculateBagYouDesireChance(player, itemChance) local playerTaintLevel = player:getTaintLevel() if not playerTaintLevel or playerTaintLevel == 0 then return {} @@ -1102,29 +1101,24 @@ function Monster:generateBagYouDesireLoot(player) end local loot = {} - local totalChance = SoulWarQuest.baseBagYouDesireChance 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 - totalChance = totalChance + megalomaniaKills * SoulWarQuest.bagYouDesireChancePerTaint + itemChance = itemChance + megalomaniaKills * SoulWarQuest.bagYouDesireChancePerTaint else -- General handling for other monsters (bosses and non-bosses) - totalChance = totalChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) + itemChance = itemChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) end - logger.trace("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, totalChance) + logger.info("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, itemChance) if math.random(1, 100000) <= totalChance then - local itemType = ItemType(SoulWarQuest.bagYouDesireItemId) - if itemType then - loot[itemType:getId()] = { count = 1 } - logger.debug("Player {} killed {} and got a bag you desire with drop chance {}", player:getName(), monsterName, totalChance) - if monsterName == "Goshnar's Megalomania" then - -- Reset kill count on successful drop - soulWarQuest:scoped("megalomania-kills"):set("count", 0) - end + 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 @@ -1133,7 +1127,7 @@ function Monster:generateBagYouDesireLoot(player) end end - return loot + return itemChance end local intervalBetweenExecutions = 10000 diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 93ecb6c4230..1433d2ed7d4 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -94,6 +94,7 @@ monster.loot = { { name = "figurine of megalomania", chance = 400 }, { name = "megalomania's skull", chance = 400 }, { name = "megalomania's essence", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index 6c7b16bd707..f6d60b0b747 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -99,6 +99,7 @@ monster.loot = { { name = "figurine of megalomania", chance = 400 }, { name = "megalomania's skull", chance = 400 }, { name = "megalomania's essence", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index f42d09e89dd..44f87cca574 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -96,6 +96,7 @@ monster.loot = { { name = "figurine of cruelty", chance = 400 }, { name = "spectral saddle", chance = 400 }, { name = "spectral horse tack", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 7c6110f4592..f1f5284398e 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -97,6 +97,7 @@ monster.loot = { { name = "greed's arm", chance = 25000, maxCount = 1 }, { name = "figurine of greed", chance = 400 }, { name = "the skull of a beast", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index 89f00c899eb..47fedea4e7c 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -96,6 +96,7 @@ monster.loot = { { name = "spectral horseshoe", chance = 400 }, { name = "spectral horse tack", chance = 400 }, { name = "bracelet of strengthening", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index 6931add8bd5..e0b054dc10d 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -97,6 +97,7 @@ monster.loot = { { name = "spectral horseshoe", chance = 400 }, { name = "the skull of a beast", chance = 400 }, { name = "figurine of malice", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 9e04448fa19..40817c335b0 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -93,6 +93,7 @@ monster.loot = { { name = "the skull of a beast", chance = 400 }, { name = "figurine of spite", chance = 400 }, { name = "spite's spirit", chance = 400 }, + { name = "bag you desire", chance = 100 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua index 95120bfb0d2..d6e4bcfcafe 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua @@ -100,6 +100,7 @@ monster.loot = { { name = "goblet of gloom", chance = 880 }, { name = "glacier kilt", chance = 880 }, { name = "glacial rod", chance = 1210 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua index dfb94760041..a76c9f40ee9 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua @@ -103,6 +103,7 @@ monster.loot = { { name = "mastermind shield", chance = 420 }, { name = "assassin dagger", chance = 340 }, { name = "alloy legs", chance = 170 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua index 87a491a29d9..f5dccd47eb5 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua @@ -98,6 +98,7 @@ monster.loot = { { name = "twiceslicer", chance = 420 }, { name = "crystalline sword", chance = 390 }, { name = "ruthless axe", chance = 330 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua index 15221cdaf0a..0b3bf5b8572 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua @@ -97,6 +97,7 @@ monster.loot = { { id = 23542, chance = 1180 }, -- collar of blue plasma { name = "glacial rod", chance = 940 }, { name = "ornate crossbow", chance = 940 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua index 12d78f77654..3f28aac16b9 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua @@ -96,6 +96,7 @@ monster.loot = { { name = "spellbook of warding", chance = 2890 }, { id = 23531, chance = 1930 }, -- ring of green plasma { name = "glacial rod", chance = 1290 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua index c0436e734a3..957307c40eb 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua @@ -94,6 +94,7 @@ monster.loot = { { name = "platinum amulet", chance = 1750 }, { name = "glacier robe", chance = 880 }, { id = 23544, chance = 440 }, -- collar of red plasma + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua index c5e440ef02e..ff25ab1f058 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua @@ -98,6 +98,7 @@ monster.loot = { { name = "blue gem", chance = 1490 }, { name = "brooch of embracement", chance = 1490 }, { name = "wand of defiance", chance = 990 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua index 4df38a95cc7..615e4637841 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua @@ -96,6 +96,7 @@ monster.loot = { { name = "stone skin amulet", chance = 910 }, { name = "nightmare blade", chance = 1190 }, { name = "demonrage sword", chance = 600 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua index 55f5ccb623f..316959ddcd8 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua @@ -84,7 +84,6 @@ monster.loot = { { id = 33932, chance = 3520 }, -- head { name = "glacial rod", chance = 620 }, { id = 34024, chance = 650 }, -- gruesome fan - { id = 34109, chance = 1 }, -- bag you desire } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua index f4ab16a5307..ae4c60df095 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua @@ -98,6 +98,7 @@ monster.loot = { { name = "violet crystal shard", chance = 1080 }, { id = 23529, chance = 1080 }, -- ring of blue plasma { name = "green gem", chance = 1080 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua index 9fa157fa7a0..0ec107c192f 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua @@ -98,6 +98,7 @@ monster.loot = { { name = "giant sword", chance = 2860 }, { name = "magma boots", chance = 2290 }, { name = "stone skin amulet", chance = 570 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua index 1704c7338e0..82b7fd2090a 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua @@ -99,6 +99,7 @@ monster.loot = { { name = "crystal mace", chance = 1610 }, { name = "war axe", chance = 1410 }, { name = "warrior's axe", chance = 1410 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua index b9be9209d0e..f763e9b4dfe 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua @@ -92,6 +92,7 @@ monster.loot = { { name = "giant sword", chance = 1720 }, { name = "stone skin amulet", chance = 1500 }, { name = "crown shield", chance = 640 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua index ad924df93ff..f60b999dbce 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua @@ -99,6 +99,7 @@ monster.loot = { { name = "glacier robe", chance = 2130 }, { name = "gruesome fan", chance = 610 }, { name = "glacial rod", chance = 610 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua index a2b38a76b90..1af17c877fa 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua @@ -97,6 +97,7 @@ monster.loot = { { id = 23529, chance = 1040 }, -- ring of blue plasma { name = "ornate crossbow", chance = 840 }, { name = "crystal crossbow", chance = 620 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua index ab9db4fe484..2dc7a49555c 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua @@ -95,6 +95,7 @@ monster.loot = { { name = "stone skin amulet", chance = 1560 }, { id = 23542, chance = 1250 }, -- collar of blue plasma { id = 23529, chance = 1250 }, -- ring of blue plasma + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua index f49c96d8b7d..f5d5808b05c 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua @@ -95,6 +95,7 @@ monster.loot = { { name = "stone skin amulet", chance = 740 }, { name = "terra mantle", chance = 510 }, { name = "rubber cap", chance = 430 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua index 63faa04b271..e554c27dfd9 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua @@ -96,6 +96,7 @@ monster.loot = { { name = "wand of starstorm", chance = 1310 }, { name = "stone skin amulet", chance = 1310 }, { name = "alloy legs", chance = 440 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua index 57cacb3ee76..6d7804f2207 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua @@ -95,6 +95,7 @@ monster.loot = { { name = "crystalline armor", chance = 710 }, { name = "rubber cap", chance = 710 }, { name = "stone skin amulet", chance = 470 }, + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/hazardous_phantom.lua b/data-otservbr-global/monster/undeads/hazardous_phantom.lua index f1ba2b8c335..1cd1eb7d38c 100644 --- a/data-otservbr-global/monster/undeads/hazardous_phantom.lua +++ b/data-otservbr-global/monster/undeads/hazardous_phantom.lua @@ -82,6 +82,7 @@ monster.loot = { { id = 282, chance = 1570 }, -- giant shimmering pearl { name = "wand of everblazing", chance = 790 }, { id = 23542, chance = 790 }, -- collar of blue plasma + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua deleted file mode 100644 index 04fe2035956..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_drop_loot_bag_you_desire.lua +++ /dev/null @@ -1,16 +0,0 @@ -local callback = EventCallback("MonsterOnDropBagYouDesire") - -function callback.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 - - corpse:addLoot(monster:generateBagYouDesireLoot(player)) -end - -callback:register() diff --git a/data/libs/functions/monster.lua b/data/libs/functions/monster.lua index f26f2a5b9b4..0327daab3b3 100644 --- a/data/libs/functions/monster.lua +++ b/data/libs/functions/monster.lua @@ -204,7 +204,7 @@ do return table.contains(equipmentTypes, t) end - function MonsterType.getBossReward(self, lootFactor, topScore, equipmentOnly, lootTable) + function MonsterType.getBossReward(self, lootFactor, topScore, equipmentOnly, lootTable, player) if configManager.getNumber(configKeys.RATE_LOOT) <= 0 then return lootTable or {} end @@ -221,6 +221,6 @@ do end return true end, - }, lootTable) + }, lootTable, player) end end diff --git a/data/libs/functions/monstertype.lua b/data/libs/functions/monstertype.lua index 168cab13109..a9f6fff59f7 100644 --- a/data/libs/functions/monstertype.lua +++ b/data/libs/functions/monstertype.lua @@ -1,7 +1,7 @@ -- return a dictionary of itemId => { count, gut } ---@param config { factor: number, gut: boolean, filter?: fun(itemType: ItemType, unique: boolean): boolean } ---@return LootItems -function MonsterType:generateLootRoll(config, resultTable) +function MonsterType:generateLootRoll(config, resultTable, player) if configManager.getNumber(configKeys.RATE_LOOT) <= 0 then return resultTable or {} end @@ -28,6 +28,11 @@ function MonsterType:generateLootRoll(config, resultTable) end local chance = item.chance + if iType:getId() == SoulWarQuest.bagYouDesireItemId then + result[item.itemId].chance = self:calculateBagYouDesireChance(player, chance) + logger.debug("Final chance for bag you desire: {}, original chance: {}", result[item.itemId].chance, chance) + end + if config.gut and iType:getType() == ITEM_TYPE_CREATUREPRODUCT then chance = math.ceil((chance * GLOBAL_CHARM_GUT) / 100) end diff --git a/data/scripts/eventcallbacks/monster/ondroploot__base.lua b/data/scripts/eventcallbacks/monster/ondroploot__base.lua index 900259d4fef..0f724be9f67 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot__base.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot__base.lua @@ -22,7 +22,7 @@ function callback.monsterOnDropLoot(monster, corpse) local charm = player and player:getCharmMonsterType(CHARM_GUT) local gut = charm and charm:raceId() == mType:raceId() - local lootTable = mType:generateLootRoll({ factor = factor, gut = gut }, {}) + local lootTable = mType:generateLootRoll({ factor = factor, gut = gut }, {}, player) corpse:addLoot(lootTable) for _, item in ipairs(lootTable) do if item.gut then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua index 507df354ab7..3bb43256772 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua @@ -22,7 +22,7 @@ function callback.monsterOnDropLoot(monster, corpse) local factor = 1.0 local msgSuffix = " (boosted loot)" - corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {})) + corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {}, player)) local existingSuffix = corpse:getAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX) or "" corpse:setAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX, existingSuffix .. msgSuffix) diff --git a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua index 92ae22482fd..78851d186e6 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua @@ -31,7 +31,7 @@ function callback.monsterOnDropLoot(monster, corpse) local lootTable = {} for _ = 1, rolls do - lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable) + lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable, player) end corpse:addLoot(lootTable) diff --git a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua index a0fce3d4457..eb4657ccc4f 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua @@ -42,7 +42,7 @@ function callback.monsterOnDropLoot(monster, corpse) msgSuffix = msgSuffix .. " (active prey bonus)" end - corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {})) + corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {}, player)) local existingSuffix = corpse:getAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX) or "" corpse:setAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX, existingSuffix .. msgSuffix) end diff --git a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua index ffba226956a..a4f3c67fe9c 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua @@ -59,7 +59,7 @@ function callback.monsterOnDropLoot(monster, corpse) local lootTable = {} for _ = 1, rolls do - lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable) + lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable, player) end corpse:addLoot(lootTable) diff --git a/data/scripts/systems/reward_chest.lua b/data/scripts/systems/reward_chest.lua index 384115af0b0..07ec9b512a9 100644 --- a/data/scripts/systems/reward_chest.lua +++ b/data/scripts/systems/reward_chest.lua @@ -103,9 +103,9 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn end local playerLoot = creature:generateGemAtelierLoot() - playerLoot = monsterType:getBossReward(lootFactor, _ == 1, false, playerLoot) + playerLoot = monsterType:getBossReward(lootFactor, _ == 1, false, playerLoot, player) for _ = 2, rolls do - playerLoot = monsterType:getBossReward(lootFactor, false, true, playerLoot) + playerLoot = monsterType:getBossReward(lootFactor, false, true, playerLoot, player) end -- Add droped items to reward container From e763b835dab7a41df16d794e8abfbd9a4685297c Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 14:42:29 -0300 Subject: [PATCH 59/60] fix: missing monster and remove "." --- data-otservbr-global/lib/quests/soul_war.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 1072ee519ab..4b48d3be9d8 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -49,7 +49,8 @@ SoulWarQuest = { "Rotten Golem", "Sorcerer's Apparition", "Turbulent Elemental", - "Vibrant Phantom.", + "Vibrant Phantom", + "Hazardous Phantom", "Goshnar's Cruelty", "Goshnar's Spite", "Goshnar's Malice", From 376e38783293c2d82ecc00bfd279d8f6ee1287ac Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 10 Sep 2024 14:44:23 -0300 Subject: [PATCH 60/60] fix: calculate desire chance function --- data-otservbr-global/lib/quests/soul_war.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 4b48d3be9d8..a9e9d920e91 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1092,16 +1092,15 @@ end function MonsterType:calculateBagYouDesireChance(player, itemChance) local playerTaintLevel = player:getTaintLevel() if not playerTaintLevel or playerTaintLevel == 0 then - return {} + return itemChance end local monsterName = self:getName() local isMonsterValid = table.contains(SoulWarQuest.bagYouDesireMonsters, monsterName) if not isMonsterValid then - return {} + return itemChance end - local loot = {} local soulWarQuest = player:soulWarQuestKV() local megalomaniaKills = soulWarQuest:scoped("megalomania-kills"):get("count") or 0