From d50660187d90bc8e7ebcb238cf91c699b1618666 Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 06:02:22 +0000 Subject: [PATCH 1/7] Fixed Zantetsuken --- .../actions/abilities/pets/zantetsuken.lua | 23 +++++++++++++++---- scripts/globals/pets.lua | 13 +++++++---- sql/abilities.sql | 2 +- sql/pet_skills.sql | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/scripts/actions/abilities/pets/zantetsuken.lua b/scripts/actions/abilities/pets/zantetsuken.lua index 769191f5e73..165dee22491 100644 --- a/scripts/actions/abilities/pets/zantetsuken.lua +++ b/scripts/actions/abilities/pets/zantetsuken.lua @@ -11,10 +11,14 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.summoner.canUseBloodPact(player, player:getPet(), target, ability) + return 0, 0 end abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) + if summoner == nil then + return 0 + end + local returnParam = 0 local power = summoner:getMP() / utils.clamp(summoner:getMaxMP(), 1, 9999) @@ -37,15 +41,17 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) returnParam = info.damage else - -- Insta-kill: Highly innacurate against regular monsters. - local chance = 50 * power / utils.clamp(petskill:getTotalTargets(), 1, 50) + -- Zantetsuken chance to kill starts at MP/MaxMP so at full MP the kill chance is 100% + -- This is then divided by the number of targets. So with 4 targets, each one has a 25% + -- to die if the summoner has full MP. I am guessing at the 1% floor with 100 targets. + -- I have never tested this against more than 5-6 targets on retail. + local chance = 100 * power / utils.clamp(petskill:getTotalTargets(), 1, 100) if math.randomInt(1, 100) <= chance and target:getAnimation() ~= 33 then - petskill:setMsg(xi.msg.basic.SKILL_ENFEEB_IS) - target:takeDamage(target:getHP(), pet, xi.attackType.MAGICAL, xi.damageType.DARK) + target:setHP(0) returnParam = xi.effect.KO else @@ -57,6 +63,13 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) summoner:setMP(0) + -- Despawn Odin after 8 seconds. + pet:timer(8000, function() + if summoner then + summoner:despawnPet() + end + end) + return returnParam end diff --git a/scripts/globals/pets.lua b/scripts/globals/pets.lua index bb5d4296013..61cce276574 100644 --- a/scripts/globals/pets.lua +++ b/scripts/globals/pets.lua @@ -117,10 +117,12 @@ xi.pet.spawnPet = function(caster, petID, state, target) end elseif petID == xi.petId.ODIN then if target then - caster:petAttack(target) - --pet:timer(5000, function() - -- pet:usePetAbility(xi.jobAbility.ZANTETSUKEN, target) - --end) + local pet = caster:getPet() + if pet then + pet:timer(5000, function() + pet:usePetAbility(xi.jobAbility.ZANTETSUKEN, target) + end) + end end elseif petID == xi.petId.ATOMOS then if target then @@ -128,11 +130,12 @@ xi.pet.spawnPet = function(caster, petID, state, target) local pet = caster:getPet() if pet then -- Timed sequence after spawning, wait -> Deconstruction -> wait -> Chronoshift (despawn pet after complete) + -- Full cycle takes 3s -> 6s -> 3s from summon to despawn pet:timer(3000, function() pet:usePetAbility(xi.jobAbility.DECONSTRUCTION, target) end) - pet:timer(10000, function() + pet:timer(6000, function() pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) end) end diff --git a/sql/abilities.sql b/sql/abilities.sql index dfb2ba3e662..c09a73eb9f8 100644 --- a/sql/abilities.sql +++ b/sql/abilities.sql @@ -523,7 +523,7 @@ INSERT INTO `abilities` VALUES (666,'pavor_nocturnus',15,98,4,60,174,0,0,94,2000 INSERT INTO `abilities` VALUES (667,'blindside',15,99,4,60,173,0,0,94,2000,0,6,10,0,0,1,60,0,0,'SOA'); INSERT INTO `abilities` VALUES (668,'deconstruction',15,75,4,0,0,0,0,0,0,0,6,20,0,0,0,0,0,0,NULL); INSERT INTO `abilities` VALUES (669,'chronoshift',15,75,4,0,0,0,0,0,0,0,6,0,0,14,0,0,0,0,NULL); --- INSERT INTO `abilities` VALUES (670,'zantetsuken',22,1,1,0,300,0,0,???,2000,0,6,10,0,10,450,900,0,0,NULL); +-- INSERT INTO `abilities` VALUES (670,'zantetsuken',22,1,1,0,300,0,0,151,2000,0,6,10,0,10,450,900,0,0,NULL); INSERT INTO `abilities` VALUES (671,'perfect_defense',15,75,1,0,300,0,0,152,2000,0,6,0,0,10,0,0,0,2,NULL); INSERT INTO `abilities` VALUES (672,'foot_kick',9,25,257,1,102,0,0,0,2000,0,6,3,0,0,1,60,0,0,NULL); INSERT INTO `abilities` VALUES (673,'dust_cloud',9,25,257,1,102,0,0,0,2000,0,6,3,0,10,1,60,0,0,NULL); diff --git a/sql/pet_skills.sql b/sql/pet_skills.sql index 5410be2924d..5907566da97 100644 --- a/sql/pet_skills.sql +++ b/sql/pet_skills.sql @@ -187,7 +187,7 @@ INSERT INTO `pet_skills` VALUES (666,0,155,'pavor_nocturnus',0,0,12,2000,1000,4, INSERT INTO `pet_skills` VALUES (667,0,173,'blindside',0,0,10,2000,1000,4,317,@SKILLFLAG_BLOODPACT_RAGE,0,13,0,@SC_GRAVITATION,@SC_TRANSFIXION,0); -- No LUA yet INSERT INTO `pet_skills` VALUES (668,0,171,'deconstruction',0,0,21,2000,1000,4,323,@SKILLFLAG_BLOODPACT_RAGE,0,13,0,0,0,0); INSERT INTO `pet_skills` VALUES (669,0,172,'chronoshift',1,14,0,2000,1000,3,283,@SKILLFLAG_BLOODPACT_WARD,0,13,0,0,0,0); --- 670: Zantetsuken 10y (Radius 10y) +INSERT INTO `pet_skills` VALUES (670,0,151,'zantetsuken',4,15,15.0,2000,1000,4,277,@SKILLFLAG_ASTRAL_FLOW | @SKILLFLAG_BLOODPACT_RAGE,0,13,0,0,0,0); -- Conal 15y, anim/msg from capture INSERT INTO `pet_skills` VALUES (671,0,152,'perfect_defense',1,10,0,2000,1000,3,316,@SKILLFLAG_ASTRAL_FLOW | @SKILLFLAG_BLOODPACT_WARD,0,13,0,0,0,0); -- Jug Pet Ready Moves INSERT INTO `pet_skills` VALUES (672,3840,1,'foot_kick',0,0,3,2000,1500,4,185,@SKILLFLAG_NONE,0,11,0,@SC_REVERBERATION,0,0); From 2be187897bc56bee9ef5e1f0fca9a43ac29f6231 Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 06:11:27 +0000 Subject: [PATCH 2/7] Fixing timing on Atomos --- scripts/actions/abilities/pets/deconstruction.lua | 4 ++++ scripts/globals/pets.lua | 8 ++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/actions/abilities/pets/deconstruction.lua b/scripts/actions/abilities/pets/deconstruction.lua index 39c7f5781b8..603fd8d2c72 100644 --- a/scripts/actions/abilities/pets/deconstruction.lua +++ b/scripts/actions/abilities/pets/deconstruction.lua @@ -26,6 +26,10 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) target:addEnmity(pet, 1, 60) target:addEnmity(summoner, 1, 0) -- this is to ensure you cannot cheese mobs with this, mob goes passive if not added + pet:timer(5000, function() + pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) + end) + return effectCount end diff --git a/scripts/globals/pets.lua b/scripts/globals/pets.lua index 61cce276574..72cc416a624 100644 --- a/scripts/globals/pets.lua +++ b/scripts/globals/pets.lua @@ -130,14 +130,10 @@ xi.pet.spawnPet = function(caster, petID, state, target) local pet = caster:getPet() if pet then -- Timed sequence after spawning, wait -> Deconstruction -> wait -> Chronoshift (despawn pet after complete) - -- Full cycle takes 3s -> 6s -> 3s from summon to despawn - pet:timer(3000, function() + -- Full cycle takes 5s -> 5s -> 5s from summon to despawn + pet:timer(5000, function() pet:usePetAbility(xi.jobAbility.DECONSTRUCTION, target) end) - - pet:timer(6000, function() - pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) - end) end end end From 0b1b6d255e7199b8f6e0b863d8dc7c2825c1aaba Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 06:36:52 +0000 Subject: [PATCH 3/7] Zantetsuken message was incorrect --- scripts/actions/abilities/pets/zantetsuken.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/actions/abilities/pets/zantetsuken.lua b/scripts/actions/abilities/pets/zantetsuken.lua index 165dee22491..c928eafe419 100644 --- a/scripts/actions/abilities/pets/zantetsuken.lua +++ b/scripts/actions/abilities/pets/zantetsuken.lua @@ -51,7 +51,7 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) math.randomInt(1, 100) <= chance and target:getAnimation() ~= 33 then - target:setHP(0) + target:takeDamage(target:getHP(), pet, xi.attackType.MAGICAL, xi.damageType.DARK) returnParam = xi.effect.KO else From 2e2dddd40461631be8d50ac4b94419844403520b Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 07:17:29 +0000 Subject: [PATCH 4/7] Removing Astral Flow on usage and stopping Odin from attacking mobs --- scripts/actions/abilities/pets/zantetsuken.lua | 2 ++ src/map/ai/states/petskill_state.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/actions/abilities/pets/zantetsuken.lua b/scripts/actions/abilities/pets/zantetsuken.lua index c928eafe419..7812a584bf7 100644 --- a/scripts/actions/abilities/pets/zantetsuken.lua +++ b/scripts/actions/abilities/pets/zantetsuken.lua @@ -22,6 +22,8 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) local returnParam = 0 local power = summoner:getMP() / utils.clamp(summoner:getMaxMP(), 1, 9999) + summoner:delStatusEffect(xi.effect.ASTRAL_FLOW) + if target:isNM() then local params = {} diff --git a/src/map/ai/states/petskill_state.cpp b/src/map/ai/states/petskill_state.cpp index 116e9d653c4..dfa47c9e567 100644 --- a/src/map/ai/states/petskill_state.cpp +++ b/src/map/ai/states/petskill_state.cpp @@ -168,7 +168,7 @@ bool CPetSkillState::Update(timer::time_point tick) PSummoner->StatusEffectContainer->GetStatusEffect(xi::StatusEffect::AvatarsFavor)->SetPower(power > 11 ? power : 11); } - if (PTarget && m_PEntity->getPetType() == PET_TYPE::AVATAR && (m_PEntity->petID() != PETID_ALEXANDER && m_PEntity->petID() != PETID_ATOMOS)) + if (PTarget && m_PEntity->getPetType() == PET_TYPE::AVATAR && (m_PEntity->petID() != PETID_ALEXANDER && m_PEntity->petID() != PETID_ATOMOS && m_PEntity->petID() != PETID_ODIN)) { auto* PBattleTarget = dynamic_cast(PTarget); if (PBattleTarget && From 65f96acdabb278af9d98f8b2443d7f5c14617149 Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 17:33:53 +0000 Subject: [PATCH 5/7] Testing around behaviors and edge cases revealed some needed changes --- .../actions/abilities/pets/chronoshift.lua | 6 ---- .../actions/abilities/pets/deconstruction.lua | 4 --- .../actions/abilities/pets/zantetsuken.lua | 7 ---- scripts/globals/pets.lua | 35 ++++++++++++++----- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/scripts/actions/abilities/pets/chronoshift.lua b/scripts/actions/abilities/pets/chronoshift.lua index b72ce87dfcf..d0bdfe037ee 100644 --- a/scripts/actions/abilities/pets/chronoshift.lua +++ b/scripts/actions/abilities/pets/chronoshift.lua @@ -24,12 +24,6 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) petskill:setMsg(xi.msg.basic.NO_EFFECT) end - pet:timer(5000, function() - if summoner then - summoner:despawnPet() - end - end) - return effectCount end diff --git a/scripts/actions/abilities/pets/deconstruction.lua b/scripts/actions/abilities/pets/deconstruction.lua index 603fd8d2c72..39c7f5781b8 100644 --- a/scripts/actions/abilities/pets/deconstruction.lua +++ b/scripts/actions/abilities/pets/deconstruction.lua @@ -26,10 +26,6 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) target:addEnmity(pet, 1, 60) target:addEnmity(summoner, 1, 0) -- this is to ensure you cannot cheese mobs with this, mob goes passive if not added - pet:timer(5000, function() - pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) - end) - return effectCount end diff --git a/scripts/actions/abilities/pets/zantetsuken.lua b/scripts/actions/abilities/pets/zantetsuken.lua index 7812a584bf7..b6db0fe309d 100644 --- a/scripts/actions/abilities/pets/zantetsuken.lua +++ b/scripts/actions/abilities/pets/zantetsuken.lua @@ -65,13 +65,6 @@ abilityObject.onPetAbility = function(target, pet, petskill, summoner, action) summoner:setMP(0) - -- Despawn Odin after 8 seconds. - pet:timer(8000, function() - if summoner then - summoner:despawnPet() - end - end) - return returnParam end diff --git a/scripts/globals/pets.lua b/scripts/globals/pets.lua index 72cc416a624..6b2564385c2 100644 --- a/scripts/globals/pets.lua +++ b/scripts/globals/pets.lua @@ -116,25 +116,42 @@ xi.pet.spawnPet = function(caster, petID, state, target) end) end elseif petID == xi.petId.ODIN then - if target then - local pet = caster:getPet() - if pet then + local pet = caster:getPet() + if pet then + if target then pet:timer(5000, function() pet:usePetAbility(xi.jobAbility.ZANTETSUKEN, target) end) end + -- Retail Odin despawns 15 seconds after appearing even if he fails to use Zantetsuken + pet:timer(15000, function() + caster:despawnPet() + end) end elseif petID == xi.petId.ATOMOS then - if target then - -- Use Deconstruction on the target 3 seconds after spawning. - local pet = caster:getPet() - if pet then - -- Timed sequence after spawning, wait -> Deconstruction -> wait -> Chronoshift (despawn pet after complete) - -- Full cycle takes 5s -> 5s -> 5s from summon to despawn + local pet = caster:getPet() + if pet then + -- Timed sequence after spawning, wait -> Deconstruction -> wait -> Chronoshift -> despawn + -- Full cycle takes 5s -> 5s -> 5s from summon to despawn + charge times which ends up at about 17s + -- On retail the behavior appears to be that Atomos will always do Deconstruction if the target is in range + -- If the target moves out of range, Atomos still does Chronoshift as long as the summoner is within 30 yalms + -- If the target and the summoner are both out of range then Atomos just waits and despawns + if target then pet:timer(5000, function() pet:usePetAbility(xi.jobAbility.DECONSTRUCTION, target) end) end + -- 11 seconds here gives time for the animation to consistently line up to retail + pet:timer(11000, function() + -- Chronoshift requires the summoner be within 30 yalms + if caster:checkDistance(pet) <= 30 then + pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) + end + end) + -- Ensure Atomos always despawns + pet:timer(17000, function() + caster:despawnPet() + end) end end end From fc42f53247692a972c4854a4b976e05c7a64dd74 Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 17:40:41 +0000 Subject: [PATCH 6/7] Was just max range for packets --- scripts/globals/pets.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/globals/pets.lua b/scripts/globals/pets.lua index 6b2564385c2..98be23dec1d 100644 --- a/scripts/globals/pets.lua +++ b/scripts/globals/pets.lua @@ -143,10 +143,7 @@ xi.pet.spawnPet = function(caster, petID, state, target) end -- 11 seconds here gives time for the animation to consistently line up to retail pet:timer(11000, function() - -- Chronoshift requires the summoner be within 30 yalms - if caster:checkDistance(pet) <= 30 then - pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) - end + pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) end) -- Ensure Atomos always despawns pet:timer(17000, function() From e12263c728d560f8542e63ba5f4bd9ed0c87b3c8 Mon Sep 17 00:00:00 2001 From: Genoxd Date: Fri, 17 Jul 2026 20:08:39 +0000 Subject: [PATCH 7/7] target validity checks in timer --- scripts/globals/pets.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/globals/pets.lua b/scripts/globals/pets.lua index 98be23dec1d..2c7f74fb9e3 100644 --- a/scripts/globals/pets.lua +++ b/scripts/globals/pets.lua @@ -118,11 +118,11 @@ xi.pet.spawnPet = function(caster, petID, state, target) elseif petID == xi.petId.ODIN then local pet = caster:getPet() if pet then - if target then - pet:timer(5000, function() + pet:timer(5000, function() + if target and target:isAlive() then pet:usePetAbility(xi.jobAbility.ZANTETSUKEN, target) - end) - end + end + end) -- Retail Odin despawns 15 seconds after appearing even if he fails to use Zantetsuken pet:timer(15000, function() caster:despawnPet() @@ -136,11 +136,11 @@ xi.pet.spawnPet = function(caster, petID, state, target) -- On retail the behavior appears to be that Atomos will always do Deconstruction if the target is in range -- If the target moves out of range, Atomos still does Chronoshift as long as the summoner is within 30 yalms -- If the target and the summoner are both out of range then Atomos just waits and despawns - if target then - pet:timer(5000, function() + pet:timer(5000, function() + if target and target:isAlive() then pet:usePetAbility(xi.jobAbility.DECONSTRUCTION, target) - end) - end + end + end) -- 11 seconds here gives time for the animation to consistently line up to retail pet:timer(11000, function() pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet)