----------------------------------- -- Pet Global Functions ----------------------------------- require('scripts/globals/nyzul/pathos') ----------------------------------- xi = xi or {} xi.pet = xi.pet or {} local avatarPetIDs = set { xi.petId.CARBUNCLE, xi.petId.FENRIR, xi.petId.IFRIT, xi.petId.TITAN, xi.petId.LEVIATHAN, xi.petId.GARUDA, xi.petId.SHIVA, xi.petId.RAMUH, xi.petId.DIABOLOS, xi.petId.ALEXANDER, xi.petId.ODIN, xi.petId.ATOMOS, xi.petId.CAIT_SITH, xi.petId.SIREN, } local onMasterDeath = function(mob) local pet = mob:getPet() if pet ~= nil and pet:isAlive() then if not pet:isEngaged() then DespawnMob(pet:getID(), 2) end end end local astralOnlySpellIDs = set { xi.magic.spell.ODIN, xi.magic.spell.ALEXANDER, } ---@param target CBaseEntity ---@param mob CBaseEntity ---@param skill CMobSkill ---@return number xi.pet.onMobSkillCheck = function(target, mob, skill) -- block mobskill if mob doesn't have an assigned pet or pet is currently spawned if mob:getPet() == nil or mob:hasPet() then return 1 end return 0 end ---@param caster CBaseEntity ---@param target CBaseEntity ---@param spell CSpell ---@return number xi.pet.onCastingCheck = function(caster, target, spell) local result = 0 if caster:hasPet() then result = xi.msg.basic.ALREADY_HAS_A_PET elseif astralOnlySpellIDs[spell:getID()] and not caster:hasStatusEffect(xi.effect.ASTRAL_FLOW) then result = xi.msg.basic.MAGIC_MUST_ASTRAL_FLOW elseif not caster:canUseMisc(xi.zoneMisc.PET) then result = xi.msg.basic.CANT_BE_USED_IN_AREA elseif caster:getObjType() == xi.objType.PC then result = xi.summon.avatarMiniFightCheck(caster, spell:getID()) else -- non-pc without an attached pet if caster:getPet() == nil then result = 1 end end return result end ---@param caster CBaseEntity ---@param petID number? ---@param state CSpell|CMobSkill? ---@param target CBaseEntity? ---@return nil xi.pet.spawnPet = function(caster, petID, state, target) caster:spawnPet(petID) -- mobs don't emit message when using call beast/wyvern, activate, or summoner spells if caster:getObjType() ~= xi.objType.PC and state then state:setMsg(xi.msg.basic.NONE) if state:getID() == xi.mobSkill.CALL_BEAST then -- bst mob pets despawn if not engaged when owner leaves caster:addListener('DEATH', 'BEASTMASTER_DEATH', onMasterDeath) caster:addListener('DESPAWN', 'BEASTMASTER_DESPAWN', onMasterDeath) end end if avatarPetIDs[petID] then local effect = caster:getStatusEffect(xi.effect.AVATARS_FAVOR) if effect then effect:setPower(1) -- resummon resets effect xi.avatarsFavor.applyAvatarsFavorAuraToPet(caster, effect) xi.avatarsFavor.applyAvatarsFavorDebuffsToPet(caster) end if petID == xi.petId.ALEXANDER then -- Use Perfect Defense 5 seconds after spawning. local pet = caster:getPet() if pet then pet:timer(5000, function() pet:usePetAbility(xi.jobAbility.PERFECT_DEFENSE, pet) end) end elseif petID == xi.petId.ODIN then if target then caster:petAttack(target) --pet:timer(5000, function() -- pet:usePetAbility(xi.jobAbility.ZANTETSUKEN, target) --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) pet:timer(3000, function() pet:usePetAbility(xi.jobAbility.DECONSTRUCTION, target) end) pet:timer(10000, function() pet:usePetAbility(xi.jobAbility.CHRONOSHIFT, pet) end) end end end end -- Nyzul Isle has Pathos set randomly on floors and is recorded as bits in a localvar of the instance if caster:getZoneID() == xi.zone.NYZUL_ISLE then xi.nyzul.addPetSpawnPathos(caster) end end -- TODO should charmed entities lose their buffs when they become uncharmed? xi.pet.applyFamiliarBuffs = function(owner, pet) if not owner or not pet or not pet:isAlive() or pet:getLocalVar('hasFamiliarBuffs') ~= 0 then return end pet:setLocalVar('hasFamiliarBuffs', 1) local familiarBonus = owner:getMod(xi.mod.FAMILIAR_BONUS) if owner:isPC() and pet:isCharmed() then -- extends duration by 25m-30m local minSeconds = 25 * 60 local maxSeconds = 30 * 60 local bonusSeconds = familiarBonus * 60 pet:extendCharm(minSeconds + bonusSeconds, maxSeconds + bonusSeconds) end if familiarBonus > 0 then pet:addMod(xi.mod.HASTE_ABILITY, familiarBonus * 100) end local familiarBoost = 10 local familiarBoostPerc = familiarBoost / 100 local addedHP = pet:getMaxHP() * familiarBoostPerc pet:setMaxHP(pet:getMaxHP() + addedHP) -- technically BASE_HP mod is added back to generate modhp, but close enough -- wakes up pets pet:addHP(addedHP) -- TODO does familiar give some bonus resistance to crowd control? Is it only for mob pets? -- Lots of reports of mobs using Familiar and the pet having higher chance to resist bind/sleep/etc end -- Assigns a pet to the "mob" parameter by adding "offset" to the mob's ID -- will bail out if the offset mob's name doesn't match "petName" parameter, as a sanity check for ID shifts or mobs that have multiple job types in the same zone ---@param mob CBaseEntity ---@param offset number ---@param petName string ---@return nil xi.pet.setMobPet = function(mob, offset, petName) if not mob or mob:getObjType() ~= xi.objType.MOB then return end local pet = GetMobByID(mob:getID() + offset) if not pet or pet:getName() ~= petName then return end if pet:getMaster() or mob:getPet() then return end -- pet is always spawned by master DisallowRespawn(pet:getID(), true) if not mob:isSpawned() and pet:isSpawned() then DespawnMob(pet:getID(), 2) end -- link mob and pet for things like call_beast, summon elemental, etc mob:setPet(pet) end