landsandboat/scripts/globals/trust.lua

503 lines
17 KiB
Lua

-----------------------------------
-- Trust
-----------------------------------
require('scripts/globals/magic')
require('scripts/globals/roe')
-----------------------------------
xi = xi or {}
xi.trust = xi.trust or {}
xi.trust.movementType =
{
-- NOTE: If you need to add special movement types, add descending into the minus values.
-- : All of the positive values are taken for the ranged movement range.
-- : See trust_controller.cpp for more.
-- NOTE: You can use any positive value as a distance, and it will act as MID_RANGE or LONG_RANGE, but with the value you've provided.
-- : For example:
-- : mob:setMobMod(xi.mobMod.TRUST_DISTANCE, 20)
-- : Will set the combat distance the trust tries to stick to to 20'
-- NOTE: If a Trust doesn't immediately sprint to a certain distance at the start of battle, it's probably NO_MOVE or MELEE.
NO_MOVE = -1, -- Will stand still providing they're within casting distance of their master and target when the fight starts. Otherwise will reposition to be within 9.0' of both
NON_COMBAT = -2, -- Will follow the master if first trust in party and will follow the trust in front if lower in the list.
MELEE = 0, -- Default: will continually reposition to stay within melee range of the target
MID_RANGE = 6, -- Will path at the start of battle to 6' away from the target, and try to stay at that distance
LONG_RANGE = 12, -- Will path at the start of battle to 12' away from the target, and try to stay at that distance
}
xi.trust.messageOffset =
{
SPAWN = 1,
TEAMWORK_1 = 4,
TEAMWORK_2 = 5,
TEAMWORK_3 = 6,
TEAMWORK_4 = 7,
TEAMWORK_5 = 8,
DEATH = 9,
DESPAWN = 11,
SPECIAL_MOVE_1 = 18,
SPECIAL_MOVE_2 = 19,
}
local maxMessagePage = 121
local rovKIBattlefieldIDs = set{
5, -- Shattering Stars (WAR LB5)
6, -- Shattering Stars (BLM LB5)
7, -- Shattering Stars (RNG LB5)
70, -- Shattering Stars (RDM LB5)
71, -- Shattering Stars (THF LB5)
72, -- Shattering Stars (BST LB5)
101, -- Shattering Stars (MNK LB5)
102, -- Shattering Stars (WHM LB5)
103, -- Shattering Stars (SMN LB5)
163, -- Survival of the Wisest (SCH LB5)
194, -- Shattering Stars (SAM LB5)
195, -- Shattering Stars (NIN LB5)
196, -- Shattering Stars (DRG LB5)
517, -- Shattering Stars (PLD LB5)
518, -- Shattering Stars (DRK LB5)
519, -- Shattering Stars (BRD LB5)
530, -- A Furious Finale (DNC LB5)
1091, -- Breaking the Bonds of Fate (COR LB5)
1123, -- Achieving True Power (PUP LB5)
1154, -- The Beast Within (BLU LB5)
-- TODO: GEO LB5
-- TODO: RUN LB5
}
-- NOTE: Unfortunately, these are not linear, so we have to use
-- a big lookup of offsets instead of a single offset
local poolIDToMessagePageOffset =
{
[5896] = 0, -- Shantotto
[5897] = 1, -- Naji
[5898] = 2, -- Kupipi
[5899] = 3, -- Excenmille
[5900] = 4, -- Ayame
[5901] = 5, -- Nanaa Mihgo
[5902] = 6, -- Curilla
[5903] = 7, -- Volker
[5904] = 8, -- Ajido-Marujido
[5905] = 9, -- Trion
[5906] = 10, -- Zeid
[5907] = 11, -- Lion
[5908] = 12, -- Tenzen
[5909] = 13, -- Mihli Aliapoh
[5910] = 14, -- Valaineral
[5911] = 15, -- Joachim
[5912] = 16, -- Naja Salaheem
[5913] = 17, -- Prishe
[5914] = 18, -- Ulmia
[5915] = 19, -- Shikaree Z
[5916] = 20, -- Cherukiki
[5917] = 21, -- Iron Eater
[5918] = 22, -- Gessho
[5919] = 23, -- Gadalar
[5920] = 24, -- Rainemard
[5921] = 25, -- Ingrid
[5922] = 26, -- Lehko Habhoka
[5923] = 27, -- Nashmeira
[5924] = 28, -- Zazarg
[5925] = 29, -- Ovjang
[5926] = 30, -- Mnejing
[5927] = 31, -- Sakura
[5928] = 32, -- Luzaf
[5929] = 33, -- Najelith
[5930] = 34, -- Aldo
[5931] = 35, -- Moogle
[5932] = 36, -- Fablinix
[5933] = 37, -- Maat
[5934] = 38, -- D.Shantotto
[5935] = 39, -- Star Sibyl
[5936] = 40, -- Karaha-Baruha
[5937] = 41, -- Cid
[5938] = 42, -- Gilgamesh
[5939] = 43, -- Areuhat
[5940] = 44, -- Semih Lafihna
[5941] = 45, -- Elivira
[5942] = 46, -- Noillurie
[5943] = 47, -- Lhu Mhakaracca
[5944] = 48, -- Ferreous Coffi
[5945] = 49, -- Lilisette
[5946] = 50, -- Mumor
[5947] = 51, -- Uka Totlihn
[5948] = 53, -- Klara
[5949] = 54, -- Romaa Mihgo
[5950] = 55, -- Kuyin Hathdenna
[5951] = 56, -- Rahal
[5952] = 57, -- Koru-Moru
[5953] = 58, -- Pieuje UC
[5954] = 60, -- I.Shield UC
[5955] = 61, -- Apururu UC
[5956] = 62, -- Jakoh UC
[5957] = 59, -- Flaviria UC
[5958] = 67, -- Babban
[5959] = 68, -- Abenzio
[5960] = 69, -- Rughadjeen
[5961] = 70, -- Kukki-Chebukki
[5962] = 71, -- Margret
[5963] = 72, -- Chacharoon
[5964] = 73, -- Lhe Lhangavo
[5965] = 74, -- Arciela
[5966] = 75, -- Mayakov
[5967] = 76, -- Qultada
[5968] = 77, -- Adelheid
[5969] = 78, -- Amchuchu
[5970] = 79, -- Brygid
[5971] = 80, -- Mildaurion
[5972] = 87, -- Halver
[5973] = 88, -- Rongelouts
[5974] = 89, -- Leonoyne
[5975] = 90, -- Maximilian
[5976] = 91, -- Kayeel-Payeel
[5977] = 92, -- Robel-Akbel
[5978] = 93, -- Kupofried
[5979] = 94, -- Selhteus
[5980] = 95, -- Yoran-Oran UC
[5981] = 96, -- Sylvie UC
[5982] = 98, -- Abquhbah
[5983] = 99, -- Balamor
[5984] = 100, -- August
[5985] = 101, -- Rosulatia
[5986] = 103, -- Teodor
[5987] = 105, -- Ullegore
[5988] = 106, -- Makki-Chebukki
[5989] = 107, -- King of Hearts
[5990] = 108, -- Morimar
[5991] = 109, -- Darrcuiln
[5992] = 113, -- ArkHM
[5993] = 114, -- ArkEV
[5994] = 115, -- ArkMK
[5995] = 116, -- ArkTT
[5996] = 117, -- ArkGK
[5997] = 110, -- Iroha
[5998] = 118, -- Ygnas
[5999] = 120, -- Monberaux
[6002] = 119, -- Cornelia
[6003] = 121, -- Matsui-P
[6004] = 52, -- Excenmille [S]
[6005] = 63, -- Ayame UC
[6006] = 64, -- Maat UC
[6007] = 65, -- Aldo UC
[6008] = 66, -- Naja UC
[6009] = 81, -- Lion II
[6010] = 86, -- Zeid II
[6011] = 82, -- Prishe II
[6012] = 83, -- Nashmeira II
[6013] = 84, -- Lilisette II
[6014] = 97, -- Tenzen II
[6015] = 104, -- Mumor II
[6016] = 102, -- Ingrid II
[6017] = 85, -- Arciela II
[6018] = 111, -- Iroha II
[6019] = 112, -- Shantotto II
}
-- TODO: handle Dynamis Divergence, Omen, etc that are not "battlefields" but have a trust upper limit.
xi.trust.checkBattlefieldTrustCount = function(caster)
local battlefield = caster:getBattlefield()
if battlefield then
local participants = battlefield:getPlayersAndTrusts()
local maxParticipants = battlefield:getMaxParticipants()
local numPlayers = battlefield:getPlayerCount()
local numTrusts = 0
-- RoV KI Battlefields are limited to LB5 fights which are restricted to
-- one participant which would cause the return to fail. In this case,
-- set the maxParticipants value to 6 to allow summoning.
if
rovKIBattlefieldIDs[battlefield:getID()] and
caster:hasKeyItem(xi.ki.RHAPSODY_IN_UMBER)
then
maxParticipants = 6
end
for _, entity in ipairs(participants) do
local objType = entity:getObjType()
if objType == xi.objType.TRUST then
numTrusts = numTrusts + 1
end
end
return (numPlayers + numTrusts) < maxParticipants
end
return true
end
xi.trust.hasPermit = function(player)
return player:hasKeyItem(xi.ki.WINDURST_TRUST_PERMIT) or
player:hasKeyItem(xi.ki.BASTOK_TRUST_PERMIT) or
player:hasKeyItem(xi.ki.SAN_DORIA_TRUST_PERMIT)
end
xi.trust.onTradeCipher = function(player, trade, csid, rovCs, arkAngelCs)
local itemId = trade:getItemId(0)
local subId = trade:getItemSubId(0)
local isCipher = itemId >= 10112 and itemId <= 10193
-- subId is a smallInt in the database (16 bits).
-- The bottom 12 bits of the subId are the spellId taught by the ciper
-- The top 4 bits of the subId are for the flags to be given to the csid
local spellId = bit.band(subId, 0x0FFF)
local flags = bit.rshift(bit.band(subId, 0xF000), 12)
-- To generate this packed subId for storage in the db:
-- local encoded = spellId + bit.lshift(flags, 12)
-- Cipher type cs args (Wetata's text as example):
-- 0 (add 0) : Did you know that the person mentioned here is also a participant in the Trust initiative?
-- All the stuffiest scholars... (Default)
-- 1 (add 4096) : Wait a second... just who is that? How am I supposed to use <cipher> in conditions like these? (WOTG)
-- 2 (add 8192) : You may be shocked to hear that there are trusts beyond the five races (Beasts & Monsters)
-- 3 (add 12288): How on earth did you get your hands on this? If it's a real cipher I have to try! (Special)
-- 4 (add 16384): Progressed leaps and bounds. You and that person must have something truly special-wecial going on between you.
-- (Mainline story princesses and II trust versions??)
if
xi.trust.hasPermit(player) and
trade:getSlotCount() == 1 and
subId ~= 0 and
isCipher and
not player:hasSpell(spellId)
then
player:setLocalVar('TradingTrustCipher', spellId)
-- TODO Blocking for ROV ciphers
local rovBlock = false
local arkAngelCipher = itemId >= 10188 and itemId <= 10192
if rovBlock then
player:startEvent(rovCs)
elseif arkAngelCipher then
player:startEvent(arkAngelCs, 0, 0, 0, itemId)
else
player:startEvent(csid, 0, 0, flags, itemId)
end
end
end
xi.trust.canCast = function(caster, spell, notAllowedTrustIds)
-- Trusts must be enabled in settings
if xi.settings.main.ENABLE_TRUST_CASTING == 0 then
return xi.msg.basic.TRUST_NO_CAST_TRUST
end
-- GMs can do what they want (as long as ENABLE_TRUST_CASTING is enabled)
if caster:getGMLevel() > 0 and caster:getVisibleGMLevel() >= 3 then
return 0
end
-- Trusts not allowed in an alliance
if caster:checkSoloPartyAlliance() == 2 then
return xi.msg.basic.TRUST_NO_CAST_TRUST
end
-- Trusts only allowed in certain zones (Remove this for trusts everywhere)
if not caster:canUseMisc(xi.zoneMisc.TRUST) then
return xi.msg.basic.TRUST_NO_CALL_AE
end
-- You can only summon trusts if you are the party leader or solo
local leader = caster:getPartyLeader()
if leader and caster:getID() ~= leader:getID() then
caster:messageSystem(xi.msg.system.TRUST_SOLO_OR_LEADER)
return -1
end
-- Block summoning trusts if seeking a party
if caster:isSeekingParty() then
caster:messageSystem(xi.msg.system.TRUST_NO_SEEKING_PARTY)
return -1
end
-- Block summoning trusts if someone recently joined party (120s)
local lastPartyMemberAddedTime = caster:getPartyLastMemberJoinedTime()
if GetSystemTime() - lastPartyMemberAddedTime < 120 then
caster:messageSystem(xi.msg.system.TRUST_DELAY_NEW_PARTY_MEMBER)
return -1
end
-- Trusts cannot be summoned if you have hate
if caster:hasEnmity() then
caster:messageSystem(xi.msg.system.TRUST_NO_ENMITY)
return -1
end
-- Check party for trusts
local numPt = 0
local numTrusts = 0
local party = caster:getPartyWithTrusts()
for _, member in pairs(party) do
if member:getObjType() == xi.objType.TRUST then
-- Check for same trust
if member:getTrustID() == spell:getID() then
caster:messageSystem(xi.msg.system.TRUST_ALREADY_CALLED)
return -1
-- Check not allowed trust combinations (Shantotto I vs Shantotto II)
elseif type(notAllowedTrustIds) == 'number' then
if member:getTrustID() == notAllowedTrustIds then
caster:messageSystem(xi.msg.system.TRUST_ALREADY_CALLED)
return -1
end
elseif type(notAllowedTrustIds) == 'table' then
for _, v in pairs(notAllowedTrustIds) do
if type(v) == 'number' then
if member:getTrustID() == v then
caster:messageSystem(xi.msg.system.TRUST_ALREADY_CALLED)
return -1
end
end
end
end
numTrusts = numTrusts + 1
end
numPt = numPt + 1
end
-- Max party size
if numPt >= 6 then
caster:messageSystem(xi.msg.system.TRUST_MAXIMUM_NUMBER)
return -1
end
-- Some battlefields allow trusts after you get this ROV Key Item, and this
-- rule should take precedence. If it is not one of these, then fall back
-- to checking the battlefield's definitions.
local casterBattlefieldID = caster:getBattlefieldID()
if rovKIBattlefieldIDs[casterBattlefieldID] then
if not caster:hasKeyItem(xi.ki.RHAPSODY_IN_UMBER) then
return xi.msg.basic.TRUST_NO_CAST_TRUST
end
elseif
xi.battlefield.contents[casterBattlefieldID] and
not xi.battlefield.contents[casterBattlefieldID].allowTrusts
then
return xi.msg.basic.TRUST_NO_CAST_TRUST
end
-- Limits set by ROV Key Items
if numTrusts >= 3 and not caster:hasKeyItem(xi.ki.RHAPSODY_IN_WHITE) then
caster:messageSystem(xi.msg.system.TRUST_MAXIMUM_NUMBER)
return -1
elseif numTrusts >= 4 and not caster:hasKeyItem(xi.ki.RHAPSODY_IN_CRIMSON) then
caster:messageSystem(xi.msg.system.TRUST_MAXIMUM_NUMBER)
return -1
end
if not xi.trust.checkBattlefieldTrustCount(caster) then
return xi.msg.basic.TRUST_NO_CAST_TRUST
end
return 0
end
xi.trust.spawn = function(caster, spell)
caster:spawnTrust(spell:getID())
-- Records of Eminence: Call Forth an Alter Ego
if caster:getEminenceProgress(932) then
xi.roe.onRecordTrigger(caster, 932)
end
return 0
end
-- Exponent curves for xi.trust.modGrowthValMax
-- Curve | lvl for val > 0 | lvl for 1/2 full value | lvl for full value
-- 2.0 30 71 99
-- 1.5 20 63 99
-- 1.2 13 56 99
-- 1.0 10 50 99
-- NOTE: This does take into account iLevel, iLevel is different and trust get much more of an aggressive curve.
xi.trust.modGrowthValMax = function(mob, maxVal, optionalCurve)
local lvl = math.max(mob:getMainLvl(), 1) -- Ensure lvl is at least 1
local curve = optionalCurve or 1.5 -- Gentle curve: starts increasing around lvl 20, this needs testing more, but seems to work well at this value.
local progress = (lvl - 1) / 98 -- Normalize level to 0.0 - 1.0 range (98 is the span between 1 and 99)
local exponentGrowth = math.pow(progress, curve)
local val = math.floor(maxVal * exponentGrowth)
return math.max(val, 1)
end
-- Work out the value of aura power for passive trust, default is 1.0 (linear curve)
xi.trust.auraValue = function(lvl, maxVal, optionalCurve)
local bonus = 0
local trustLevel = math.max(lvl, 1) -- Ensure lvl is at least 1
if trustLevel > 99 then
bonus = trustLevel - 99
trustLevel = 99
end
local curve = optionalCurve or 1.0 -- linear curve
local progress = (trustLevel - 1) / 98 -- Normalize level to 0.0 - 1.0 range (98 is the span between 1 and 99)
local exponentGrowth = math.pow(progress, curve)
local val = math.floor(maxVal * exponentGrowth)
return math.max(val + bonus, 1)
end
-- pageOffset is: (summon_message_id - 1) / 100
-- Example: Shantotto II summon message ID: 11201
-- pageOffset: (11201 - 1) / 100 = 112
xi.trust.message = function(mob, messageOffset)
local poolID = mob:getPool()
local pageOffset = poolIDToMessagePageOffset[poolID]
if pageOffset == nil then
print('trust.lua: pageOffset not set for Trust poolID: ' .. poolID)
return
end
if pageOffset > maxMessagePage then
print('trust.lua: maxMessagePage exceeded!')
return
end
local trustOffset = xi.msg.system.GLOBAL_TRUST_OFFSET + (pageOffset * 100)
mob:trustPartyMessage(trustOffset + messageOffset)
end
xi.trust.teamworkMessage = function(mob, teamwork_messages)
local messages = {}
local master = mob:getMaster()
local party = master:getPartyWithTrusts()
for _, member in pairs(party) do
if member:getObjType() == xi.objType.TRUST then
for id, message in pairs(teamwork_messages) do
if member:getTrustID() == id then
table.insert(messages, message)
end
end
end
end
if #messages > 0 then
xi.trust.message(mob, messages[math.random(1, #messages)])
else
-- Defaults to regular spawn message
xi.trust.message(mob, xi.trust.messageOffset.SPAWN)
end
end
-- For debugging and lining up teamwork messages
xi.trust.dumpMessages = function(mob, pageOffset)
for i = 0, 20 do
xi.trust.message(mob, pageOffset)
end
end
xi.trust.dumpMessagePages = function(mob)
for i = 0, 120 do
xi.trust.message(mob, i)
end
end