Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions scripts/effects/visitant.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
-----------------------------------
-- xi.effect.VISITANT
-----------------------------------
require('scripts/globals/abyssea')
---@type TEffect
local effectObject = {}

local remainingTimeLimits = { 300, 240, 180, 120, 60, 30, 10, 5, 4, 3, 2, 1 }

local function getGoldenExpBonusPct(player)
local cap = 200
local lightTable = xi.abyssea.getLightsTable(player)
local golden = math.min(lightTable[xi.abyssea.lightType.GOLDEN] or 0, cap)
local ebon = math.min(lightTable[xi.abyssea.lightType.EBON] or 0, cap)

local goldenBonus = (golden / cap) * 50
local ebonFactor = 1 + (ebon / cap)

return math.floor(goldenBonus * ebonFactor)
end

local function updateGoldenExpBonus(player)
local applied = player:getLocalVar('abysseaGoldenExpBonusApplied')
local newBonus = getGoldenExpBonusPct(player)

if applied ~= newBonus then
if applied > 0 then
player:delMod(xi.mod.EXP_BONUS, applied)
end

if newBonus > 0 then
player:addMod(xi.mod.EXP_BONUS, newBonus)
end

player:setLocalVar('abysseaGoldenExpBonusApplied', newBonus)
end
end

-- NOTE: Update the last
local reportTimeRemaining
reportTimeRemaining = function(player, effect)
Expand Down Expand Up @@ -74,6 +104,7 @@ effectObject.onEffectGain = function(target, effect)
effect:addEffectFlag(xi.effectFlag.HIDE_TIMER)

target:setLocalVar('lastTimeUpdate', effect:getTimeRemaining() / 1000 + 1)
updateGoldenExpBonus(target)
end

effectObject.onEffectTick = function(target, effect)
Expand All @@ -87,6 +118,8 @@ effectObject.onEffectTick = function(target, effect)
xi.abyssea.searingWardTimer(target)
end

updateGoldenExpBonus(target)

-- Handle Time Remaining Messages. This will no longer be called if the time
-- remaining is less that 30s, as then we move to timers set on the player to
-- ensure that they're displayed at the appropriate timings.
Expand Down Expand Up @@ -119,9 +152,20 @@ effectObject.onEffectLose = function(target, effect)
target:setCharVar('abysseaTimeStored', timeRemaining)
end

local appliedGoldenExpBonus = target:getLocalVar('abysseaGoldenExpBonusApplied')
if appliedGoldenExpBonus > 0 then
target:delMod(xi.mod.EXP_BONUS, appliedGoldenExpBonus)
target:setLocalVar('abysseaGoldenExpBonusApplied', 0)
end

-- Reset Abyssea Lights
target:setCharVar('abysseaLights1', 0)
target:setCharVar('abysseaLights2', 0)

-- Reset Cruor reverse-chain progress when visitant status is lost.
target:setCharVar('AbysseaCruorChainCount', 0)
target:setCharVar('AbysseaCruorLastIdentity', 0)
target:setCharVar('AbysseaCruorLastKillTime', 0)
end

return effectObject
6 changes: 4 additions & 2 deletions scripts/globals/abyssea.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ local function setLightsFromTable(player, lightTable)
if k <= 4 then
lightMaskFirst = lightMaskFirst + bit.lshift(lightTable[k], (k - 1) * 8)
else
lightMaskSecond = lightMaskSecond + bit.lshift(lightTable[k], (k - 1) * 8)
lightMaskSecond = lightMaskSecond + bit.lshift(lightTable[k], (k - 5) * 8)
end
end

Expand Down Expand Up @@ -1126,7 +1126,9 @@ xi.abyssea.addPlayerLights = function(player, light, amount)
end

xi.abyssea.getLightValue = function(player, light)
return bit.band(bit.rshift(player:getCharVar('abysseaLights'), (light - 1) * 2), 0xFF)
local lightTable = xi.abyssea.getLightsTable(player)

