----------------------------------- -- 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 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