600 lines
17 KiB
Lua
600 lines
17 KiB
Lua
-----------------------------------
|
|
-- The Voidwalker NM System
|
|
-----------------------------------
|
|
require('scripts/globals/mobs')
|
|
require('scripts/globals/voidwalkerpos')
|
|
-----------------------------------
|
|
xi = xi or {}
|
|
xi.voidwalker = xi.voidwalker or {}
|
|
|
|
local abyssiteKeyitems =
|
|
{
|
|
[9] = xi.keyItem.BLACK_ABYSSITE,
|
|
[8] = xi.keyItem.GREY_ABYSSITE,
|
|
[7] = xi.keyItem.PURPLE_ABYSSITE,
|
|
[6] = xi.keyItem.YELLOW_ABYSSITE,
|
|
[5] = xi.keyItem.BROWN_ABYSSITE,
|
|
[4] = xi.keyItem.ORANGE_ABYSSITE,
|
|
[3] = xi.keyItem.BLUE_ABYSSITE,
|
|
[2] = xi.keyItem.COLORFUL_ABYSSITE,
|
|
[1] = xi.keyItem.CLEAR_ABYSSITE,
|
|
}
|
|
|
|
local abyssiteMessage =
|
|
{
|
|
[xi.keyItem.CLEAR_ABYSSITE] = 0,
|
|
[xi.keyItem.COLORFUL_ABYSSITE] = 1,
|
|
[xi.keyItem.BLUE_ABYSSITE] = 2,
|
|
[xi.keyItem.ORANGE_ABYSSITE] = 2,
|
|
[xi.keyItem.BROWN_ABYSSITE] = 2,
|
|
[xi.keyItem.YELLOW_ABYSSITE] = 2,
|
|
[xi.keyItem.GREY_ABYSSITE] = 2,
|
|
[xi.keyItem.BLACK_ABYSSITE] = 3
|
|
}
|
|
|
|
local function getCurrentKIsBitsFromPlayer(player)
|
|
local results = 0
|
|
|
|
for i, keyitem in ipairs(abyssiteKeyitems) do
|
|
local currentBit = 0
|
|
if player:hasKeyItem(keyitem) then
|
|
currentBit = 1
|
|
end
|
|
|
|
results = results + bit.lshift(currentBit, i - 1)
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
local function getCurrentKIsFromPlayer(player)
|
|
local results = {}
|
|
|
|
for i, keyitem in ipairs(abyssiteKeyitems) do
|
|
if player:hasKeyItem(keyitem) then
|
|
table.insert(results, keyitem)
|
|
end
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
local function getMobsFromAbyssites(zoneId, abyssites)
|
|
local results = {}
|
|
|
|
for i, keyitem in ipairs(abyssites) do
|
|
if
|
|
zones[zoneId] and
|
|
zones[zoneId].mob and
|
|
zones[zoneId].mob.VOIDWALKER[keyitem]
|
|
then
|
|
for _, mobId in ipairs(zones[zoneId].mob.VOIDWALKER[keyitem]) do
|
|
local mob = GetMobByID(mobId)
|
|
|
|
if mob and mob:isAlive() and mob:getLocalVar('[VoidWalker]PopedBy') == 0 then
|
|
table.insert(results, { mobId = mobId, keyItem = keyitem })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
local function removeMobIdFromPos(zoneId, mobId)
|
|
for i, pos in ipairs(xi.voidwalker.pos[zoneId]) do
|
|
if pos.mobId == mobId then
|
|
xi.voidwalker.pos[zoneId][i].mobId = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function searchEmptyPos(zoneId)
|
|
local maxPos = #xi.voidwalker.pos[zoneId]
|
|
local pos = math.randomInt(1, maxPos)
|
|
local currentPos = xi.voidwalker.pos[zoneId][pos]
|
|
|
|
if currentPos.mobId == nil then
|
|
return pos
|
|
else
|
|
return searchEmptyPos(zoneId)
|
|
end
|
|
end
|
|
|
|
local function setRandomPos(zoneId, mobId)
|
|
local mob = GetMobByID(mobId)
|
|
|
|
if
|
|
not mob or
|
|
not xi.voidwalker.pos[zoneId]
|
|
then
|
|
return
|
|
end
|
|
|
|
local pos = searchEmptyPos(zoneId)
|
|
|
|
xi.voidwalker.pos[zoneId][pos].mobId = mobId
|
|
local vPos = xi.voidwalker.pos[zoneId][pos].pos
|
|
|
|
mob:setSpawn(vPos[1], vPos[2], vPos[3])
|
|
mob:setPos(vPos[1], vPos[2], vPos[3])
|
|
end
|
|
|
|
local getNearestMob = function(player, mobs)
|
|
local results = {}
|
|
|
|
for _, v in ipairs(mobs) do
|
|
local mob = GetMobByID(v.mobId)
|
|
local distance = player:checkDistance(mob)
|
|
|
|
table.insert(results, { mobId = v.mobId, keyItem = v.keyItem, distance = distance })
|
|
end
|
|
|
|
table.sort(results, function(a, b)
|
|
return a.distance < b.distance
|
|
end)
|
|
|
|
if #results > 0 then
|
|
return results[1]
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local getDirection = function(player, mob, distance)
|
|
local posPlayer = player:getPos()
|
|
local posMob = mob:getPos()
|
|
local diffx = posMob.x - posPlayer.x
|
|
local diffz = posMob.z - posPlayer.z
|
|
local tan = math.atan(diffz / diffx)
|
|
local degree = math.deg(tan)
|
|
|
|
if degree < 0 then
|
|
degree = degree * -1
|
|
end
|
|
|
|
local minDegree = 20
|
|
local maxDegree = 70
|
|
|
|
-- Degree >= 70
|
|
if degree >= maxDegree then
|
|
if diffz >= 0 then
|
|
return 6
|
|
else
|
|
return 2
|
|
end
|
|
|
|
-- Degree <= 20
|
|
elseif degree <= minDegree then
|
|
if diffx >= 0 then
|
|
return 0
|
|
else
|
|
return 4
|
|
end
|
|
|
|
-- Degree between 20 and 70
|
|
else
|
|
if diffz >= 0 then
|
|
if diffx >= 0 then
|
|
return 7
|
|
else
|
|
return 5
|
|
end
|
|
else
|
|
if diffx >= 0 then
|
|
return 1
|
|
else
|
|
return 3
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- check keyitem upgrade
|
|
-----------------------------------
|
|
local function checkUpgrade(player, mob, nextKeyItem)
|
|
if
|
|
player and
|
|
mob:getZoneID() == player:getZoneID()
|
|
then
|
|
local zoneTextTable = zones[mob:getZoneID()].text
|
|
local currentKeyItem = mob:getLocalVar('[VoidWalker]PopedWith')
|
|
local rand = math.randomInt(1, 10)
|
|
|
|
if rand == 5 then
|
|
if player:hasKeyItem(currentKeyItem) then
|
|
player:delKeyItem(currentKeyItem)
|
|
end
|
|
|
|
if nextKeyItem then
|
|
player:addKeyItem(nextKeyItem)
|
|
|
|
if currentKeyItem == xi.keyItem.CLEAR_ABYSSITE then
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_UPGRADE_KI_1, currentKeyItem, nextKeyItem)
|
|
elseif currentKeyItem == xi.keyItem.COLORFUL_ABYSSITE then
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_UPGRADE_KI_2, currentKeyItem, nextKeyItem)
|
|
elseif nextKeyItem == xi.keyItem.BLACK_ABYSSITE then
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_OBTAIN_KI, nextKeyItem)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- NPC Assai Nybaem
|
|
-----------------------------------
|
|
xi.voidwalker.npcOnTrigger = function(player, npc)
|
|
if xi.settings.main.ENABLE_VOIDWALKER ~= 1 then
|
|
return
|
|
end
|
|
|
|
local currentKIS = getCurrentKIsBitsFromPlayer(player)
|
|
player:startEvent(10120, currentKIS)
|
|
end
|
|
|
|
xi.voidwalker.npcOnEventUpdate = function(player, csid, option, npc)
|
|
local opt = bit.band(option, 0xF)
|
|
|
|
if
|
|
csid == 10120 and
|
|
opt == 3
|
|
then
|
|
local hasGil = player:getGil() >= 1000
|
|
local hasKi = player:hasKeyItem(xi.keyItem.CLEAR_ABYSSITE)
|
|
|
|
if not hasGil then
|
|
player:updateEvent(3)
|
|
elseif hasKi then
|
|
player:updateEvent(2)
|
|
else
|
|
player:updateEvent(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
xi.voidwalker.npcOnEventFinish = function(player, csid, option, npc)
|
|
local opt = bit.band(option, 0xF)
|
|
|
|
if csid == 10120 then
|
|
if opt == 1 then
|
|
local msg = zones[xi.zone.RULUDE_GARDENS]
|
|
local ki = abyssiteKeyitems[1]
|
|
player:delGil(1000)
|
|
player:addKeyItem(ki)
|
|
player:messageSpecial(msg.text.KEYITEM_OBTAINED, ki)
|
|
elseif opt == 2 then
|
|
local numAbyssite = bit.rshift(option, 4)
|
|
player:delKeyItem(abyssiteKeyitems[numAbyssite])
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Zone On Init
|
|
-----------------------------------
|
|
xi.voidwalker.zoneOnInit = function(zone)
|
|
local zoneId = zone:getID()
|
|
local voidwalkerMobs = zones[zoneId].mob.VOIDWALKER
|
|
|
|
for ki, mobs in pairs(voidwalkerMobs) do
|
|
for _, mob in pairs(mobs) do
|
|
setRandomPos(zoneId, mob)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function doMobSkillEveryHPP(mob, every, start, mobskill, condition)
|
|
local mobhpp = mob:getHPP()
|
|
|
|
if
|
|
mobhpp <= start and
|
|
condition
|
|
then
|
|
local mobHppModulo = mobhpp % every
|
|
local startHppModulo = start % every
|
|
local isSame = startHppModulo == mobHppModulo
|
|
|
|
if
|
|
isSame and
|
|
mob:getLocalVar('MOB_SKILL_' .. mobhpp) == 0
|
|
then
|
|
mob:useMobAbility(mobskill)
|
|
mob:setLocalVar('MOB_SKILL_' .. mobhpp, 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function randomly(mob, chance, between, effect, skill)
|
|
if
|
|
math.randomInt(0, 100) <= chance and
|
|
not mob:hasStatusEffect(effect) and
|
|
GetSystemTime() > (mob:getLocalVar('MOBSKILL_TIME') + between)
|
|
then
|
|
mob:setLocalVar('MOBSKILL_USE', 1)
|
|
mob:setLocalVar('MOBSKILL_TIME', GetSystemTime())
|
|
mob:useMobAbility(skill)
|
|
end
|
|
end
|
|
|
|
local function DespawnPet(mob)
|
|
local zoneId = mob:getZoneID()
|
|
local mobId = mob:getID()
|
|
|
|
if zones[zoneId].pet and zones[zoneId].pet[mobId] then
|
|
local petIds = zones[zoneId].pet[mobId]
|
|
|
|
for i, petId in ipairs(petIds) do
|
|
local pet = GetMobByID(petId)
|
|
|
|
if pet then
|
|
DespawnMob(petId)
|
|
pet:setSpawn(mob:getXPos(), mob:getYPos(), mob:getZPos())
|
|
pet:setPos(mob:getXPos(), mob:getYPos(), mob:getZPos())
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local modByMobName =
|
|
{
|
|
['Krabkatoa'] = function(mob)
|
|
mob:addStatusEffect(xi.effect.REGAIN, { power = 10, origin = mob })
|
|
mob:addMod(xi.mod.DOUBLE_ATTACK, 10)
|
|
end,
|
|
|
|
['Tammuz'] = function(mob)
|
|
mob:addStatusEffect(xi.effect.MIGHTY_STRIKES, { power = 1, origin = mob })
|
|
end,
|
|
|
|
['Erebus'] = function(mob)
|
|
mob:addImmunity(xi.immunity.GRAVITY)
|
|
mob:addImmunity(xi.immunity.BIND)
|
|
end,
|
|
|
|
['Raker_Bee'] = function(mob)
|
|
mob:addImmunity(xi.immunity.GRAVITY)
|
|
mob:addImmunity(xi.immunity.BIND)
|
|
end,
|
|
|
|
['Gjenganger'] = function(mob)
|
|
mob:addImmunity(xi.immunity.STUN)
|
|
end,
|
|
}
|
|
|
|
local mixinByMobName =
|
|
{
|
|
['Capricornus'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.MIGHTY_STRIKES_1, not mob:hasStatusEffect(xi.effect.MIGHTY_STRIKES))
|
|
if
|
|
mob:hasStatusEffect(xi.effect.MIGHTY_STRIKES) and
|
|
not xi.combat.behavior.isEntityBusy(mob)
|
|
then
|
|
mob:useMobAbility(xi.mobSkill.RECOIL_DIVE_1)
|
|
end
|
|
end,
|
|
|
|
['Yacumama'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.HUNDRED_FISTS_1, not mob:hasStatusEffect(xi.effect.HUNDRED_FISTS))
|
|
end,
|
|
|
|
['Lamprey_Lord'] = function(mob)
|
|
randomly(mob, 10, 60, xi.effect.BLOOD_WEAPON, xi.mobSkill.BLOOD_WEAPON_1)
|
|
end,
|
|
|
|
['Shoggoth'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.CHAINSPELL_1, not mob:hasStatusEffect(xi.effect.CHAINSPELL))
|
|
end,
|
|
|
|
['Jyeshtha'] = function(mob)
|
|
randomly(mob, 30, 60, xi.mobSkill.MIGHTY_STRIKES_1, xi.mobSkill.MIGHTY_STRIKES_1)
|
|
if
|
|
mob:getLocalVar('MOBSKILL_USE') == 1 and
|
|
not mob:hasStatusEffect(xi.effect.MIGHTY_STRIKES)
|
|
then
|
|
mob:setLocalVar('MOBSKILL_USE', 0)
|
|
end
|
|
end,
|
|
|
|
['Blobdingnag'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 82, xi.mobSkill.CYTOKINESIS, true)
|
|
end,
|
|
|
|
['Farruca_Fly'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.PERFECT_DODGE_1, not mob:hasStatusEffect(xi.effect.PERFECT_DODGE))
|
|
end,
|
|
|
|
['Skuld'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.CHAINSPELL_1, not mob:hasStatusEffect(xi.effect.CHAINSPELL))
|
|
end,
|
|
|
|
['Erebus'] = function(mob)
|
|
randomly(mob, 30, 60, xi.effect.BLOOD_WEAPON, xi.mobSkill.BLOOD_WEAPON_1)
|
|
if
|
|
mob:hasStatusEffect(xi.effect.BLOOD_WEAPON) and
|
|
not mob:hasStatusEffect(xi.effect.HUNDRED_FISTS)
|
|
then
|
|
mob:addStatusEffect(xi.effect.HUNDRED_FISTS, { power = 1, duration = 30, origin = mob })
|
|
end
|
|
end,
|
|
|
|
['Feuerunke'] = function(mob)
|
|
randomly(mob, 30, 60, xi.effect.HUNDRED_FISTS, xi.mobSkill.HUNDRED_FISTS_1)
|
|
end,
|
|
|
|
['Dawon'] = function(mob)
|
|
doMobSkillEveryHPP(mob, 20, 80, xi.mobSkill.PERFECT_DODGE_1, not mob:hasStatusEffect(xi.effect.PERFECT_DODGE))
|
|
end
|
|
}
|
|
|
|
-----------------------------------
|
|
-- Mob On Init
|
|
-----------------------------------
|
|
xi.voidwalker.onMobInitialize = function(mob)
|
|
end
|
|
|
|
xi.voidwalker.onMobSpawn = function(mob)
|
|
local mobName = mob:getName()
|
|
mob:setStatus(xi.status.INVISIBLE)
|
|
mob:hideHP(true)
|
|
mob:hideName(true)
|
|
mob:setUntargetable(true)
|
|
local mods = modByMobName[mobName]
|
|
|
|
if mods then
|
|
mods(mob)
|
|
end
|
|
end
|
|
|
|
xi.voidwalker.onMobFight = function(mob, target)
|
|
local mobName = mob:getName()
|
|
local mixin = mixinByMobName[mobName]
|
|
|
|
if mixin then
|
|
mixin(mob)
|
|
end
|
|
|
|
local poptime = mob:getLocalVar('[VoidWalker]PopedAt')
|
|
local now = GetSystemTime()
|
|
|
|
if
|
|
mob:isSpawned() and
|
|
(
|
|
now > (poptime + 7200) or
|
|
mob:checkDistance(target) > 25
|
|
)
|
|
then
|
|
local zoneTextTable = zones[mob:getZoneID()].text
|
|
|
|
target:messageSpecial(zoneTextTable.VOIDWALKER_DESPAWN)
|
|
DespawnMob(mob:getID())
|
|
end
|
|
end
|
|
|
|
xi.voidwalker.onMobDisengage = function(mob)
|
|
mob:setLocalVar('[VoidWalker]PopedBy', 0)
|
|
mob:setLocalVar('[VoidWalker]checkPopedBy', 0)
|
|
mob:setLocalVar('[VoidWalker]PopedWith', 0)
|
|
mob:setLocalVar('[VoidWalker]PopedAt', 0)
|
|
mob:setLocalVar('MOBSKILL_USE', 0)
|
|
mob:setLocalVar('MOBSKILL_TIME', 0)
|
|
DespawnPet(mob)
|
|
mob:setStatus(xi.status.INVISIBLE)
|
|
mob:hideHP(true)
|
|
mob:hideName(true)
|
|
mob:setUntargetable(true)
|
|
end
|
|
|
|
xi.voidwalker.onMobDespawn = function(mob)
|
|
local zoneId = mob:getZoneID()
|
|
local mobId = mob:getID()
|
|
|
|
removeMobIdFromPos(zoneId, mobId)
|
|
setRandomPos(zoneId, mobId)
|
|
mob:setLocalVar('[VoidWalker]PopedBy', 0)
|
|
mob:setLocalVar('[VoidWalker]checkPopedBy', 0)
|
|
mob:setLocalVar('[VoidWalker]PopedWith', 0)
|
|
mob:setLocalVar('[VoidWalker]PopedAt', 0)
|
|
mob:setLocalVar('MOBSKILL_USE', 0)
|
|
mob:setLocalVar('MOBSKILL_TIME', 0)
|
|
DespawnPet(mob)
|
|
end
|
|
|
|
xi.voidwalker.onMobDeath = function(mob, player, optParams, keyItem)
|
|
if player then
|
|
local popkeyitem = mob:getLocalVar('[VoidWalker]PopedWith')
|
|
|
|
if optParams.isKiller then
|
|
local playerpoped = GetPlayerByID(mob:getLocalVar('[VoidWalker]PopedBy'))
|
|
local alliance = player:getAlliance()
|
|
local outOfParty = true
|
|
|
|
for _, member in pairs(alliance) do
|
|
if
|
|
playerpoped and
|
|
member:getID() == playerpoped:getID()
|
|
then
|
|
outOfParty = false
|
|
break
|
|
end
|
|
end
|
|
|
|
if
|
|
outOfParty and
|
|
playerpoped and
|
|
not playerpoped:hasKeyItem(keyItem)
|
|
then
|
|
checkUpgrade(playerpoped, mob, keyItem)
|
|
end
|
|
end
|
|
|
|
if
|
|
player:hasKeyItem(popkeyitem) and
|
|
not player:hasKeyItem(keyItem)
|
|
then
|
|
checkUpgrade(player, mob, keyItem)
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- onHealing : trigg when player /heal
|
|
-----------------------------------
|
|
xi.voidwalker.onHealing = function(player)
|
|
if xi.settings.main.ENABLE_VOIDWALKER ~= 1 then
|
|
return
|
|
end
|
|
|
|
local zoneId = player:getZoneID()
|
|
local zoneTextTable = zones[zoneId].text
|
|
local abyssites = getCurrentKIsFromPlayer(player)
|
|
|
|
if
|
|
#abyssites == 0 or
|
|
not zones[zoneId].mob or
|
|
not zones[zoneId].mob.VOIDWALKER
|
|
then
|
|
return
|
|
end
|
|
|
|
local mobs = getMobsFromAbyssites(zoneId, abyssites)
|
|
local mobNearest = getNearestMob(player, mobs)
|
|
|
|
if not mobNearest then
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_NO_MOB, abyssites[1])
|
|
elseif mobNearest.distance <= 4 then
|
|
local mob = GetMobByID(mobNearest.mobId)
|
|
if not mob then
|
|
return
|
|
end
|
|
|
|
mob:setLocalVar('[VoidWalker]PopedBy', player:getID())
|
|
mob:setLocalVar('[VoidWalker]PopedWith', mobNearest.keyItem)
|
|
mob:setLocalVar('[VoidWalker]PopedAt', GetSystemTime())
|
|
|
|
if
|
|
mobNearest.keyItem ~= xi.keyItem.CLEAR_ABYSSITE and
|
|
mobNearest.keyItem ~= xi.keyItem.COLORFUL_ABYSSITE
|
|
then
|
|
player:delKeyItem(mobNearest.keyItem)
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_BREAK_KI, mobNearest.keyItem)
|
|
else
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_SPAWN_MOB)
|
|
mob:hideHP(false)
|
|
end
|
|
|
|
mob:hideName(false)
|
|
mob:setUntargetable(false)
|
|
mob:setStatus(xi.status.UPDATE)
|
|
mob:updateClaim(player)
|
|
|
|
elseif mobNearest.distance >= 300 then
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_MOB_TOO_FAR, mobNearest.keyItem)
|
|
|
|
else
|
|
local mob = GetMobByID(mobNearest.mobId)
|
|
local direction = getDirection(player, mob, mobNearest.distance)
|
|
player:messageSpecial(zoneTextTable.VOIDWALKER_MOB_HINT, abyssiteMessage[mobNearest.keyItem], direction, mobNearest.distance, mobNearest.keyItem)
|
|
end
|
|
end
|