return lightTable[light] or 0
end

xi.abyssea.canEnterAbyssea = function(player)
Expand Down
155 changes: 155 additions & 0 deletions scripts/globals/abyssea/cruor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
-----------------------------------
-- Abyssea Cruor Rewards
-----------------------------------
require('scripts/globals/abyssea')
xi = xi or {}
xi.abyssea = xi.abyssea or {}
xi.abyssea.cruor = xi.abyssea.cruor or {}

local function getConfig(name, default)
local value = xi.settings.main[name]
if value == nil then
return default
end

return value
end

local function getLightBonus(player)
local silverCap = getConfig('ABYSSEA_CRUOR_SILVER_CAP', 200)
local ebonCap = getConfig('ABYSSEA_CRUOR_EBON_CAP', 200)
local silver = math.min(xi.abyssea.getLightValue(player, xi.abyssea.lightType.SILVERY) or 0, silverCap)
local ebon = math.min(xi.abyssea.getLightValue(player, xi.abyssea.lightType.EBON) or 0, ebonCap)

local silverMaxBonus = getConfig('ABYSSEA_CRUOR_SILVER_MAX_BONUS', 0.6)
local ebonAmplifier = getConfig('ABYSSEA_CRUOR_EBON_SILVER_AMPLIFIER', 1.0)

local silverRatio = silver / silverCap
local ebonRatio = ebon / ebonCap

return 1 + silverRatio * silverMaxBonus * (1 + ebonRatio * ebonAmplifier)
end

local function getIdentity(mob)
local identityMode = getConfig('ABYSSEA_CRUOR_CHAIN_IDENTITY_MODE', 'pool')

if identityMode == 'name' then
return mob:getName()
end

return mob:getPool()
end

local function getFamilyModifier(mob)
local modifier = 1.0

-- Ephemeral mobs are documented by community resources as high Cruor/EXP outliers.
if string.find(mob:getName(), 'Ephemeral_', 1, true) then
modifier = modifier * getConfig('ABYSSEA_CRUOR_EPHEMERAL_MULTIPLIER', 3.0)
end

return modifier
end

local function getBaseCruor(mob)
if mob:isNM() then
local size = mob:getModelSize() or 0
local smallThreshold = getConfig('ABYSSEA_CRUOR_NM_SMALL_SIZE', 4)
local mediumThreshold = getConfig('ABYSSEA_CRUOR_NM_MEDIUM_SIZE', 7)

if size <= smallThreshold then
return getConfig('ABYSSEA_CRUOR_NM_BASE_T1', 50)
elseif size <= mediumThreshold then
return getConfig('ABYSSEA_CRUOR_NM_BASE_T2', 65)
end

return getConfig('ABYSSEA_CRUOR_NM_BASE_T3', 80)
end

local levelStep = getConfig('ABYSSEA_CRUOR_LEVEL_STEP', 0)
local minBase = getConfig('ABYSSEA_CRUOR_KILL_BASE_MIN', 10)
local maxBase = getConfig('ABYSSEA_CRUOR_KILL_BASE_MAX', 20)

if maxBase < minBase then
maxBase = minBase
end

local randomizedBase = math.random(minBase, maxBase)

return math.max(1, math.floor((randomizedBase + mob:getMainLvl() * levelStep) * getFamilyModifier(mob)))
end

local function getEligibleMembers(killer)
local members = {}

for _, member in pairs(killer:getAlliance()) do
local visitantEffect = member:getStatusEffect(xi.effect.VISITANT)
if
member:isPC() and
member:getZoneID() == killer:getZoneID() and
visitantEffect and
visitantEffect:getIcon() == xi.effect.VISITANT
then
table.insert(members, member)
end
end

return members
end

local function getKiller(player)
if player and not player:isPC() and player:getAllegiance() == 1 then
local master = player:getMaster()
if master then
return master
end
end

return player
end

xi.abyssea.cruor.onMobDefeat = function(killer, mob)
killer = getKiller(killer)
if not killer or not killer:isPC() then
return
end

