Skip to content

Commit 9bbb8f6

Browse files
author
lefneer311
committed
[core] [lua] abyssea cruor reward system
- Adds a fully configurable system for cruor drops in abyssea zones - Adds base cruor drop range for standard mobs, ephemeral mob multiplier, and the varying cruor amounts based on NM model size - Adds the differing species cruor chain mechanic - Incorporates the effect of silver lights on cruor earning (0-60%) - Incorporates the effect of ebon lights on silver lights (1.0 - 2.0) - Incorporates the effect of the reaper abyssites for 10% bonus (configurable) - Adds bonus exp effect of golden lights (0~50%) - Incorporates the effect of ebon lights on golden lights (1.0 - 2.0) - Resolves SQL table formatting bug for traverser start (bug #9893)
1 parent 3a9a151 commit 9bbb8f6

6 files changed

Lines changed: 238 additions & 3 deletions

File tree

scripts/effects/visitant.lua

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
11
-----------------------------------
22
-- xi.effect.VISITANT
33
-----------------------------------
4+
require('scripts/globals/abyssea')
45
---@type TEffect
56
local effectObject = {}
67

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

10+
local function getGoldenExpBonusPct(player)
11+
local cap = 200
12+
local lightTable = xi.abyssea.getLightsTable(player)
13+
local golden = math.min(lightTable[xi.abyssea.lightType.GOLDEN] or 0, cap)
14+
local ebon = math.min(lightTable[xi.abyssea.lightType.EBON] or 0, cap)
15+
16+
local goldenBonus = (golden / cap) * 50
17+
local ebonFactor = 1 + (ebon / cap)
18+
19+
return math.floor(goldenBonus * ebonFactor)
20+
end
21+
22+
local function updateGoldenExpBonus(player)
23+
local applied = player:getLocalVar('abysseaGoldenExpBonusApplied')
24+
local newBonus = getGoldenExpBonusPct(player)
25+
26+
if applied ~= newBonus then
27+
if applied > 0 then
28+
player:delMod(xi.mod.EXP_BONUS, applied)
29+
end
30+
31+
if newBonus > 0 then
32+
player:addMod(xi.mod.EXP_BONUS, newBonus)
33+
end
34+
35+
player:setLocalVar('abysseaGoldenExpBonusApplied', newBonus)
36+
end
37+
end
38+
939
-- NOTE: Update the last
1040
local reportTimeRemaining
1141
reportTimeRemaining = function(player, effect)
@@ -74,6 +104,7 @@ effectObject.onEffectGain = function(target, effect)
74104
effect:addEffectFlag(xi.effectFlag.HIDE_TIMER)
75105

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

79110
effectObject.onEffectTick = function(target, effect)
@@ -87,6 +118,8 @@ effectObject.onEffectTick = function(target, effect)
87118
xi.abyssea.searingWardTimer(target)
88119
end
89120

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

155+
local appliedGoldenExpBonus = target:getLocalVar('abysseaGoldenExpBonusApplied')
156+
if appliedGoldenExpBonus > 0 then
157+
target:delMod(xi.mod.EXP_BONUS, appliedGoldenExpBonus)
158+
target:setLocalVar('abysseaGoldenExpBonusApplied', 0)
159+
end
160+
122161
-- Reset Abyssea Lights
123162
target:setCharVar('abysseaLights1', 0)
124163
target:setCharVar('abysseaLights2', 0)
164+
165+
-- Reset Cruor reverse-chain progress when visitant status is lost.
166+
target:setCharVar('AbysseaCruorChainCount', 0)
167+
target:setCharVar('AbysseaCruorLastIdentity', 0)
168+
target:setCharVar('AbysseaCruorLastKillTime', 0)
125169
end
126170

127171
return effectObject

scripts/globals/abyssea.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ local function setLightsFromTable(player, lightTable)
10561056
if k <= 4 then
10571057
lightMaskFirst = lightMaskFirst + bit.lshift(lightTable[k], (k - 1) * 8)
10581058
else
1059-
lightMaskSecond = lightMaskSecond + bit.lshift(lightTable[k], (k - 1) * 8)
1059+
lightMaskSecond = lightMaskSecond + bit.lshift(lightTable[k], (k - 5) * 8)
10601060
end
10611061
end
10621062

@@ -1126,7 +1126,9 @@ xi.abyssea.addPlayerLights = function(player, light, amount)
11261126
end
11271127

11281128
xi.abyssea.getLightValue = function(player, light)
1129-
return bit.band(bit.rshift(player:getCharVar('abysseaLights'), (light - 1) * 2), 0xFF)
1129+
local lightTable = xi.abyssea.getLightsTable(player)
1130+
1131+
return lightTable[light] or 0
11301132
end
11311133

11321134
xi.abyssea.canEnterAbyssea = function(player)

scripts/globals/abyssea/cruor.lua

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
-----------------------------------
2+
-- Abyssea Cruor Rewards
3+
-----------------------------------
4+
require('scripts/globals/abyssea')
5+
xi = xi or {}
6+
xi.abyssea = xi.abyssea or {}
7+
xi.abyssea.cruor = xi.abyssea.cruor or {}
8+
9+
local function getConfig(name, default)
10+
local value = xi.settings.main[name]
11+
if value == nil then
12+
return default
13+
end
14+
15+
return value
16+
end
17+
18+
19+
local function getLightBonus(player)
20+
local silverCap = getConfig('ABYSSEA_CRUOR_SILVER_CAP', 200)
21+
local ebonCap = getConfig('ABYSSEA_CRUOR_EBON_CAP', 200)
22+
local silver = math.min(xi.abyssea.getLightValue(player, xi.abyssea.lightType.SILVERY) or 0, silverCap)
23+
local ebon = math.min(xi.abyssea.getLightValue(player, xi.abyssea.lightType.EBON) or 0, ebonCap)
24+
25+
local silverMaxBonus = getConfig('ABYSSEA_CRUOR_SILVER_MAX_BONUS', 0.6)
26+
local ebonAmplifier = getConfig('ABYSSEA_CRUOR_EBON_SILVER_AMPLIFIER', 1.0)
27+
28+
local silverRatio = silver / silverCap
29+
local ebonRatio = ebon / ebonCap
30+
31+
return 1 + silverRatio * silverMaxBonus * (1 + ebonRatio * ebonAmplifier)
32+
end
33+
34+
local function getIdentity(mob)
35+
local identityMode = getConfig('ABYSSEA_CRUOR_CHAIN_IDENTITY_MODE', 'pool')
36+
37+
if identityMode == 'name' then
38+
return mob:getName()
39+
end
40+
41+
return mob:getPool()
42+
end
43+
44+
local function getFamilyModifier(mob)
45+
local modifier = 1.0
46+
47+
-- Ephemeral mobs are documented by community resources as high Cruor/EXP outliers.
48+
if string.find(mob:getName(), 'Ephemeral_', 1, true) then
49+
modifier = modifier * getConfig('ABYSSEA_CRUOR_EPHEMERAL_MULTIPLIER', 3.0)
50+
end
51+
52+
return modifier
53+
end
54+
55+
local function getBaseCruor(mob)
56+
if mob:isNM() then
57+
local size = mob:getModelSize() or 0
58+
local smallThreshold = getConfig('ABYSSEA_CRUOR_NM_SMALL_SIZE', 4)
59+
local mediumThreshold = getConfig('ABYSSEA_CRUOR_NM_MEDIUM_SIZE', 7)
60+
61+
if size <= smallThreshold then
62+
return getConfig('ABYSSEA_CRUOR_NM_BASE_T1', 50)
63+
elseif size <= mediumThreshold then
64+
return getConfig('ABYSSEA_CRUOR_NM_BASE_T2', 65)
65+
end
66+
67+
return getConfig('ABYSSEA_CRUOR_NM_BASE_T3', 80)
68+
end
69+
70+
local levelStep = getConfig('ABYSSEA_CRUOR_LEVEL_STEP', 0)
71+
local minBase = getConfig('ABYSSEA_CRUOR_KILL_BASE_MIN', 10)
72+
local maxBase = getConfig('ABYSSEA_CRUOR_KILL_BASE_MAX', 20)
73+
74+
if maxBase < minBase then
75+
maxBase = minBase
76+
end
77+
78+
local randomizedBase = math.random(minBase, maxBase)
79+
80+
return math.max(1, math.floor((randomizedBase + mob:getMainLvl() * levelStep) * getFamilyModifier(mob)))
81+
end
82+
83+
local function getEligibleMembers(killer)
84+
local members = {}
85+
86+
for _, member in pairs(killer:getAlliance()) do
87+
local visitantEffect = member:getStatusEffect(xi.effect.VISITANT)
88+
if
89+
member:isPC() and
90+
member:getZoneID() == killer:getZoneID() and
91+
visitantEffect and
92+
visitantEffect:getIcon() == xi.effect.VISITANT
93+
then
94+
table.insert(members, member)
95+
end
96+
end
97+
98+
return members
99+
end
100+
101+
local function getKiller(player)
102+
if player and not player:isPC() and player:getAllegiance() == 1 then
103+
local master = player:getMaster()
104+
if master then
105+
return master
106+
end
107+
end
108+
109+
return player
110+
end
111+
112+
xi.abyssea.cruor.onMobDefeat = function(killer, mob)
113+
killer = getKiller(killer)
114+
if not killer or not killer:isPC() then
115+
return
116+
end
117+
118+
local timeout = getConfig('ABYSSEA_CRUOR_CHAIN_TIMEOUT_SEC', 0)
119+
local chainStep = getConfig('ABYSSEA_CRUOR_CHAIN_STEP', 9)
120+
local chainCap = getConfig('ABYSSEA_CRUOR_CHAIN_CAP', 180)
121+
local reaperStep = getConfig('ABYSSEA_CRUOR_REAPER_BONUS_STEP', 0.2)
122+
123+
local now = GetSystemTime()
124+
local identity = tostring(getIdentity(mob))
125+
126+
for _, member in pairs(getEligibleMembers(killer)) do
127+
local lastIdentity = tostring(member:getCharVar('AbysseaCruorLastIdentity'))
128+
local lastKillTime = member:getCharVar('AbysseaCruorLastKillTime')
129+
local chainCount = member:getCharVar('AbysseaCruorChainCount')
130+
131+
if timeout > 0 and lastKillTime > 0 and now - lastKillTime > timeout then
132+
chainCount = 0
133+
end
134+
135+
if lastIdentity == identity then
136+
chainCount = 0
137+
else
138+
chainCount = chainCount + 1
139+
end
140+
141+
local baseCruor = getBaseCruor(mob)
142+
local chainBonus = math.min(chainCount * chainStep, chainCap)
143+
local reaperBonus = 1 + xi.abyssea.getAbyssiteTotal(member, xi.abyssea.abyssiteType.THE_REAPER) * reaperStep
144+
local lightBonus = getLightBonus(member)
145+
local cruorReward = math.floor((baseCruor + chainBonus) * reaperBonus * lightBonus)
146+
147+
member:addCurrency('cruor', cruorReward)
148+
member:messageSpecial(zones[member:getZoneID()].text.CRUOR_OBTAINED, cruorReward)
149+
150+
member:setCharVar('AbysseaCruorChainCount', chainCount)
151+
member:setCharVar('AbysseaCruorLastIdentity', identity)
152+
member:setCharVar('AbysseaCruorLastKillTime', now)
153+
end
154+
end
155+
156+
return xi.abyssea.cruor

scripts/globals/abyssea/lights.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
-- Abyssea Lights Global
33
-----------------------------------
44
require('scripts/globals/abyssea')
5+
require('scripts/globals/abyssea/cruor')
56
-----------------------------------
67
xi = xi or {}
78
xi.abyssea = xi.abyssea or {}
@@ -523,6 +524,12 @@ local lightTypes =
523524
[xi.abyssea.deathType.WS_MAGICAL] = { light = xi.abyssea.lightType.AMBER, lightType = 'amber' }, -- Amber
524525
}
525526

