landsandboat/scripts/globals/pets.lua
2026-02-23 16:24:04 +00:00

217 lines
6.6 KiB
Lua

-----------------------------------
-- 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