1611 lines
44 KiB
Lua
1611 lines
44 KiB
Lua
-----------------------------------
|
|
-- Monstrosity (MON)
|
|
--
|
|
-- === How does it work? ===
|
|
--
|
|
-- Monstrosity is enabled through two mechanisms: setting your job to JOB_MON (23) and zoning.
|
|
-- Currently, there are some details that seemingly can only be populated at zone-time, so switching in/out
|
|
-- of MON mode is reliant on zoning.
|
|
--
|
|
-- When you zone your job will be checked, and if it is JOB_MON, then PChar->m_PMonstrosity will get
|
|
-- populated with your relevant Monstrosity data from table defined in char_monstrosity.sql. If you don't have
|
|
-- this information yet, it'll be created and saved for you with the defaults (the starting 3 MONs and the basic instincts).
|
|
--
|
|
-- Most other logic for determining stats, exp, exp ranges, traits, etc. will check you are either JOB_MON
|
|
-- or have m_PMonstrosity populated, and then look up what main/sub job your current species is, and then
|
|
-- forward that information into the relevant code for working out stats, etc.
|
|
--
|
|
-- IT IS VITAL that m_PMonstrosity is managed correctly, or that it's existance is constantly checked.
|
|
--
|
|
-- There is _a lot_ of client-side validation for MON, but we have all the information available server-side,
|
|
-- so we make sure to validate everything that comes through the zone_in and MON equip packets. It's also important
|
|
-- to validate all things for MON, because if they're invalid the client will get stuck in a state where they can't change
|
|
-- jobs, species, instincts, or names without GM intervention.
|
|
--
|
|
-- MONs main and subjob are in lock-step, so if you are a MNK15/NIN, the NIN will also be Lv15, and you'll get all the abilities,
|
|
-- traits, and stat contributions (TODO?) from both - except for the 2H which comes from the main job.
|
|
-----------------------------------
|
|
require('scripts/globals/npc_util')
|
|
require('scripts/globals/quests')
|
|
-----------------------------------
|
|
xi = xi or {}
|
|
xi.monstrosity = xi.monstrosity or {}
|
|
|
|
-----------------------------------
|
|
-- Enums
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.species =
|
|
{
|
|
RABBIT = 1,
|
|
BEHEMOTH = 2,
|
|
TIGER = 3,
|
|
SHEEP = 4,
|
|
RAM = 5,
|
|
DHALMEL = 6,
|
|
COEURL = 7,
|
|
OPO_OPO = 8,
|
|
MANTICORE = 9,
|
|
BUFFALO = 10,
|
|
MARID = 11,
|
|
CERBERUS = 12,
|
|
GNOLE = 13,
|
|
|
|
FUNGUAR = 15,
|
|
TREANT_SAPLING = 16,
|
|
MORBOL = 17,
|
|
MANDRAGORA = 18,
|
|
SABOTENDER = 19,
|
|
FLYTRAP = 20,
|
|
GOOBBUE = 21,
|
|
RAFFLESIA = 22,
|
|
PANOPT = 23,
|
|
|
|
BEE = 27,
|
|
BEETLE = 28,
|
|
CRAWLER = 29,
|
|
FLY = 30,
|
|
SCORPION = 31,
|
|
SPIDER = 32,
|
|
ANTLION = 33,
|
|
DIREMITE = 34,
|
|
CHIGOE = 35,
|
|
WAMOURACAMPA = 36,
|
|
LADYBUG = 37,
|
|
GNAT = 38,
|
|
|
|
LIZARD = 43,
|
|
RAPTOR = 44,
|
|
ADAMANTOISE = 45,
|
|
BUGARD = 46,
|
|
EFT = 47,
|
|
WIVRE = 48,
|
|
PEISTE = 49,
|
|
|
|
SLIME = 52,
|
|
HECTEYES = 53,
|
|
FLAN = 54,
|
|
SLUG = 56,
|
|
SANDWORM = 57,
|
|
LEECH = 58,
|
|
|
|
CRAB = 60,
|
|
PUGIL = 61,
|
|
SEA_MONK = 62,
|
|
URAGNITE = 63,
|
|
OROBON = 64,
|
|
RUSZOR = 65,
|
|
TOAD = 66,
|
|
|
|
BIRD = 69,
|
|
COCKATRICE = 70,
|
|
ROC = 71,
|
|
BAT = 72,
|
|
HIPPOGRYPH = 73,
|
|
APKALLU = 74,
|
|
COLIBRI = 75,
|
|
AMPHIPTERE = 76,
|
|
|
|
ASTOLTIAN_SLIME = 126,
|
|
EORZEAN_SPRIGGAN = 127,
|
|
}
|
|
|
|
xi.monstrosity.variants =
|
|
{
|
|
-- Rabbit
|
|
ONYX_RABBIT = 0,
|
|
ALABASTER_RABBIT = 1,
|
|
LAPINION = 2,
|
|
|
|
-- Behemoth
|
|
ELASMOTH = 3,
|
|
|
|
-- Tiger
|
|
LEGENDARY_TIGER = 5,
|
|
SMILODON = 6,
|
|
|
|
-- Sheep
|
|
KARAKUL = 7,
|
|
|
|
-- Coeurl
|
|
LYNX = 10,
|
|
COLLARED_LYNX = 11,
|
|
|
|
-- Manticore
|
|
LEGENDARY_MANTICORE = 12,
|
|
|
|
-- Cerberus
|
|
ORTHRUS = 13,
|
|
|
|
-- Gnole
|
|
BIPEDAL_GNOLE = 14,
|
|
|
|
-- Funguar
|
|
COPPERCAP = 15,
|
|
|
|
-- Treant Sapling
|
|
TREANT = 16,
|
|
FLOWERING_TREANT = 17,
|
|
SCARLET_TINGED_TREANT = 18,
|
|
BARREN_TREANT = 19,
|
|
NECKLACED_TREANT = 20,
|
|
|
|
-- Morbol
|
|
PYGMY_MORBOL = 21,
|
|
SCARE_MORBOL = 22,
|
|
AMERETAT = 23,
|
|
PURBOL = 24,
|
|
|
|
-- Mandragora
|
|
KORRIGAN = 25,
|
|
LYCOPODIUM = 26,
|
|
PYGMY_MANDRAGORA = 27,
|
|
ADENIUM = 28,
|
|
PACHYPODIUM = 29,
|
|
ENLIGHTENED_MANDRAGORA = 30,
|
|
NEW_YEAR_MANDRAGORA = 31,
|
|
|
|
-- Sabotender
|
|
SABOTENDER_FLORIDO = 32,
|
|
|
|
-- Rafflesia
|
|
MITRASTEMA = 33,
|
|
|
|
-- Bee
|
|
VERMILLION_AND_ONYX_BEE = 34,
|
|
ZAFFRE_BEE = 35,
|
|
|
|
-- Beetle
|
|
ONYX_BEETLE = 36,
|
|
GAMBOGE_BEETLE = 37,
|
|
|
|
-- Crawler
|
|
ERUCA = 38,
|
|
EMERALD_CRAWLER = 39,
|
|
PYGMY_EMERALD_CRAWLER = 40,
|
|
|
|
-- Fly
|
|
VERMILLION_FLY = 41,
|
|
|
|
-- Scorpion
|
|
SCOLOPENDRID = 42,
|
|
UNUSUAL_SCOLOPENDRID = 43,
|
|
|
|
-- Spider
|
|
RETICULATED_SPIDER = 44,
|
|
VERMILLION_AND_ONYX_SPIDER = 45,
|
|
|
|
-- Antlion
|
|
ONYX_ANTLION = 46,
|
|
FORMICEROS = 47,
|
|
|
|
-- Diremite
|
|
ARUNDIMITE = 48,
|
|
|
|
-- Chigoe
|
|
AZURE_CHIGOE = 49,
|
|
|
|
-- Wamouracampa
|
|
COILED_WAMOURACAMPA = 50,
|
|
|
|
-- Wamoura
|
|
WAMOURA = 51,
|
|
CORAL_WAMOURA = 52,
|
|
|
|
-- Ladybug
|
|
GOLD_LADYBUG = 53,
|
|
|
|
-- Gnat
|
|
MIDGE = 54,
|
|
|
|
-- Lizard
|
|
ASHEN_LIZARD = 59,
|
|
|
|
-- Raptor
|
|
EMERALD_RAPTOR = 60,
|
|
VERMILLION_RAPTOR = 61,
|
|
|
|
-- Adamantoise
|
|
PYGMY_ADAMANTOISE = 62,
|
|
LEGENDARY_ADAMANTOISE = 63,
|
|
FERROMANTOISE = 64,
|
|
|
|
-- Bugard
|
|
ABYSSOBUGARD = 65,
|
|
|
|
-- Eft
|
|
TARICHUK = 66,
|
|
|
|
-- Wivre
|
|
UNUSUAL_WIVRE = 67,
|
|
|
|
-- Peiste
|
|
SIBILUS = 68,
|
|
|
|
-- Slime
|
|
CLOT = 73,
|
|
GOLD_SLIME = 74,
|
|
BOIL = 75,
|
|
|
|
-- Flan
|
|
GOLD_FLAN = 76,
|
|
BLANCMANGE = 77,
|
|
|
|
-- Sandworm
|
|
PYGMY_SANDWORM = 78,
|
|
GIGAWORM = 79,
|
|
|
|
-- Leech
|
|
AZURE_LEECH = 80,
|
|
OBDELLA = 81,
|
|
|
|
-- Crab
|
|
VERMILLION_CRAB = 84,
|
|
BASKET_BURDENED_CRAB = 85,
|
|
VERMILLION_BASKET_BURDENED_CRAB = 86,
|
|
PORTER_CRAB = 87,
|
|
|
|
-- Pugil
|
|
JAGIL = 88,
|
|
|
|
-- Sea Monk
|
|
AZURE_SEA_MONK = 89,
|
|
|
|
-- Uragnite
|
|
LIMASCABRA = 90,
|
|
|
|
-- Orobon
|
|
PYGMY_OROBON = 91,
|
|
OGREBON = 92,
|
|
|
|
-- Toad
|
|
AZURE_TOAD = 93,
|
|
VERMILLION_TOAD = 94,
|
|
|
|
-- Bird
|
|
ONYX_BIRD = 95,
|
|
|
|
-- Cockatrice
|
|
ZIZ = 96,
|
|
|
|
-- Roc
|
|
LEGENDARY_ROC = 97,
|
|
GAGANA = 98,
|
|
|
|
-- Bat
|
|
BATS = 99,
|
|
VERMILLION_BAT = 100,
|
|
VERMILLION_BATS = 101,
|
|
|
|
-- Apkallu
|
|
INGUZA = 102,
|
|
|
|
-- Colibri
|
|
TOUCALIBRI = 103,
|
|
|
|
-- Amphiptere
|
|
SANGUIPTERE = 104,
|
|
|
|
-- Slime
|
|
SHE_SLIME = 252,
|
|
METAL_SLIME = 253,
|
|
|
|
-- Spriggan
|
|
SPRIGGAN_C = 254,
|
|
SPRIGGAN_G = 255,
|
|
}
|
|
|
|
xi.monstrosity.purchasableInstincts =
|
|
{
|
|
-- Default (0x1F)
|
|
HUME_I = 0,
|
|
ELVAAN_I = 1,
|
|
TARU_I = 2,
|
|
MITHRA_I = 3,
|
|
GALKA_I = 4,
|
|
|
|
HUME_II = 5,
|
|
ELVAAN_II = 6,
|
|
TARU_II = 7,
|
|
MITHRA_II = 8,
|
|
GALKA_II = 9,
|
|
|
|
WAR = 10,
|
|
MNK = 11,
|
|
WHM = 12,
|
|
BLM = 13,
|
|
RDM = 14,
|
|
THF = 15,
|
|
PLD = 16,
|
|
DRK = 17,
|
|
BST = 18,
|
|
BRD = 19,
|
|
RNG = 20,
|
|
SAM = 21,
|
|
NIN = 22,
|
|
DRG = 23,
|
|
SMN = 24,
|
|
BLU = 25,
|
|
COR = 26,
|
|
PUP = 27,
|
|
DNC = 28,
|
|
SCH = 29,
|
|
GEO = 30,
|
|
RUN = 31,
|
|
}
|
|
|
|
local limitBreakQuests =
|
|
{
|
|
[xi.job.BLU] = { xi.questLog.AHT_URHGAN, xi.quest.id.ahtUrhgan.THE_BEAST_WITHIN },
|
|
[xi.job.COR] = { xi.questLog.AHT_URHGAN, xi.quest.id.ahtUrhgan.BREAKING_THE_BONDS_OF_FATE },
|
|
[xi.job.PUP] = { xi.questLog.BASTOK, xi.quest.id.bastok.ACHIEVING_TRUE_POWER },
|
|
[xi.job.DNC] = { xi.questLog.JEUNO, xi.quest.id.jeuno.A_FURIOUS_FINALE },
|
|
[xi.job.SCH] = { xi.questLog.OTHER_AREAS, xi.quest.id.otherAreas.SURVIVAL_OF_THE_WISEST },
|
|
[xi.job.GEO] = { xi.questLog.ADOULIN, xi.quest.id.adoulin.ELEMENTARY_MY_DEAR_SYLVIE },
|
|
[xi.job.RUN] = { xi.questLog.ADOULIN, xi.quest.id.adoulin.ENDEAVORING_TO_AWAKEN },
|
|
}
|
|
|
|
-- NOTE: Cost and granted species/variant are hardcoded into Terynon's event; however, the requirements
|
|
-- to get each of these purchasable MONs is not displayed, and can be modified to a different set or
|
|
-- level. The requirements are limited to species!
|
|
local terynonMonData =
|
|
{
|
|
[0] = -- Beasts
|
|
{
|
|
[0] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.LAPINION,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.RABBIT, 90 },
|
|
},
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.SHEEP,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.BEHEMOTH,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.RABBIT, 75 },
|
|
{ xi.monstrosity.species.OPO_OPO, 75 },
|
|
{ xi.monstrosity.species.GNOLE, 75 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.ELASMOTH,
|
|
infamyCost = 25000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.BEHEMOTH, 50 },
|
|
},
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.CERBERUS,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.BUFFALO, 60 },
|
|
{ xi.monstrosity.species.MANTICORE, 60 },
|
|
{ xi.monstrosity.species.MARID, 60 },
|
|
{ xi.monstrosity.species.SHEEP, 60 },
|
|
{ xi.monstrosity.species.DHALMEL, 60 },
|
|
},
|
|
},
|
|
|
|
[5] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.ORTHRUS,
|
|
infamyCost = 25000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.CERBERUS, 50 },
|
|
},
|
|
},
|
|
},
|
|
|
|
[1] = -- Plantoids
|
|
{
|
|
[0] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PYGMY_MANDRAGORA,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.MANDRAGORA, 45 },
|
|
},
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.TREANT,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PYGMY_MORBOL,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.MORBOL, 1 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PURBOL,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.MORBOL, 75 },
|
|
},
|
|
},
|
|
},
|
|
|
|
[2] = -- Vermin
|
|
{
|
|
[0] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.GOLD_LADYBUG,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.LADYBUG, 50 },
|
|
},
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.BEETLE,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.UNUSUAL_SCOLOPENDRID,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.SCORPION, 60 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.ANTLION,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.SCORPION, 60 },
|
|
},
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.FORMICEROS,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.ANTLION, 60 },
|
|
},
|
|
},
|
|
|
|
[5] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PYGMY_EMERALD_CRAWLER,
|
|
infamyCost = 6000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.CRAWLER, 60 },
|
|
},
|
|
},
|
|
|
|
[6] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.CORAL_WAMOURA,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.WAMOURACAMPA, 60 },
|
|
},
|
|
},
|
|
|
|
[7] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.GNAT,
|
|
infamyCost = 5000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.LADYBUG, 50 },
|
|
{ xi.monstrosity.species.WAMOURACAMPA, 50 },
|
|
},
|
|
},
|
|
},
|
|
|
|
[3] = -- Lizards
|
|
{
|
|
[0] =
|
|
{
|
|
monVariant = xi.monstrosity.species.UNUSUAL_WIVRE,
|
|
infamyCost = 7500,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.WIVRE, 60 },
|
|
},
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.ADAMANTOISE,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.BUGARD, 60 },
|
|
{ xi.monstrosity.species.LIZARD, 60 },
|
|
{ xi.monstrosity.species.WIVRE, 60 },
|
|
},
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.FERROMANTOISE,
|
|
infamyCost = 20000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.ADAMANTOISE, 70 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.RAPTOR,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.PEISTE,
|
|
infamyCost = 8000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.EFT, 50 },
|
|
{ xi.monstrosity.species.RAPTOR, 50 },
|
|
},
|
|
},
|
|
|
|
[5] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.SIBILUS,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.PEISTE, 50 },
|
|
},
|
|
},
|
|
},
|
|
|
|
[4] = -- Amorphs
|
|
{
|
|
[0] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.SLIME,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.BOIL,
|
|
infamyCost = 25000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.SLIME, 50 },
|
|
},
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PYGMY_SANDWORM,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.SANDWORM, 1 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.GIGAWORM,
|
|
infamyCost = 25000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.SANDWORM, 60 },
|
|
},
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.LEECH,
|
|
infamyCost = 2000,
|
|
},
|
|
},
|
|
|
|
[5] = -- Aquans
|
|
{
|
|
[0] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.CRAB,
|
|
infamyCost = 2000,
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.BASKET_BURDENED_CRAB,
|
|
infamyCost = 20000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.CRAB, 1 },
|
|
},
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.VERMILLION_BASKET_BURDENED_CRAB,
|
|
infamyCost = 20000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.CRAB, 15 },
|
|
},
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PORTER_CRAB,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.CRAB, 60 },
|
|
},
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.PUGIL,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[5] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.LIMASCABRA,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.URAGNITE, 50 },
|
|
},
|
|
},
|
|
|
|
[6] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.PYGMY_OROBON,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.OROBON, 1 },
|
|
},
|
|
},
|
|
|
|
[7] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.OGREBON,
|
|
infamyCost = 18000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.OROBON, 50 },
|
|
},
|
|
},
|
|
|
|
[8] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.RUSZOR,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.OROBON, 75 },
|
|
{ xi.monstrosity.species.URAGNITE, 75 },
|
|
},
|
|
},
|
|
},
|
|
|
|
[6] = -- Birds
|
|
{
|
|
[0] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.COCKATRICE,
|
|
infamyCost = 3000,
|
|
},
|
|
|
|
[1] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.GAGANA,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.ROC, 75 },
|
|
},
|
|
},
|
|
|
|
[2] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.BAT,
|
|
infamyCost = 2000,
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.INGUZA,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.APKALLU, 50 },
|
|
},
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.COLIBRI,
|
|
infamyCost = 5000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.BAT, 50 },
|
|
{ xi.monstrosity.species.BIRD, 45 },
|
|
},
|
|
},
|
|
|
|
[5] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.TOUCALIBRI,
|
|
infamyCost = 15000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.COLIBRI, 50 },
|
|
},
|
|
},
|
|
|
|
[6] =
|
|
{
|
|
monSpecies = xi.monstrosity.species.AMPHIPTERE,
|
|
infamyCost = 10000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.COCKATRICE, 75 },
|
|
{ xi.monstrosity.species.ROC, 75 },
|
|
{ xi.monstrosity.species.HIPPOGRYPH, 75 },
|
|
},
|
|
},
|
|
|
|
[7] =
|
|
{
|
|
monVariant = xi.monstrosity.variants.SANGUIPTERE,
|
|
infamyCost = 20000,
|
|
requirements =
|
|
{
|
|
{ xi.monstrosity.species.AMPHIPTERE, 50 },
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
xi.monstrosity.teleports =
|
|
{
|
|
[xi.zone.EAST_RONFAURE] =
|
|
{
|
|
{ 120, 0.5, -530, 192 },
|
|
{ 115, -59.684, 247, 16 },
|
|
},
|
|
|
|
[xi.zone.QUFIM_ISLAND] =
|
|
{
|
|
{ -2, -20.001, 324, 64 },
|
|
{ 161, -20, 37, 192 },
|
|
},
|
|
|
|
[xi.zone.SOUTH_GUSTABERG] =
|
|
{
|
|
{ -115, -0.136, -165, 64 },
|
|
},
|
|
|
|
[xi.zone.VALKURM_DUNES] =
|
|
{
|
|
{ 838, 0, -162, 64 },
|
|
},
|
|
|
|
[xi.zone.WESTERN_ALTEPA_DESERT] =
|
|
{
|
|
{ 685.548, -1.744, -50.395, 128 },
|
|
},
|
|
}
|
|
|
|
-- NOTE: The zones in this list are not customisable, but the level caps are!
|
|
xi.monstrosity.belligerencyCaps =
|
|
{
|
|
[xi.zone.BUBURIMU_PENINSULA] = 30,
|
|
[xi.zone.XARCABARD] = 60,
|
|
[xi.zone.ULEGUERAND_RANGE] = 90,
|
|
}
|
|
|
|
-----------------------------------
|
|
-- Helpers
|
|
-----------------------------------
|
|
-- Use xi.monstrosity.species
|
|
xi.monstrosity.unlockStartingMONs = function(player, choice)
|
|
local data =
|
|
{
|
|
monstrosityId = choice,
|
|
species = choice,
|
|
}
|
|
|
|
player:setMonstrosityData(data)
|
|
end
|
|
|
|
-- Use xi.monstrosity.species
|
|
xi.monstrosity.getSpeciesLevel = function(player, species)
|
|
local data = player:getMonstrosityData()
|
|
return data['levels'][species]
|
|
end
|
|
|
|
-- Use xi.monstrosity.species
|
|
xi.monstrosity.hasUnlockedSpecies = function(player, species)
|
|
return xi.monstrosity.getSpeciesLevel(player, species) > 0
|
|
end
|
|
|
|
-- Use xi.monstrosity.species
|
|
xi.monstrosity.setSpeciesLevel = function(player, species, level)
|
|
local data = player:getMonstrosityData()
|
|
data.levels[species] = level
|
|
player:setMonstrosityData(data)
|
|
end
|
|
|
|
-- Use xi.monstrosity.species
|
|
xi.monstrosity.unlockSpecies = function(player, species)
|
|
if not xi.monstrosity.hasUnlockedSpecies(player, species) then
|
|
xi.monstrosity.setSpeciesLevel(player, species, 1)
|
|
end
|
|
end
|
|
|
|
-- Use xi.monstrosity.variants
|
|
xi.monstrosity.hasUnlockedVariant = function(player, variant)
|
|
local data = player:getMonstrosityData()
|
|
|
|
local byteOffset = math.floor(variant / 8)
|
|
local shiftAmount = variant % 8
|
|
|
|
if byteOffset < 32 then
|
|
return bit.band(data.variants[byteOffset] or 0, bit.lshift(0x01, shiftAmount)) > 0
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Use xi.monstrosity.variants
|
|
xi.monstrosity.unlockVariant = function(player, variant)
|
|
if not xi.monstrosity.hasUnlockedVariant(player, variant) then
|
|
local data = player:getMonstrosityData()
|
|
|
|
local byteOffset = math.floor(variant / 8)
|
|
local shiftAmount = variant % 8
|
|
|
|
if byteOffset < 32 then
|
|
data.variants[byteOffset] = bit.bor(data.variants[byteOffset] or 0, bit.lshift(0x01, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
|
|
player:setMonstrosityData(data)
|
|
end
|
|
end
|
|
|
|
local function hasPurchasedInstinct(player, purchasableInstinctId)
|
|
local data = player:getMonstrosityData()
|
|
local byteOffset = 20 + math.floor(purchasableInstinctId / 8)
|
|
local shiftAmount = purchasableInstinctId % 8
|
|
|
|
if byteOffset >= 20 and byteOffset < 24 then
|
|
return bit.band(data.instincts[byteOffset], bit.lshift(1, shiftAmount)) > 0
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
end
|
|
|
|
local function getPurchasedInstinctsMask(player)
|
|
local instinctMask = 0
|
|
|
|
for _, purchasableInstinctId in pairs(xi.monstrosity.purchasableInstincts) do
|
|
if
|
|
purchasableInstinctId >= xi.monstrosity.purchasableInstincts.HUME_II and
|
|
hasPurchasedInstinct(player, purchasableInstinctId)
|
|
then
|
|
instinctMask = utils.mask.setBit(instinctMask, purchasableInstinctId - xi.monstrosity.purchasableInstincts.HUME_II, true)
|
|
end
|
|
end
|
|
|
|
return instinctMask
|
|
end
|
|
|
|
local function addPurchasedInstinct(player, purchasableInstinctId)
|
|
local data = player:getMonstrosityData()
|
|
local byteOffset = 20 + math.floor(purchasableInstinctId / 8)
|
|
local shiftAmount = purchasableInstinctId % 8
|
|
|
|
if byteOffset >= 20 and byteOffset < 24 then
|
|
data.instincts[byteOffset] = bit.bor(data.instincts[byteOffset] or 0, bit.lshift(0x01, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
|
|
player:setMonstrosityData(data)
|
|
end
|
|
|
|
-- When generating Terynon's mask for discounts, we need a bitmask for
|
|
-- specific jobs. Since only one quest exists for pre-ToAU jobs, use
|
|
-- Maat's Cap tracking for those.
|
|
local function hasCompletedLimitBreak(player, jobId)
|
|
if jobId <= xi.job.SMN then
|
|
local maatsCap = player:getCharVar('maatsCap')
|
|
|
|
return utils.mask.getBit(maatsCap, jobId - 1)
|
|
else
|
|
return player:hasCompletedQuest(unpack(limitBreakQuests[jobId]))
|
|
end
|
|
end
|
|
|
|
local function getLimitBreakMask(player)
|
|
local limitMask = 0
|
|
|
|
for jobId = xi.job.WAR, xi.job.RUN do
|
|
if hasCompletedLimitBreak(player, jobId) then
|
|
limitMask = utils.mask.setBit(limitMask, jobId - 1, true)
|
|
end
|
|
end
|
|
|
|
return limitMask
|
|
end
|
|
|
|
local function hasPurchaseRequirements(player, monCategory, selectedMon)
|
|
local selectedMonData = terynonMonData[monCategory][selectedMon]
|
|
local eligibleSpecies = selectedMonData.monSpecies and xi.monstrosity.getSpeciesLevel(player, selectedMonData.monSpecies) == 0
|
|
local eligibleVariant = selectedMonData.monVariant and not xi.monstrosity.hasUnlockedVariant(player, selectedMonData.monVariant)
|
|
|
|
if
|
|
eligibleSpecies or
|
|
eligibleVariant
|
|
then
|
|
if selectedMonData.requirements then
|
|
for _, reqTable in ipairs(selectedMonData.requirements) do
|
|
if xi.monstrosity.getSpeciesLevel(player, reqTable[1]) < reqTable[2] then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function getMonPageMask(player, monCategory)
|
|
local pageMask = 0
|
|
|
|
if terynonMonData[monCategory] then
|
|
local categoryTable = terynonMonData[monCategory]
|
|
|
|
for bitPos, _ in pairs(categoryTable) do
|
|
if hasPurchaseRequirements(player, monCategory, bitPos) then
|
|
pageMask = utils.mask.setBit(pageMask, bitPos, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
return pageMask
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Bound by C++ (DO NOT CHANGE SIGNATURE)
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.onMonstrosityUpdate = function(player, data)
|
|
-- Tap level-based unlocks
|
|
|
|
-- Instincts by MON level
|
|
-- NOTE: Since this is a bitfield, it's zero-indexed!
|
|
for _, val in pairs(xi.monstrosity.species) do
|
|
local speciesKey = val
|
|
local speciesLevel = data.levels[val]
|
|
local byteOffset = math.floor(speciesKey / 4)
|
|
local unlockAmount = math.floor(speciesLevel / 30)
|
|
local shiftAmount = (speciesKey * 2) % 8
|
|
|
|
-- Special case for writing Slime & Spriggan data at the end of the 64-byte array
|
|
if byteOffset == 31 then
|
|
byteOffset = 63
|
|
end
|
|
|
|
if byteOffset < 64 then
|
|
data.instincts[byteOffset] = bit.bor(data.instincts[byteOffset] or 0, bit.lshift(unlockAmount, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
end
|
|
|
|
-- TODO: Handle level-based variants here
|
|
end
|
|
|
|
xi.monstrosity.onMonstrosityReturnToEntrance = function(player)
|
|
local data = player:getMonstrosityData()
|
|
|
|
local x = data.entry_x
|
|
local y = data.entry_y
|
|
local z = data.entry_z
|
|
local rot = data.entry_rot
|
|
local zoneId = data.entry_zone_id
|
|
local mjob = data.entry_mjob
|
|
local sjob = data.entry_sjob
|
|
|
|
-- TODO: Sanity check
|
|
|
|
for _, effect in pairs(player:getStatusEffects()) do
|
|
player:delStatusEffectSilent(effect:getEffectType())
|
|
end
|
|
|
|
if xi.settings.main.MONSTROSITY_TELEPORT_TO_FERETORY == 1 then
|
|
if player:getZoneID() ~= xi.zone.FERETORY then
|
|
player:setPos(-358, -3.4, -440, 64, xi.zone.FERETORY)
|
|
return
|
|
end
|
|
|
|
-- Otherwise fallthrough and exit as normal
|
|
end
|
|
|
|
player:changeJob(mjob)
|
|
player:changesJob(sjob)
|
|
player:setPos(x, y, z, rot, zoneId)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Relinquish
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.relinquishSteps =
|
|
{
|
|
[0] = function(player)
|
|
player:messageBasic(xi.msg.basic.FERETORY_COUNTDOWN, 0, 4)
|
|
end,
|
|
|
|
[1] = function(player)
|
|
player:messageBasic(xi.msg.basic.FERETORY_COUNTDOWN, 0, 3)
|
|
end,
|
|
|
|
[2] = function(player)
|
|
player:messageBasic(xi.msg.basic.FERETORY_COUNTDOWN, 0, 2)
|
|
end,
|
|
|
|
[3] = function(player)
|
|
player:messageBasic(xi.msg.basic.FERETORY_COUNTDOWN, 0, 1)
|
|
end,
|
|
|
|
[4] = function(player)
|
|
xi.monstrosity.onMonstrosityReturnToEntrance(player)
|
|
end,
|
|
}
|
|
|
|
xi.monstrosity.relinquishFuncBody = function(player)
|
|
-- TODO: Make this countdown interruptable
|
|
player:timer(1000, function(playerArg)
|
|
local step = utils.clamp(playerArg:getLocalVar('RELINQUISH_COUNTDOWN'), 0, 4)
|
|
xi.monstrosity.relinquishSteps[step](playerArg)
|
|
playerArg:setLocalVar('RELINQUISH_COUNTDOWN', step + 1)
|
|
xi.monstrosity.relinquishFuncBody(playerArg)
|
|
end)
|
|
end
|
|
|
|
xi.monstrosity.relinquishOnAbility = function(player, target, ability)
|
|
xi.monstrosity.relinquishFuncBody(player)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Debug
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.unlockAll = function(player)
|
|
-- Complete quest
|
|
local logId = xi.questLog.OTHER_AREAS
|
|
player:completeQuest(logId, xi.quest.id[xi.quest.area[logId]].MONSTROSITY)
|
|
|
|
-- Add Monstrosity key item
|
|
player:addKeyItem(xi.keyItem.RING_OF_SUPERNAL_DISJUNCTION)
|
|
|
|
local data = player:getMonstrosityData()
|
|
|
|
-- Set all levels to 99
|
|
for _, val in pairs(xi.monstrosity.species) do
|
|
data.levels[val] = 99
|
|
end
|
|
|
|
-- Instincts by MON level
|
|
-- NOTE: Since this is a bitfield, it's zero-indexed!
|
|
for _, val in pairs(xi.monstrosity.species) do
|
|
local speciesKey = val
|
|
local speciesLevel = data.levels[val]
|
|
local byteOffset = math.floor(speciesKey / 4)
|
|
local unlockAmount = math.floor(speciesLevel / 30)
|
|
local shiftAmount = (speciesKey * 2) % 8
|
|
|
|
-- Special case for writing Slime & Spriggan data at the end of the 64-byte array
|
|
if byteOffset == 31 then
|
|
byteOffset = 63
|
|
end
|
|
|
|
if byteOffset < 64 then
|
|
data.instincts[byteOffset] = bit.bor(data.instincts[byteOffset] or 0, bit.lshift(unlockAmount, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
end
|
|
|
|
-- Instincts (Purchasable)
|
|
for _, val in pairs(xi.monstrosity.purchasableInstincts) do
|
|
local byteOffset = 20 + math.floor(val / 8)
|
|
local shiftAmount = val % 8
|
|
|
|
if byteOffset >= 20 and byteOffset < 24 then
|
|
data.instincts[byteOffset] = bit.bor(data.instincts[byteOffset] or 0, bit.lshift(0x01, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
end
|
|
|
|
-- Variants
|
|
-- Force unlock all
|
|
for _, val in pairs(xi.monstrosity.variants) do
|
|
local speciesKey = val
|
|
local byteOffset = math.floor(speciesKey / 8)
|
|
local shiftAmount = speciesKey % 8
|
|
|
|
if byteOffset < 32 then
|
|
data.variants[byteOffset] = bit.bor(data.variants[byteOffset] or 0, bit.lshift(0x01, shiftAmount))
|
|
else
|
|
print('byteOffset out of range')
|
|
end
|
|
end
|
|
|
|
-- Set data
|
|
player:setMonstrosityData(data)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Odyssean Passage (Feretory Only)
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.odysseanPassageOnTrade = function(player, npc, trade)
|
|
end
|
|
|
|
xi.monstrosity.odysseanPassageOnTrigger = function(player, npc)
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return
|
|
end
|
|
|
|
local monSize = player:getMonstrositySize()
|
|
local hasBelligerency = player:getBelligerencyFlag() and 1 or 0
|
|
|
|
-- Show the full menu, not the restricted one
|
|
if xi.settings.main.MONSTROSITY_PVP_ZONE_BYPASS == 1 then
|
|
hasBelligerency = 0
|
|
end
|
|
|
|
-- NOTE: The list of available zones is built from the char's list of
|
|
-- visited zones. If you haven't visited any zones in a category it'll back
|
|
-- out immediately.
|
|
-- NOTE: Param5 is not consistent, Bee has seen 0, 1, and 2 so far
|
|
-- player:startEvent(5, 0, 0, 0, 0, 2, 0, 0, 0) -- Bee
|
|
player:startEvent(5, 0, monSize, hasBelligerency, 0, 0, 0, 0, 0)
|
|
end
|
|
|
|
xi.monstrosity.odysseanPassageOnEventUpdate = function(player, csid, option, npc)
|
|
local zoneSelected = bit.rshift(option, 4)
|
|
player:updateEvent(xi.monstrosity.belligerencyCaps[zoneSelected], 0, 0, 0, 1, 0, 0, 0)
|
|
end
|
|
|
|
xi.monstrosity.odysseanPassageOnEventFinish = function(player, csid, option, npc)
|
|
local eventOption = bit.band(option, 0xF)
|
|
local zoneSelected = bit.rshift(option, 4)
|
|
|
|
if eventOption == 1 then
|
|
if zoneSelected == 0 then
|
|
xi.monstrosity.onMonstrosityReturnToEntrance(player)
|
|
else
|
|
if xi.monstrosity.teleports[zoneSelected] then
|
|
local teleportPos = xi.monstrosity.teleports[zoneSelected][math.random(1, #xi.monstrosity.teleports[zoneSelected])]
|
|
|
|
player:setPos(teleportPos[1],
|
|
teleportPos[2],
|
|
teleportPos[3],
|
|
teleportPos[4],
|
|
zoneSelected
|
|
)
|
|
else
|
|
print('Monstrosity Teleport - No Valid Entries for Zone ' .. zoneSelected .. '. Setting pos to (0, 0, 0)!')
|
|
player:setPos(0, 0, 0, 0, zoneSelected)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Feretory
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.feretoryOnZoneIn = function(player, prevZone)
|
|
local cs = -1
|
|
|
|
if
|
|
player:getXPos() == 0 and
|
|
player:getYPos() == 0 and
|
|
player:getZPos() == 0
|
|
then
|
|
player:setPos(-358.000, -3.400, -440.00, 63)
|
|
end
|
|
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return cs
|
|
end
|
|
|
|
if player:getMainJob() ~= xi.job.MON then
|
|
player:changeJob(xi.job.MON)
|
|
end
|
|
|
|
for _, effect in pairs(player:getStatusEffects()) do
|
|
player:delStatusEffectSilent(effect:getEffectType())
|
|
end
|
|
|
|
return cs
|
|
end
|
|
|
|
xi.monstrosity.feretoryOnZoneOut = function(player)
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return
|
|
end
|
|
|
|
-- Mark all status effects so they'll survive zoning
|
|
-- (there are some routines that will force them off anyway)
|
|
for _, effect in pairs(player:getStatusEffects()) do
|
|
effect:delEffectFlag(xi.effectFlag.ON_ZONE)
|
|
effect:delEffectFlag(xi.effectFlag.LOGOUT)
|
|
end
|
|
end
|
|
|
|
xi.monstrosity.feretoryOnEventUpdate = function(player, csid, option, npc)
|
|
end
|
|
|
|
xi.monstrosity.feretoryOnEventFinish = function(player, csid, option, npc)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Aengus (Feretory NPC)
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.aengusOnTrade = function(player, npc, trade)
|
|
end
|
|
|
|
xi.monstrosity.aengusOnTrigger = function(player, npc)
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return
|
|
end
|
|
|
|
local inBelligerency = player:getBelligerencyFlag() and 1 or 0
|
|
player:startEvent(13, inBelligerency, player:getCurrency('infamy'), 0, 0, 0, 0, 0, 0)
|
|
end
|
|
|
|
xi.monstrosity.aengusOnEventUpdate = function(player, csid, option, npc)
|
|
end
|
|
|
|
xi.monstrosity.aengusOnEventFinish = function(player, csid, option, npc)
|
|
if csid == 13 and option == 1 then
|
|
-- Toggle
|
|
player:setBelligerencyFlag(not player:getBelligerencyFlag())
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Teyrnon (Feretory NPC)
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.teyrnonOnTrade = function(player, npc, trade)
|
|
end
|
|
|
|
xi.monstrosity.teyrnonOnTrigger = function(player, npc)
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return
|
|
end
|
|
|
|
player:startEvent(7, player:getCurrency('infamy'), 0, 0, 0, 0, 0, 0, 0)
|
|
end
|
|
|
|
xi.monstrosity.teyrnonOnEventUpdate = function(player, csid, option, npc)
|
|
if csid == 7 then
|
|
local optionType = bit.band(option, 0xFF)
|
|
|
|
if optionType == 0 then
|
|
-- Monsters
|
|
|
|
local monPage = bit.rshift(option, 16)
|
|
local availableMons = getMonPageMask(player, monPage)
|
|
|
|
player:updateEvent(availableMons, 0, 0, 0, 0, 0, 0, 0)
|
|
elseif optionType == 1 then
|
|
-- Instincts
|
|
|
|
local purchasedInstincts = getPurchasedInstinctsMask(player)
|
|
local completedLimits = getLimitBreakMask(player)
|
|
|
|
player:updateEvent(purchasedInstincts, completedLimits, 0, 0, 0, 0, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
xi.monstrosity.teyrnonOnEventFinish = function(player, csid, option, npc)
|
|
local optionType = bit.band(option, 0xFF)
|
|
|
|
if optionType == 1 then
|
|
local selectedCategory = bit.band(bit.rshift(option, 8), 0xF) - 1
|
|
local selectedMon = bit.rshift(option, 16)
|
|
local monData = terynonMonData[selectedCategory][selectedMon]
|
|
|
|
if not monData then
|
|
print(string.format('Invalid Event Finish Option received by Terynon! (%s:%d)', player:getName(), option))
|
|
return
|
|
end
|
|
|
|
if player:getCurrency('infamy') >= monData.infamyCost then
|
|
player:delCurrency('infamy', monData.infamyCost)
|
|
|
|
if monData.monSpecies then
|
|
xi.monstrosity.unlockSpecies(player, monData.monSpecies)
|
|
elseif monData.monVariant then
|
|
xi.monstrosity.unlockVariant(player, monData.monVariant)
|
|
end
|
|
|
|
player:messageSpecial(zones[xi.zone.FERETORY].text.MAY_POSSESS_BEASTS + 3 * selectedCategory, 0, selectedMon)
|
|
else
|
|
player:messageSpecial(zones[xi.zone.FERETORY].text.THY_BRAZEN_DISREGARD)
|
|
end
|
|
|
|
elseif optionType == 2 then
|
|
-- Instincts: Costs are hardcoded, and adjusted based on having completed certain
|
|
-- prerequisites. This data is not tabled with Terynon, as it cannot be controlled.
|
|
|
|
local selectedInstinct = bit.band(bit.rshift(option, 8), 0xFF)
|
|
local instinctPrice = selectedInstinct > xi.monstrosity.purchasableInstincts.GALKA_II and 10000 or 500
|
|
local checkValue = bit.rshift(option, 16)
|
|
|
|
if checkValue ~= 119 then
|
|
print(string.format('Invalid Event Finish Option received by Terynon! (%s:%d)', player:getName(), option))
|
|
return
|
|
end
|
|
|
|
if
|
|
selectedInstinct > xi.monstrosity.purchasableInstincts.GALKA_II and
|
|
hasCompletedLimitBreak(player, selectedInstinct - xi.monstrosity.purchasableInstincts.GALKA_II)
|
|
then
|
|
instinctPrice = instinctPrice / 2
|
|
end
|
|
|
|
if player:getCurrency('infamy') >= instinctPrice then
|
|
player:delCurrency('infamy', instinctPrice)
|
|
addPurchasedInstinct(player, selectedInstinct)
|
|
|
|
-- NOTE: The offset below is the beginning parameter for purchased instincts used by this message, and
|
|
-- lower values will result in an item being placed in the message. Base offset for all instincts
|
|
-- is 29696 (29696 + 3 -> Rabbit Instinct I)
|
|
player:messageSpecial(zones[xi.zone.FERETORY].text.YOU_LEARNED_INSTINCT, 30464 + selectedInstinct)
|
|
else
|
|
player:messageSpecial(zones[xi.zone.FERETORY].text.THY_BRAZEN_DISREGARD)
|
|
end
|
|
|
|
elseif optionType == 3 then
|
|
-- TODO: The casting effects and animations
|
|
|
|
local tryPayCost = function(playerArg, cost)
|
|
if playerArg:getCurrency('infamy') < cost then
|
|
playerArg:messageSpecial(zones[xi.zone.FERETORY].text.THY_BRAZEN_DISREGARD)
|
|
return false
|
|
end
|
|
|
|
playerArg:delCurrency('infamy', cost)
|
|
return true
|
|
end
|
|
|
|
local selectedEffect = bit.rshift(option, 8)
|
|
switch(selectedEffect): caseof
|
|
{
|
|
-- 0: Dedication 1
|
|
-- 50% experience bonus 60 minutes or until a maximum bonus of 10,000 EXP is gained
|
|
[0] = function()
|
|
if not tryPayCost(player, 3000) then
|
|
return
|
|
end
|
|
|
|
local effect = xi.effect.DEDICATION
|
|
local power = 50
|
|
local duration = utils.minutes(60)
|
|
local subpower = 10000
|
|
player:delStatusEffectSilent(power)
|
|
xi.itemUtils.addItemExpEffect(player, effect, power, duration, subpower)
|
|
end,
|
|
|
|
-- 1: Dedication 2
|
|
-- 100% experience bonus 60 minutes or until a maximum bonus of 2,000 EXP is gained
|
|
[1] = function()
|
|
if not tryPayCost(player, 400) then
|
|
return
|
|
end
|
|
|
|
local effect = xi.effect.DEDICATION
|
|
local power = 100
|
|
local duration = utils.minutes(60)
|
|
local subpower = 2000
|
|
player:delStatusEffectSilent(power)
|
|
xi.itemUtils.addItemExpEffect(player, effect, power, duration, subpower)
|
|
end,
|
|
|
|
-- 2: Regen
|
|
[2] = function()
|
|
if not tryPayCost(player, 10) then
|
|
return
|
|
end
|
|
|
|
player:delStatusEffectSilent(xi.effect.REGEN)
|
|
player:addStatusEffect(xi.effect.REGEN, { power = 1, duration = 3600, origin = player, tick = 3 })
|
|
end,
|
|
|
|
-- 3: Refresh
|
|
[3] = function()
|
|
if not tryPayCost(player, 10) then
|
|
return
|
|
end
|
|
|
|
player:delStatusEffectSilent(xi.effect.REFRESH)
|
|
player:addStatusEffect(xi.effect.REFRESH, { power = 1, duration = 3600, origin = player, tick = 3 }) -- Does indeed get overwriten by regular refresh.
|
|
end,
|
|
|
|
-- 4: Protect
|
|
[4] = function()
|
|
if not tryPayCost(player, 100) then
|
|
return
|
|
end
|
|
|
|
local mLvl = player:getMainLvl()
|
|
local power = 220
|
|
local tier = 5
|
|
|
|
if mLvl < 27 then
|
|
power = 20
|
|
tier = 1
|
|
elseif mLvl < 47 then
|
|
power = 50
|
|
tier = 2
|
|
elseif mLvl < 63 then
|
|
power = 90
|
|
tier = 3
|
|
elseif mLvl < 76 then
|
|
power = 140
|
|
tier = 4
|
|
end
|
|
|
|
local bonus = 0
|
|
if player:getMod(xi.mod.ENHANCES_PROT_SHELL_RCVD) > 0 then
|
|
bonus = 2 -- 2x Tier from MOD
|
|
end
|
|
|
|
power = power + (bonus * tier)
|
|
player:delStatusEffectSilent(xi.effect.PROTECT)
|
|
player:addStatusEffect(xi.effect.PROTECT, { power = power, duration = 1800, origin = player, tier = tier })
|
|
end,
|
|
|
|
-- 5: Shell
|
|
[5] = function()
|
|
if not tryPayCost(player, 100) then
|
|
return
|
|
end
|
|
|
|
local mLvl = player:getMainLvl()
|
|
|
|
-- Shell V (75/256)
|
|
local power = 2930
|
|
local tier = 5
|
|
|
|
if mLvl < 37 then
|
|
power = 1055 -- Shell I (27/256)
|
|
tier = 1
|
|
elseif mLvl < 57 then
|
|
power = 1641 -- Shell II (42/256)
|
|
tier = 2
|
|
elseif mLvl < 68 then
|
|
power = 2188 -- Shell III (56/256)
|
|
tier = 3
|
|
elseif mLvl < 76 then
|
|
power = 2617 -- Shell IV (67/256)
|
|
tier = 4
|
|
end
|
|
|
|
local bonus = 0
|
|
if player:getMod(xi.mod.ENHANCES_PROT_SHELL_RCVD) > 0 then
|
|
bonus = 39 -- (1/256 bonus buff per tier of spell)
|
|
end
|
|
|
|
power = power + (bonus * tier)
|
|
player:delStatusEffectSilent(xi.effect.SHELL)
|
|
player:addStatusEffect(xi.effect.SHELL, { power = power, duration = 1800, origin = player, tier = tier })
|
|
end,
|
|
|
|
-- 6: Haste
|
|
[6] = function()
|
|
player:delStatusEffectSilent(xi.effect.HASTE)
|
|
player:addStatusEffect(xi.effect.HASTE, { power = 1000, duration = 600, origin = player })
|
|
end,
|
|
}
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Maccus (Feretory NPC)
|
|
-----------------------------------
|
|
|
|
xi.monstrosity.maccusOnTrade = function(player, npc, trade)
|
|
end
|
|
|
|
xi.monstrosity.maccusOnTrigger = function(player, npc)
|
|
if xi.settings.main.ENABLE_MONSTROSITY ~= 1 then
|
|
return
|
|
end
|
|
|
|
player:startEvent(9, 285, 2, 2, 0, 0, 0, 0, 0)
|
|
end
|
|
|
|
xi.monstrosity.maccusOnEventUpdate = function(player, csid, option, npc)
|
|
-- print('update', csid, option)
|
|
end
|
|
|
|
xi.monstrosity.maccusOnEventFinish = function(player, csid, option, npc)
|
|
-- print('finish', csid, option)
|
|
end
|