----------------------------------- -- Item Utils (Used by Skill Books) ----------------------------------- xi = xi or {} ---@class xi.itemUtils xi.itemUtils = {} ---@enum xi.itemUtils.removableEffects xi.itemUtils.removableEffects = { xi.effect.PARALYSIS, xi.effect.POISON, xi.effect.BLINDNESS, xi.effect.SILENCE, xi.effect.DISEASE, xi.effect.PETRIFICATION, xi.effect.BIND, xi.effect.WEIGHT, xi.effect.ADDLE, xi.effect.BURN, xi.effect.FROST, xi.effect.CHOKE, xi.effect.RASP, xi.effect.SHOCK, xi.effect.DROWN, xi.effect.DIA, xi.effect.BIO, xi.effect.STR_DOWN, xi.effect.DEX_DOWN, xi.effect.VIT_DOWN, xi.effect.AGI_DOWN, xi.effect.INT_DOWN, xi.effect.MND_DOWN, xi.effect.CHR_DOWN, xi.effect.MAX_HP_DOWN, xi.effect.MAX_MP_DOWN, xi.effect.ATTACK_DOWN, xi.effect.EVASION_DOWN, xi.effect.DEFENSE_DOWN, xi.effect.MAGIC_DEF_DOWN, xi.effect.INHIBIT_TP, xi.effect.MAGIC_ACC_DOWN, xi.effect.MAGIC_ATK_DOWN } ---@nodiscard ---@param target CBaseEntity ---@param foodType xi.foodType ---@return integer xi.itemUtils.foodOnItemCheck = function(target, foodType) local result = 0 local targetRace = target:getRace() local canEatFish = targetRace == xi.race.MITHRA or target:getMod(xi.mod.EAT_RAW_FISH) == 1 local canEatMeat = targetRace == xi.race.GALKA or target:getMod(xi.mod.EAT_RAW_MEAT) == 1 if (foodType == xi.foodType.RAW_FISH and not canEatFish) or (foodType == xi.foodType.RAW_MEAT and not canEatMeat) then result = xi.msg.basic.CANNOT_EAT end if target:hasStatusEffect(xi.effect.FOOD) then result = xi.msg.basic.IS_FULL end return result end ---@nodiscard ---@param target CBaseEntity ---@return integer xi.itemUtils.itemBoxOnItemCheck = function(target) local result = 0 if target:getFreeSlotsCount() == 0 then result = xi.msg.basic.ITEM_NO_USE_INVENTORY end return result end ---@nodiscard ---@param target CBaseEntity ---@param skillID integer ---@return integer xi.itemUtils.skillBookCheck = function(target, skillID) local skill = skillID local mainCap = target:getMaxSkillLevel(target:getMainLvl(), target:getMainJob(), skill) or 0 local subCap = target:getMaxSkillLevel(target:getSubLvl(), target:getSubJob(), skill) or 0 local mainDif = (mainCap * 10) / 10 - (target:getCharSkillLevel(skill) * 10) / 100 local subDif = (subCap * 10) / 10 - (target:getCharSkillLevel(skill) * 10) / 100 local noSkill = 0 if mainCap == 0 then noSkill = noSkill + 1 end if subCap == 0 then noSkill = noSkill + 1 end if noSkill >= 2 then return xi.msg.basic.ITEM_UNABLE_TO_USE end if mainCap > 0 and mainDif <= 0 then return xi.msg.basic.ITEM_UNABLE_TO_USE end if subCap > 0 and mainCap == 0 and subDif <= 0 then return xi.msg.basic.ITEM_UNABLE_TO_USE end return 0 end ---@param target CBaseEntity ---@param skillID integer ---@return nil xi.itemUtils.skillBookUse = function(target, skillID) target:trySkillUp(skillID, target:getMainLvl(), true, true) end -- selects an item from a weighted loot table ---@nodiscard ---@param lootGroup table ---@return integer xi.itemUtils.pickItemRandom = function(lootGroup) local itemId = 0 -- sum weights local max = 0 for i, entry in pairs(lootGroup) do max = max + entry.weight if entry.itemId == nil then print(fmt('[ERROR] xi.itemUtils.pickItemRandom has encountered nil item at index {} of lootGroup', i)) end end local roll = math.random(max) local current = 0 for _, entry in pairs(lootGroup) do current = current + entry.weight if current >= roll then -- xi.item.NONE gives a chance to drop nothing from a group, don't return nil if the itemId is invalid itemId = entry.itemId or 0 break end end return itemId end xi.itemUtils.addItemShield = function(target, power, duration, effect, subPower) if target:hasStatusEffect(effect) then local shield = target:getStatusEffect(effect) local activeshieldpower = shield:getPower() if activeshieldpower > power then target:messageBasic(xi.msg.basic.NO_EFFECT) return end end target:delStatusEffect(xi.effect.PHYSICAL_SHIELD) target:delStatusEffect(xi.effect.MAGIC_SHIELD) target:addStatusEffect(effect, { power = power, duration = duration, origin = target, subPower = subPower }) target:messageBasic(xi.msg.basic.GAINS_EFFECT_OF_STATUS, effect) end xi.itemUtils.addItemEffect = function(target, effect, power, duration, subpower) if target:hasStatusEffect(effect) then local buff = target:getStatusEffect(effect) local effectpower = buff:getPower() if effectpower > power then target:messageBasic(xi.msg.basic.NO_EFFECT) else target:addStatusEffect(effect, { power = power, duration = duration, origin = target, subPower = subpower }) end else target:addStatusEffect(effect, { power = power, duration = duration, origin = target, subPower = subpower }) end end xi.itemUtils.addTwoItemEffects = function(target, effect1, effect2, power1, power2, duration) if target:hasStatusEffect(effect1) then local buff = target:getStatusEffect(effect1) local effectpower = buff:getPower() if effectpower > power1 then target:messageBasic(xi.msg.basic.NO_EFFECT) else target:addStatusEffect(effect1, { power = power1, duration = duration, origin = target, subPower = power1 }) end else target:addStatusEffect(effect1, { power = power1, duration = duration, origin = target, subPower = power1 }) end if target:hasStatusEffect(effect2) then local buff = target:getStatusEffect(effect2) local effectpower = buff:getPower() if effectpower > power2 then target:messageBasic(xi.msg.basic.NO_EFFECT) else target:addStatusEffect(effect2, { power = power2, duration = duration, origin = target, subPower = power2 }) end else target:addStatusEffect(effect2, { power = power2, duration = duration, origin = target, subPower = power2 }) end end xi.itemUtils.addItemExpEffect = function(target, effect, power, duration, subpower) local deleffect = xi.effect.COMMITMENT if effect == deleffect then deleffect = xi.effect.DEDICATION end if target:hasStatusEffect(effect) then local buff = target:getStatusEffect(effect) local effectpower = buff:getPower() if effectpower > power then target:messageBasic(xi.msg.basic.NO_EFFECT) else target:delStatusEffectSilent(deleffect) target:addStatusEffect(effect, { power = power, duration = duration, origin = target, subPower = subpower }) end else target:delStatusEffectSilent(deleffect) target:addStatusEffect(effect, { power = power, duration = duration, origin = target, subPower = subpower }) end end xi.itemUtils.removeStatus = function(target, effects) for _, effect in ipairs(effects) do if target:delStatusEffect(effect) then return true end end if target:eraseStatusEffect() ~= 255 then return true end return false end xi.itemUtils.removeMultipleEffects = function(target, effects, count, random) local effectsToRemove = effects if random == 1 then -- randomize which effects get removed effectsToRemove = utils.shuffle(effects) end if count > 0 then local removed = 0 for i = 0, count do if not xi.itemUtils.removeStatus(target, effectsToRemove) then break end removed = removed + 1 if removed >= count then break end end return removed end end -- for applying pet mods based on arbitrary conditions -- I.E. avatar attack only for a particular pet -- example usage in fervor_ring.lua -- can pass nil for petId to apply to all pets xi.itemUtils.handlePetLatentMods = function(owner, petId, mods, give) local pet = owner:getPet() if pet and (not petId or pet:getPetID() == petId) then if give then for _, mod in ipairs(mods) do pet:addMod(mod[1], mod[2]) end else for _, mod in ipairs(mods) do pet:delMod(mod[1], mod[2]) end end end end