From 2e25cf2ea4efb4af4ddcc6a32ed76dc54c7f441d Mon Sep 17 00:00:00 2001 From: Skold177 <113406182+Skold177@users.noreply.github.com> Date: Sat, 3 Jan 2026 20:08:16 -0500 Subject: [PATCH] [lua] [sql] ENM Fire in the Sky Implements the ENM Fire in the Sky --- .../mobskills/self-destruct_cluster_2.lua | 7 + .../mobskills/self-destruct_cluster_3.lua | 7 + .../mobskills/self-destruct_cluster_razon.lua | 31 ++++ .../Monarch_Linn/fire_in_the_sky.lua | 39 ++--- scripts/enum/item.lua | 2 + scripts/enum/mob_pool.lua | 1 + scripts/enum/mob_skill.lua | 1 + scripts/zones/Monarch_Linn/IDs.lua | 1 + scripts/zones/Monarch_Linn/mobs/Razon.lua | 134 +++++++++++++++++- sql/mob_groups.sql | 2 +- sql/mob_skills.sql | 2 +- 11 files changed, 203 insertions(+), 24 deletions(-) create mode 100644 scripts/actions/mobskills/self-destruct_cluster_razon.lua diff --git a/scripts/actions/mobskills/self-destruct_cluster_2.lua b/scripts/actions/mobskills/self-destruct_cluster_2.lua index 16fa908a50e..c41872ea49a 100644 --- a/scripts/actions/mobskills/self-destruct_cluster_2.lua +++ b/scripts/actions/mobskills/self-destruct_cluster_2.lua @@ -12,6 +12,13 @@ end mobskillObject.onMobWeaponSkill = function(target, mob, skill) local damage = math.floor(mob:getHP() / 3) + if mob:getPool() == xi.mobPool.RAZON then + damage = mob:getMaxHP() / 2 + if mob:getHPP() <= 33 then + damage = 0 + end + end + local info = xi.mobskills.mobMagicalMove(mob, target, skill, damage, xi.element.FIRE, 0.4, xi.mobskills.magicalTpBonus.MAB_BONUS, 1) damage = xi.mobskills.mobFinalAdjustments(info, mob, skill, target, xi.attackType.MAGICAL, xi.damageType.FIRE, xi.mobskills.shadowBehavior.IGNORE_SHADOWS) diff --git a/scripts/actions/mobskills/self-destruct_cluster_3.lua b/scripts/actions/mobskills/self-destruct_cluster_3.lua index 95d8b571306..ce0b25d2b1b 100644 --- a/scripts/actions/mobskills/self-destruct_cluster_3.lua +++ b/scripts/actions/mobskills/self-destruct_cluster_3.lua @@ -12,6 +12,13 @@ end mobskillObject.onMobWeaponSkill = function(target, mob, skill) local damage = math.floor(mob:getHP() / 3) + if mob:getPool() == xi.mobPool.RAZON then + damage = math.floor(mob:getMaxHP() / 3) + if mob:getHPP() <= 66 then + damage = 0 + end + end + local info = xi.mobskills.mobMagicalMove(mob, target, skill, damage, xi.element.FIRE, 0.4, xi.mobskills.magicalTpBonus.MAB_BONUS, 1) damage = xi.mobskills.mobFinalAdjustments(info, mob, skill, target, xi.attackType.MAGICAL, xi.damageType.FIRE, xi.mobskills.shadowBehavior.IGNORE_SHADOWS) diff --git a/scripts/actions/mobskills/self-destruct_cluster_razon.lua b/scripts/actions/mobskills/self-destruct_cluster_razon.lua new file mode 100644 index 00000000000..4dc133a767b --- /dev/null +++ b/scripts/actions/mobskills/self-destruct_cluster_razon.lua @@ -0,0 +1,31 @@ +----------------------------------- +-- Self-Destruct (Fire in the Sky) +-- Description: Deals massive fire damage to enemies within range. Damage scales 1:1 with remaining HP. +-- Type: Magical Fire +-- Range: 10' +-- Utsusemi/Blink absorb: Ignores shadows +-- Notes: Used by Razon in ENM "Fire in the Sky" when final self-destruct is triggered. Results in immediate ejection from the battlefield. +----------------------------------- +---@type TMobSkill +local mobskillObject = {} + +mobskillObject.onMobSkillCheck = function(target, mob, skill) + return 0 +end + +mobskillObject.onMobWeaponSkill = function(target, mob, skill) + local damage = mob:getHP() * 2 + + local info = xi.mobskills.mobMagicalMove(mob, target, skill, damage, xi.element.FIRE, 1, xi.mobskills.magicalTpBonus.MAB_BONUS, 1) + damage = xi.mobskills.mobFinalAdjustments(info, mob, skill, target, xi.attackType.MAGICAL, xi.damageType.FIRE, xi.mobskills.shadowBehavior.IGNORE_SHADOWS) + + target:takeDamage(damage, mob, xi.attackType.MAGICAL, xi.damageType.FIRE) + + return damage +end + +mobskillObject.onMobSkillFinalize = function(mob, skill) + mob:setHP(0) +end + +return mobskillObject diff --git a/scripts/battlefields/Monarch_Linn/fire_in_the_sky.lua b/scripts/battlefields/Monarch_Linn/fire_in_the_sky.lua index fce915a46c5..7ca6231ec67 100644 --- a/scripts/battlefields/Monarch_Linn/fire_in_the_sky.lua +++ b/scripts/battlefields/Monarch_Linn/fire_in_the_sky.lua @@ -16,34 +16,41 @@ local content = Battlefield:new({ entryNpc = 'SD_Entrance', exitNpcs = { 'SD_BCNM_Exit_1', 'SD_BCNM_Exit_2', 'SD_BCNM_Exit_3' }, requiredKeyItems = { xi.ki.MONARCH_BEARD, message = monarchLinnID.text.TORN_FROM_YOUR_HANDS }, - experimental = true, + grantXP = 2500, }) content.groups = { { - mobIds = - { - { - monarchLinnID.mob.RAZON, - }, + mobs = { 'Razon' }, - { - monarchLinnID.mob.RAZON + 2, - }, - - { - monarchLinnID.mob.RAZON + 4, - }, - }, + allDeath = function(battlefield, mob) + -- If Razon dies from self-destruct, everyone gets kicked out anyway, but this keeps the chest from spawning. + if battlefield:getLocalVar('phase') < 4 then + content:handleAllMonstersDefeated(battlefield, mob) + end + end, }, } content.loot = { { - -- TODO: Loot + { itemId = xi.item.NONE, weight = 9500 }, + { itemId = xi.item.CLOUD_EVOKER, weight = 1500 }, + }, + + { + { itemId = xi.item.NONE, weight = 5000 }, + { itemId = xi.item.THUGS_ZAMBURAK, weight = 2500 }, + { itemId = xi.item.HORROR_VOULGE, weight = 2500 }, }, -} + { + { itemId = xi.item.NONE, weight = 3500 }, + { itemId = xi.item.CROSSBOWMANS_RING, weight = 2000 }, + { itemId = xi.item.WOODSMAN_RING, weight = 1500 }, + { itemId = xi.item.ETHER_RING, weight = 3000 }, + }, +} return content:register() diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index 4d6dee1403d..54c4430aa9a 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -7040,6 +7040,7 @@ xi.item = GLORY_CROWN = 16070, KAWAHORI_KABUTO = 16071, COVEN_HAT = 16076, + HORROR_VOULGE = 16078, ARES_MASK = 16084, ENYOS_MASK = 16085, PHOBOSS_MASK = 16086, @@ -7524,6 +7525,7 @@ xi.item = MARTIAL_BOW = 17209, MARTIAL_GUN = 17210, ALMOGAVAR_BOW = 17211, + THUGS_ZAMBURAK = 17215, LIGHT_CROSSBOW = 17216, CROSSBOW = 17217, ZAMBURAK = 17218, diff --git a/scripts/enum/mob_pool.lua b/scripts/enum/mob_pool.lua index 024d53b77ce..83d5bbbd13a 100644 --- a/scripts/enum/mob_pool.lua +++ b/scripts/enum/mob_pool.lua @@ -35,6 +35,7 @@ xi.mobPool = PROPAGATOR = 3206, -- Fission (Number of Adds) QNAERN_RDM = 3269, -- Qn'Aern RDM chainspell check RASKOVNIK = 3326, -- Raskovnik Soothing Aroma + RAZON = 3333, -- Self-Destruct Damage SHIKAREE_X = 3598, -- Shikaree X (Head Wind/TWT/ROS) SHIKAREE_Y = 3600, -- Shikaree Y (Head Wind/TWT/ROS) SHIKAREE_Z = 3601, -- Shikaree Z (Head Wind/TWT/ROS) diff --git a/scripts/enum/mob_skill.lua b/scripts/enum/mob_skill.lua index 1fe917b5706..88a551da32e 100644 --- a/scripts/enum/mob_skill.lua +++ b/scripts/enum/mob_skill.lua @@ -577,6 +577,7 @@ xi.mobSkill = DECAYED_FILAMENT = 1467, REACTOR_OVERHEAT = 1468, REACTOR_OVERLOAD = 1469, + SELF_DESTRUCT_CLUSTER_RAZON = 1470, HUNDRED_FISTS_PRISHE = 1485, BENEDICTION_PRISHE = 1486, diff --git a/scripts/zones/Monarch_Linn/IDs.lua b/scripts/zones/Monarch_Linn/IDs.lua index 457ba777792..736a227cf62 100644 --- a/scripts/zones/Monarch_Linn/IDs.lua +++ b/scripts/zones/Monarch_Linn/IDs.lua @@ -26,6 +26,7 @@ zones[xi.zone.MONARCH_LINN] = TORN_FROM_YOUR_HANDS = 7475, -- The is torn from your hands and sucked into the spatial displacement! CONQUEST_BASE = 7484, -- Tallying conquest results... ENTERING_THE_BATTLEFIELD_FOR = 7647, -- Entering the battlefield for [Ancient Vows/The Savage/Fire in the Sky/Bad Seed/Bugard in the Clouds/Beloved of the Atlantes/Uninvited Guests/Nest of Nightmares/The Savage]! + KNOCKED_OUT_OF_BATTLEFIELD = 7654, -- The blast wave from Razon's Self-Destruct knocks you out of the battlefield! }, mob = { diff --git a/scripts/zones/Monarch_Linn/mobs/Razon.lua b/scripts/zones/Monarch_Linn/mobs/Razon.lua index 97dd59788c9..6fab736aa8e 100644 --- a/scripts/zones/Monarch_Linn/mobs/Razon.lua +++ b/scripts/zones/Monarch_Linn/mobs/Razon.lua @@ -1,19 +1,141 @@ ----------------------------------- --- Area: Monarch_Linn --- Mob: Razon +-- Area : Monarch Linn +-- Mob : Razon +-- ENM : Fire in the Sky +----------------------------------- +local ID = zones[xi.zone.MONARCH_LINN] ----------------------------------- ---@type TMobEntity local entity = {} --- TODO: Perform full captures on Razon. +local elementData = +{ + [xi.damageType.FIRE] = { animation = 432 }, + [xi.damageType.ICE] = { animation = 433 }, + [xi.damageType.WIND] = { animation = 434 }, + [xi.damageType.EARTH] = { animation = 435 }, + [xi.damageType.THUNDER] = { animation = 436 }, + [xi.damageType.WATER] = { animation = 437 }, + [xi.damageType.LIGHT] = { animation = 438 }, + [xi.damageType.DARK] = { animation = 439 }, +} -entity.onMobSpawn = function(mob) +-- Phase changes happen from triggered element or HP thresholds, last self destruct can only be triggered by element. +local phases = +{ + [1] = { threshold = 66, skill = xi.mobSkill.SELF_DESTRUCT_CLUSTER_3 }, + [2] = { threshold = 33, skill = xi.mobSkill.SELF_DESTRUCT_CLUSTER_2 }, + [3] = { threshold = nil, skill = xi.mobSkill.SELF_DESTRUCT_CLUSTER_RAZON }, +} + +entity.onMobInitialize = function(mob) + mob:addImmunity(xi.immunity.BIND) + mob:setMobMod(xi.mobMod.MAGIC_RANGE, 40) + + -- When the final self destruct is triggered, all players get kicked out of the battlefield immediately + mob:addListener('WEAPONSKILL_STATE_EXIT', 'FINAL_SELF_DESTRUCT', function(mobArg, skillId) + if skillId ~= xi.mobSkill.SELF_DESTRUCT_CLUSTER_RAZON then + return + end + + local battlefield = mobArg:getBattlefield() + if not battlefield then + return + end + + for _, player in pairs(battlefield:getPlayers()) do + player:messageSpecial(ID.text.KNOCKED_OUT_OF_BATTLEFIELD) + end + + battlefield:cleanup(true) + end) + + mob:addListener('TAKE_DAMAGE', 'RAZON_DEALT_DAMAGE', function(mobArg, damage, attacker, attackType, damageType) + if not attacker then + return + end + + -- Avatars blood pacts can't trigger the self destruct (TODO: Jug pets too?) + if attacker:isAvatar() then + return + end + + -- Should never be an issue, but just in case. + if damageType < xi.damageType.FIRE then + return + end + + local triggerElement1 = mobArg:getLocalVar('triggerElement1') + local triggerElement2 = mobArg:getLocalVar('triggerElement2') + + if damageType == triggerElement1 or damageType == triggerElement2 then + mobArg:setLocalVar('elementTriggered', damageType) + end + end) end -entity.onMagicHit = function(caster, target, spell) +-- Setup selected elements for self destructs, set phase as a battlefield var so it can be checked in the battlefield lua for victory conditions. +entity.onMobSpawn = function(mob) + mob:setMod(xi.mod.UDMGMAGIC, 10000) + mob:setMod(xi.mod.UDMGPHYS, -9500) + + local elements = {} + for element, _ in pairs(elementData) do + table.insert(elements, element) + end + + local shuffledElements = utils.permgen(#elements, 1) + mob:setLocalVar('triggerElement1', elements[shuffledElements[1]]) + mob:setLocalVar('triggerElement2', elements[shuffledElements[2]]) + mob:setLocalVar('elementTriggered', 0) + + local battlefield = mob:getBattlefield() + + if not battlefield then + return + end + + battlefield:setLocalVar('phase', 1) end -entity.onMobDeath = function(mob, player, optParams) +entity.onMobFight = function(mob, target) + if xi.combat.behavior.isEntityBusy(mob) then + return + end + + local battlefield = mob:getBattlefield() + + if not battlefield then + return + end + + local phaseData = phases[battlefield:getLocalVar('phase')] + if not phaseData then + return + end + + local elementTriggered = mob:getLocalVar('elementTriggered') + if + elementTriggered == 0 and + (not phaseData.threshold or mob:getHPP() > phaseData.threshold) + then + return + end + + if mob:checkDistance(target) > 10 then + return + end + + -- If phase change is triggered by an element, inject colored dust cloud animation alongside self destruct + if elementTriggered > 0 then + mob:injectActionPacket(mob:getID(), 11, elementData[elementTriggered].animation, 0, 0x18, 0, 0, 0) + end + + mob:useMobAbility(phaseData.skill) + + battlefield:setLocalVar('phase', battlefield:getLocalVar('phase') + 1) + + mob:setLocalVar('elementTriggered', 0) end return entity diff --git a/sql/mob_groups.sql b/sql/mob_groups.sql index ee9a6ec544c..5d9d3ea4e55 100644 --- a/sql/mob_groups.sql +++ b/sql/mob_groups.sql @@ -1212,7 +1212,7 @@ INSERT INTO `mob_groups` VALUES (21,7286,30,'Ouryu',0,128,1962,50000,0,0,NULL); INSERT INTO `mob_groups` VALUES (1,2499,31,'Mammet-19_Epsilon',0,128,1585,4000,0,0,NULL); INSERT INTO `mob_groups` VALUES (2,3070,31,'Ouryu',0,128,0,10500,0,0,NULL); INSERT INTO `mob_groups` VALUES (3,1885,31,'Hamadryad',0,128,0,0,0,0,NULL); -INSERT INTO `mob_groups` VALUES (4,3333,31,'Razon',0,128,0,1000,0,0,NULL); +INSERT INTO `mob_groups` VALUES (4,3333,31,'Razon',0,128,0,2100,0,0,NULL); INSERT INTO `mob_groups` VALUES (5,4304,31,'Watch_Hippogryph',0,128,0,5000,0,0,NULL); INSERT INTO `mob_groups` VALUES (6,1834,31,'Guard_Hippogryph',0,128,0,1000,0,0,NULL); INSERT INTO `mob_groups` VALUES (7,1992,31,'Hotupuku',0,128,0,0,0,0,NULL); diff --git a/sql/mob_skills.sql b/sql/mob_skills.sql index 5b3e27786f0..a6e80c0bc62 100644 --- a/sql/mob_skills.sql +++ b/sql/mob_skills.sql @@ -1494,7 +1494,7 @@ INSERT INTO `mob_skills` VALUES (1466,1077,'static_filament',4,0.0,10.0,2000,100 INSERT INTO `mob_skills` VALUES (1467,1078,'decayed_filament',1,0.0,8.0,2000,1000,4,0,0,0,0,0,0); -- bar form only INSERT INTO `mob_skills` VALUES (1468,1079,'reactor_overheat',4,0.0,10.0,2000,1000,4,0,0,0,0,0,0); -- ring form only INSERT INTO `mob_skills` VALUES (1469,1080,'reactor_overload',1,0.0,8.0,2000,1000,4,0,0,0,0,0,0); -- ring form only --- INSERT INTO `mob_skills` VALUES (1470,1214,'self-destruct',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0); +INSERT INTO `mob_skills` VALUES (1470,874,'self-destruct_cluster_razon',1,0.0,10.0,2000,0,4,0,0,0,0,0,0); -- INSERT INTO `mob_skills` VALUES (1471,1215,'cattlepult',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0); -- INSERT INTO `mob_skills` VALUES (1472,1216,'cattlepult',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0); -- INSERT INTO `mob_skills` VALUES (1473,1217,'cattlepult',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0);