local timeout = getConfig('ABYSSEA_CRUOR_CHAIN_TIMEOUT_SEC', 0)
local chainStep = getConfig('ABYSSEA_CRUOR_CHAIN_STEP', 9)
local chainCap = getConfig('ABYSSEA_CRUOR_CHAIN_CAP', 180)
local reaperStep = getConfig('ABYSSEA_CRUOR_REAPER_BONUS_STEP', 0.2)

local now = GetSystemTime()
local identity = tostring(getIdentity(mob))

for _, member in pairs(getEligibleMembers(killer)) do
local lastIdentity = tostring(member:getCharVar('AbysseaCruorLastIdentity'))
local lastKillTime = member:getCharVar('AbysseaCruorLastKillTime')
local chainCount = member:getCharVar('AbysseaCruorChainCount')

if timeout > 0 and lastKillTime > 0 and now - lastKillTime > timeout then
chainCount = 0
end

if lastIdentity == identity then
chainCount = 0
else
chainCount = chainCount + 1
end

local baseCruor = getBaseCruor(mob)
local chainBonus = math.min(chainCount * chainStep, chainCap)
local reaperBonus = 1 + xi.abyssea.getAbyssiteTotal(member, xi.abyssea.abyssiteType.THE_REAPER) * reaperStep
local lightBonus = getLightBonus(member)
local cruorReward = math.floor((baseCruor + chainBonus) * reaperBonus * lightBonus)

member:addCurrency('cruor', cruorReward)
member:messageSpecial(zones[member:getZoneID()].text.CRUOR_OBTAINED, cruorReward)

member:setCharVar('AbysseaCruorChainCount', chainCount)
member:setCharVar('AbysseaCruorLastIdentity', identity)
member:setCharVar('AbysseaCruorLastKillTime', now)
end
end

return xi.abyssea.cruor
11 changes: 11 additions & 0 deletions scripts/globals/abyssea/lights.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
-- Abyssea Lights Global
-----------------------------------
require('scripts/globals/abyssea')
require('scripts/globals/abyssea/cruor')
-----------------------------------
xi = xi or {}
xi.abyssea = xi.abyssea or {}
Expand Down Expand Up @@ -523,6 +524,12 @@ local lightTypes =
[xi.abyssea.deathType.WS_MAGICAL] = { light = xi.abyssea.lightType.AMBER, lightType = 'amber' }, -- Amber
}

local function isCruorKillRewardsEnabled()
local value = xi.settings.main.ABYSSEA_ENABLE_CRUOR_KILL_REWARDS

return value == true or value == 1
end

xi.abyssea.RemoveDeathListeners = function(mob)
mob:removeListener('ABYSSEA_PHYSICAL_DEATH_CHECK')
mob:removeListener('ABYSSEA_MAGIC_DEATH_CHECK')
Expand Down Expand Up @@ -571,6 +578,10 @@ xi.abyssea.AddDeathListeners = function(mob)
deathType = xi.abyssea.deathType.PHYSICAL
end

if isCruorKillRewardsEnabled() then
xi.abyssea.cruor.onMobDefeat(player, mobArg)
end

xi.abyssea.DropLights(player, mobArg:getName(), deathType, mobArg)

xi.abyssea.RemoveDeathListeners(mobArg)
Expand Down
23 changes: 23 additions & 0 deletions settings/default/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,29 @@ xi.settings.main =
-- recomended amount 0 - 100, some lights will cap at 255 while others are less, these are capped automatically
ABYSSEA_BONUSLIGHT_AMOUNT = 0,

