From be629469bdd75c6f96b219a9c3ca7c0d1711f603 Mon Sep 17 00:00:00 2001 From: Skold177 <113406182+Skold177@users.noreply.github.com> Date: Tue, 30 Dec 2025 00:22:30 -0500 Subject: [PATCH] [lua] [sql] KSNM Cactuar Suave Adds the KSNM Cactuar Suave with a capture from retail. --- .../Chamber_of_Oracles/cactuar_suave.lua | 1 - scripts/enum/mob_skill.lua | 2 + .../mobs/Sabotender_Amante.lua | 17 +++ .../mobs/Sabotender_Campeon.lua | 118 ++++++++++++++++++ sql/mob_groups.sql | 4 +- sql/mob_pools.sql | 4 +- 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Amante.lua create mode 100644 scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Campeon.lua diff --git a/scripts/battlefields/Chamber_of_Oracles/cactuar_suave.lua b/scripts/battlefields/Chamber_of_Oracles/cactuar_suave.lua index dbd7659e88b..1e08bbdbad0 100644 --- a/scripts/battlefields/Chamber_of_Oracles/cactuar_suave.lua +++ b/scripts/battlefields/Chamber_of_Oracles/cactuar_suave.lua @@ -15,7 +15,6 @@ local content = Battlefield:new({ entryNpc = 'SC_Entrance', exitNpc = 'Shimmering_Circle', requiredItems = { xi.item.CLOTHO_ORB, wearMessage = chamberOfOraclesID.text.A_CRACK_HAS_FORMED, wornMessage = chamberOfOraclesID.text.ORB_IS_CRACKED }, - experimental = true, }) content:addEssentialMobs({ 'Sabotender_Campeon', 'Sabotender_Amante' }) diff --git a/scripts/enum/mob_skill.lua b/scripts/enum/mob_skill.lua index 13b83d8fe3e..f678e7eb7c6 100644 --- a/scripts/enum/mob_skill.lua +++ b/scripts/enum/mob_skill.lua @@ -68,6 +68,8 @@ xi.mobSkill = BAD_BREATH_1 = 319, + THOUSAND_NEEDLES_1 = 322, + DRILL_BRANCH = 328, PINECONE_BOMB = 329, diff --git a/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Amante.lua b/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Amante.lua new file mode 100644 index 00000000000..e6be50df2f6 --- /dev/null +++ b/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Amante.lua @@ -0,0 +1,17 @@ +----------------------------------- +-- Area : Chamber of Oracles +-- Mob : Sabotender Amante +-- KSNM : Cactuar Suave +----------------------------------- +---@type TMobEntity +local entity = {} + +entity.onMobInitialize = function(mob) + mob:addImmunity(xi.immunity.LIGHT_SLEEP) + mob:addImmunity(xi.immunity.DARK_SLEEP) + mob:addImmunity(xi.immunity.PETRIFY) + mob:setMobMod(xi.mobMod.BASE_DAMAGE_MULTIPLIER, 150) + mob:setMobMod(xi.mobMod.SUPERLINK, 1) +end + +return entity diff --git a/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Campeon.lua b/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Campeon.lua new file mode 100644 index 00000000000..c34f8f14cf5 --- /dev/null +++ b/scripts/zones/Chamber_of_Oracles/mobs/Sabotender_Campeon.lua @@ -0,0 +1,118 @@ +----------------------------------- +-- Area : Chamber of Oracles +-- Mob : Sabotender Campeon +-- KSNM : Cactuar Suave +----------------------------------- +---@type TMobEntity +local entity = {} + +-- Centers for the three arenas in this battlefield +local arenaCenters = +{ + [1] = { x = 0, y = 100, z = -240 }, + [2] = { x = 0, y = 0, z = 2 }, + [3] = { x = 0, y = -100, z = 240 }, +} + +local function generatePath(mob) + local battlefield = mob:getBattlefield() + if not battlefield then + return + end + + -- Get center coordinates. + local center = arenaCenters[battlefield:getArea()] + + -- Randomly choose a side to path towards, either right or left. + local positionX = center.x + 8.50 * (1 - 2 * math.random(0, 1)) + local positionY = center.y + local positionZ = center.z + 6.25 + + local pathPoints = {} + local pointCount = math.random(10, 15) + + -- Populate 1st point with the chosen corner + pathPoints[1] = { x = positionX, y = positionY, z = positionZ } + + -- Generate 10 to 15 random points with slight variation around the corner + for i = 2, pointCount do + local variationX = math.random(-300, 300) / 100 + local variationZ = math.random(-300, 300) / 100 + pathPoints[i] = { x = positionX + variationX, y = positionY, z = positionZ + variationZ } + end + + -- Begin running along the path + local pathFlags = bit.bor(xi.path.flag.COORDS, xi.path.flag.RUN, xi.path.flag.SCRIPT) + mob:pathThrough(pathPoints, pathFlags) +end + +entity.onMobInitialize = function(mob) + mob:addImmunity(xi.immunity.LIGHT_SLEEP) + mob:addImmunity(xi.immunity.DARK_SLEEP) + mob:addImmunity(xi.immunity.GRAVITY) + mob:addImmunity(xi.immunity.BIND) + mob:addImmunity(xi.immunity.PETRIFY) + mob:setMagicCastingEnabled(false) -- Only casts upon returning from a run! + mob:setMobMod(xi.mobMod.SUPERLINK, 1) +end + +entity.onMobSpawn = function(mob) + mob:setMod(xi.mod.REGEN, 120) + mob:setMod(xi.mod.TRIPLE_ATTACK, 15) + mob:setMobMod(xi.mobMod.BASE_DAMAGE_MULTIPLIER, 150) +end + +entity.onMobEngage = function(mob) + mob:setLocalVar('phase', 0) + mob:setLocalVar('nextPathTime', GetSystemTime() + math.random(5, 10)) +end + +entity.onMobFight = function(mob, target) + switch (mob:getLocalVar('phase')): caseof + { + [0] = function() + if GetSystemTime() >= mob:getLocalVar('nextPathTime') then + generatePath(mob) + mob:setLocalVar('phase', 1) + end + end, + + [1] = function() + if not mob:isFollowingPath() then + mob:setLocalVar('phase', 2) + mob:setLocalVar('nextPathTime', GetSystemTime() + math.random(20, 45)) + end + end, + + [2] = function() + local currentTarget = mob:getTarget() + if not currentTarget then + return + end + + if mob:checkDistance(currentTarget) > 7 then + return + end + + local baseId = mob:getID() + for i = 0, 2 do + local ally = GetMobByID(baseId + i) + + if + ally and + ally:isAlive() and + ally:getHPP() <= 50 + then + mob:castSpell(xi.magic.spell.CURE_V, ally) + mob:setLocalVar('phase', 0) + return + end + end + + mob:useMobAbility(xi.mobSkill.THOUSAND_NEEDLES_1) + mob:setLocalVar('phase', 0) + end, + } +end + +return entity diff --git a/sql/mob_groups.sql b/sql/mob_groups.sql index 5550ea0838f..73781098eb2 100644 --- a/sql/mob_groups.sql +++ b/sql/mob_groups.sql @@ -11874,8 +11874,8 @@ INSERT INTO `mob_groups` VALUES (8,3527,168,'Secutor_XI-XXXII',0,128,0,3800,0,64 INSERT INTO `mob_groups` VALUES (9,3357,168,'Retiarius_XI-XIX',0,128,0,4100,4100,64,64,0); INSERT INTO `mob_groups` VALUES (10,1989,168,'Hoplomachus_XI-XXVI',0,128,0,3800,3800,63,63,0); INSERT INTO `mob_groups` VALUES (11,675,168,'Centurio_XI-I',0,128,0,3500,0,65,65,0); -INSERT INTO `mob_groups` VALUES (12,3429,168,'Sabotender_Campeon',0,128,0,0,0,75,75,0); -INSERT INTO `mob_groups` VALUES (13,3427,168,'Sabotender_Amante',0,128,0,0,0,75,75,0); +INSERT INTO `mob_groups` VALUES (12,3429,168,'Sabotender_Campeon',0,128,0,6250,6250,77,77,0); +INSERT INTO `mob_groups` VALUES (13,3427,168,'Sabotender_Amante',0,128,0,13500,0,73,74,0); INSERT INTO `mob_groups` VALUES (14,3303,168,'Radiant_Wyvern',0,128,0,0,0,75,76,0); INSERT INTO `mob_groups` VALUES (15,454,168,'Blizzard_Wyvern',0,128,0,0,0,75,76,0); INSERT INTO `mob_groups` VALUES (16,2411,168,'Lightning_Wyvern',0,128,0,0,0,75,76,0); diff --git a/sql/mob_pools.sql b/sql/mob_pools.sql index e7f69a34064..c9cf0ecf59e 100644 --- a/sql/mob_pools.sql +++ b/sql/mob_pools.sql @@ -3483,9 +3483,9 @@ INSERT INTO `mob_pools` VALUES (3424,'Sabertooth_Tiger','Sabertooth_Tiger',242,0 INSERT INTO `mob_pools` VALUES (3425,'Sable-tongued_Gonberry','Sable-tongued_Gonberry',243,0x0000C60400000000000000000000000000000000,4,4,6,240,100,0,1,0,1,18,0,32,167,157,0,0,1,0,0,243,243,2,11); -- Pool only used for Sabotenders in East Altepa. Linking enabled here to ensure Cactrot Rapido links properly. onMobSpawn disables linking. INSERT INTO `mob_pools` VALUES (3426,'Sabotender','Sabotender',212,0x0000740100000000000000000000000000000000,2,2,7,360,100,0,1,0,1,0,0,0,6461,131,0,0,0,0,0,212,212,1,8); -INSERT INTO `mob_pools` VALUES (3427,'Sabotender_Amante','Sabotender_Amante',212,0x0000740100000000000000000000000000000000,2,2,7,360,100,0,1,1,1,16,0,0,4493,645,0,0,0,0,0,212,212,NULL,NULL); +INSERT INTO `mob_pools` VALUES (3427,'Sabotender_Amante','Sabotender_Amante',212,0x0000740100000000000000000000000000000000,1,2,7,240,100,0,1,1,1,16,0,0,4493,645,0,0,0,0,0,212,212,2,9); INSERT INTO `mob_pools` VALUES (3428,'Sabotender_Bailarin','Sabotender_Bailarin',212,0x0000740100000000000000000000000000000000,2,2,7,360,100,0,1,0,0,2,0,0,0,133,0,0,0,0,0,212,212,NULL,NULL); -INSERT INTO `mob_pools` VALUES (3429,'Sabotender_Campeon','Sabotender_Campeon',212,0x0000750100000000000000000000000000000000,2,3,7,360,100,0,1,1,1,16,0,0,1931,129,0,0,75,0,0,212,212,NULL,NULL); +INSERT INTO `mob_pools` VALUES (3429,'Sabotender_Campeon','Sabotender_Campeon',212,0x0000750100000000000000000000000000000000,3,6,7,240,100,0,1,1,1,16,0,0,1931,129,0,0,1,0,0,212,212,0,6); INSERT INTO `mob_pools` VALUES (3430,'Sabotender_Enamorado','Sabotender_Enamorado',212,0x0000740100000000000000000000000000000000,12,12,7,240,100,0,1,0,0,2,0,0,3906,133,0,0,0,0,0,212,212,2,9); INSERT INTO `mob_pools` VALUES (3431,'Sabotender_Maestro','Sabotender_Maestro',212,0x0000740100000000000000000000000000000000,2,2,7,360,100,0,1,0,1,0,0,0,0,0,0,0,0,0,0,212,212,NULL,NULL); INSERT INTO `mob_pools` VALUES (3432,'Sabotender_Mariachi','Sabotender_Mariachi',212,0x0000740100000000000000000000000000000000,2,2,7,360,100,0,1,0,0,2,0,0,0,133,0,0,0,0,0,212,212,2,9);