527+
local function isCruorKillRewardsEnabled()
528+
local value = xi.settings.main.ABYSSEA_ENABLE_CRUOR_KILL_REWARDS
529+
530+
return value == true or value == 1
531+
end
532+
526533
xi.abyssea.RemoveDeathListeners = function(mob)
527534
mob:removeListener('ABYSSEA_PHYSICAL_DEATH_CHECK')
528535
mob:removeListener('ABYSSEA_MAGIC_DEATH_CHECK')
@@ -571,6 +578,9 @@ xi.abyssea.AddDeathListeners = function(mob)
571578
deathType = xi.abyssea.deathType.PHYSICAL
572579
end
573580

581+
if isCruorKillRewardsEnabled() then
582+
xi.abyssea.cruor.onMobDefeat(player, mobArg)
583+
end
574584
xi.abyssea.DropLights(player, mobArg:getName(), deathType, mobArg)
575585

576586
xi.abyssea.RemoveDeathListeners(mobArg)

settings/default/main.lua

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,29 @@ xi.settings.main =
109109
-- recomended amount 0 - 100, some lights will cap at 255 while others are less, these are capped automatically
110110
ABYSSEA_BONUSLIGHT_AMOUNT = 0,
111111

112+
-- Abyssea cruor kill rewards enable toggle (true/1 = enabled or false/0 = disabled)
113+
ABYSSEA_ENABLE_CRUOR_KILL_REWARDS = 1, -- Enable or disable cruor rewards in Abyssea zones
114+
-- Base cruor implementation: reward = floor((base + chainBonus) * (silver effect * (ebon bonus)) * reaperBonus)
115+
ABYSSEA_CRUOR_KILL_BASE_MIN = 10, -- Minimum of randomized base cruor reward
116+
ABYSSEA_CRUOR_KILL_BASE_MAX = 20, -- Maximum of randomized base cruor reward
117+
ABYSSEA_CRUOR_LEVEL_STEP = 0, -- Set > 0 only if your server wants explicit level-based scaling for cruor rewards
118+
ABYSSEA_CRUOR_CHAIN_STEP = 9, -- This is the bonus earned each time a mob of a different family from the last is defeated
119+
ABYSSEA_CRUOR_CHAIN_CAP = 180, -- This is the maximum chain bonus value
120+
ABYSSEA_CRUOR_CHAIN_TIMEOUT_SEC = 0, -- 0 disables timeout; reverse-chain persists until same family/name kill or visitant loss (retail-like)
121+
ABYSSEA_CRUOR_CHAIN_IDENTITY_MODE = 'pool', -- accepts pool|name for identifying valid targets to maintain the chain, default pool (retail-like)
122+
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)
123+
ABYSSEA_CRUOR_EPHEMERAL_MULTIPLIER = 3.0, -- Ephemeral mob cruor multiplier, multiplies the result of the randomized base before chain, lights, and atma bonuses
124+
125+
ABYSSEA_CRUOR_SILVER_CAP = 200, -- Maximum silver light; determines how rapidly you can reach the cap and effectiveness per light
126+
ABYSSEA_CRUOR_EBON_CAP = 200, -- Maximum ebon light; determines how rapidly you can reach the cap and effectiveness per light
127+
ABYSSEA_CRUOR_SILVER_MAX_BONUS = 0.6, -- Maximum bonus multiplier to base+chain cruor; 0.6 = 1.6 multiplier (160%)
128+
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)
129+
ABYSSEA_CRUOR_NM_BASE_T1 = 50, -- Cruor base reward for T1 NM sizes (default 0-4, set by ABYSSEA_CRUOR_NM_SMALL_SIZE)
130+
ABYSSEA_CRUOR_NM_BASE_T2 = 65, -- Cruor base reward for T2 NM sizes (default 5-7, set by ABYSSEA_CRUOR_NM_MEDIUM_SIZE)
131+
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)
132+
ABYSSEA_CRUOR_NM_SMALL_SIZE = 4, -- Determines maximum small size threshold for NM rewards
133+
ABYSSEA_CRUOR_NM_MEDIUM_SIZE = 7, -- Determines maximum medium size threshold for NM rewards
134+
112135
-- CHARACTER CONFIG
113136
INITIAL_LEVEL_CAP = 50, -- The initial level cap for new players. There seems to be a hardcap of 255.
114137
MAX_LEVEL = 99, -- Level max of the server, lowers the attainable cap by disabling Limit Break quests.

sql/char_unlocks.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ CREATE TABLE `char_unlocks` (
1313
`campaign_windy` int(10) unsigned NOT NULL DEFAULT 0,
1414
`homepoints` blob DEFAULT NULL,
1515
`survivals` blob DEFAULT NULL,
16-
`traverser_start` TIMESTAMP DEFAULT 0,
16+
`traverser_start` TIMESTAMP NULL DEFAULT NULL,
1717
`traverser_claimed` int(10) unsigned NOT NULL DEFAULT 0,
1818
`abyssea_conflux` blob DEFAULT NULL,
1919
`waypoints` blob DEFAULT NULL,

0 commit comments

Comments
 (0)