-- Abyssea cruor kill rewards enable toggle (true/1 = enabled or false/0 = disabled)
ABYSSEA_ENABLE_CRUOR_KILL_REWARDS = 1, -- Enable or disable cruor rewards in Abyssea zones
-- Base cruor implementation: reward = floor((base + chainBonus) * (silver effect * (ebon bonus)) * reaperBonus)
ABYSSEA_CRUOR_KILL_BASE_MIN = 10, -- Minimum of randomized base cruor reward
ABYSSEA_CRUOR_KILL_BASE_MAX = 20, -- Maximum of randomized base cruor reward
ABYSSEA_CRUOR_LEVEL_STEP = 0, -- Set > 0 only if your server wants explicit level-based scaling for cruor rewards
ABYSSEA_CRUOR_CHAIN_STEP = 9, -- This is the bonus earned each time a mob of a different family from the last is defeated
ABYSSEA_CRUOR_CHAIN_CAP = 180, -- This is the maximum chain bonus value
ABYSSEA_CRUOR_CHAIN_TIMEOUT_SEC = 0, -- 0 disables timeout; reverse-chain persists until same family/name kill or visitant loss (retail-like)
ABYSSEA_CRUOR_CHAIN_IDENTITY_MODE = 'pool', -- accepts pool|name for identifying valid targets to maintain the chain, default pool (retail-like)
ABYSSEA_CRUOR_REAPER_BONUS_STEP = 0.1, -- Multiplier at the end of the calculation; eg. 0.2 = 120% and 0.15 = 115%. Default (0.1)
ABYSSEA_CRUOR_EPHEMERAL_MULTIPLIER = 3.0, -- Ephemeral mob cruor multiplier, multiplies the result of the randomized base before chain, lights, and atma bonuses

ABYSSEA_CRUOR_SILVER_CAP = 200, -- Maximum silver light; determines how rapidly you can reach the cap and effectiveness per light
ABYSSEA_CRUOR_EBON_CAP = 200, -- Maximum ebon light; determines how rapidly you can reach the cap and effectiveness per light
ABYSSEA_CRUOR_SILVER_MAX_BONUS = 0.6, -- Maximum bonus multiplier to base+chain cruor; 0.6 = 1.6 multiplier (160%)
ABYSSEA_CRUOR_EBON_SILVER_AMPLIFIER = 1.0, -- Maximum bonus multiplier to silver light multiplier; 1.0 doubles silver effectiveness at cap (0.6 -> 1.2)
ABYSSEA_CRUOR_NM_BASE_T1 = 50, -- Cruor base reward for T1 NM sizes (default 0-4, set by ABYSSEA_CRUOR_NM_SMALL_SIZE)
ABYSSEA_CRUOR_NM_BASE_T2 = 65, -- Cruor base reward for T2 NM sizes (default 5-7, set by ABYSSEA_CRUOR_NM_MEDIUM_SIZE)
ABYSSEA_CRUOR_NM_BASE_T3 = 80, -- Cruor base reward for T3 NM sizes (default 8+, will always be everything larger than ABYSSEA_CRUOR_NM_MEDIUM_SIZE)
ABYSSEA_CRUOR_NM_SMALL_SIZE = 4, -- Determines maximum small size threshold for NM rewards
ABYSSEA_CRUOR_NM_MEDIUM_SIZE = 7, -- Determines maximum medium size threshold for NM rewards

-- CHARACTER CONFIG
INITIAL_LEVEL_CAP = 50, -- The initial level cap for new players. There seems to be a hardcap of 255.
MAX_LEVEL = 99, -- Level max of the server, lowers the attainable cap by disabling Limit Break quests.
Expand Down
2 changes: 1 addition & 1 deletion sql/char_unlocks.sql
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CREATE TABLE `char_unlocks` (
`campaign_windy` int(10) unsigned NOT NULL DEFAULT 0,
`homepoints` blob DEFAULT NULL,
`survivals` blob DEFAULT NULL,
`traverser_start` TIMESTAMP DEFAULT 0,
`traverser_start` TIMESTAMP NULL DEFAULT NULL,
`traverser_claimed` int(10) unsigned NOT NULL DEFAULT 0,
`abyssea_conflux` blob DEFAULT NULL,
`waypoints` blob DEFAULT NULL,
Expand Down
Loading