912 lines
35 KiB
Lua
912 lines
35 KiB
Lua
-----------------------------------
|
|
-- Global Casket utility script
|
|
-----------------------------------
|
|
require('scripts/globals/casket_loot')
|
|
require('scripts/globals/roe')
|
|
-----------------------------------
|
|
|
|
-----------------------------------
|
|
-- Notes:
|
|
-----------------------------------
|
|
-- chest MiD's
|
|
-- 960 -- Basic Chest
|
|
-- 965 -- Blue Casket
|
|
-- 966 -- Brown Casket
|
|
-- 967 -- Bronze
|
|
-- 968 -- Red
|
|
-- 969 -- Gold
|
|
-- 1524 -- Odd Chest
|
|
-- 1932 -- Black with Red chest
|
|
-- 2425 -- Black with Red chest 2
|
|
-----------------------------------
|
|
xi = xi or {}
|
|
xi.caskets = xi.caskets or {}
|
|
|
|
local casketInfo =
|
|
{
|
|
spawnStatus =
|
|
{
|
|
DESPAWNED = 0,
|
|
SPAWNED_CLOSED = 1,
|
|
SPAWNED_OPEN = 2,
|
|
},
|
|
messageOffset =
|
|
{
|
|
NO_COMBINATION = 6, -- You were unable to enter a combination.
|
|
HUNCH_GREATER_LESS = 7, -- You have a hunch that the lock's combination is ≺0 = GREATER, 1 = LESS≻[greater/less] than ≺INPUT NUMBER≻.
|
|
UNABLE_TO_OPEN_LOCK = 8, -- Player failed to open the lock.
|
|
CORRECT_NUMBER_WAS = 9, -- It appears that the correct combination was ≺NUMBER≻.
|
|
OPENED_LOCK = 10, -- Player succeeded in opening the lock!
|
|
HUNCH_SECOND_EVEN_ODD = 11, -- You have a hunch that the second digit is ≺0 = EVEN, 1 = ODD≻[even/odd].
|
|
HUNCH_FIRST_EVEN_ODD = 12, -- You have a hunch that the first digit is ≺0 = EVEN, 1 = ODD≻[even/odd].
|
|
COMBINATION_GREATER_LESS = 13, -- You have a hunch that the combination is greater than ≺NUMBER≻ and less than ≺NUMBER≻.
|
|
COMBINATION_LESS_THAN = 14, -- You have a hunch that the combination is less than ≺NUMBER≻.
|
|
COMBINATION_GREATER_THAN = 15, -- You have a hunch that the combination is greater than ≺NUMBER≻.
|
|
ONE_OF_TWO_DIGITS_IS = 16, -- You have a hunch that one of the two digits is ≺NUMBER≻.
|
|
SECOND_DIGIT_IS = 17, -- You have a hunch that the second digit is ≺NUMBER≻, ≺NUMBER≻, or ≺NUMBER≻.
|
|
FIRST_DIGIT_IS = 18, -- You have a hunch that the first digit is ≺NUMBER≻, ≺NUMBER≻, or ≺NUMBER≻.
|
|
UNABLE_TO_GET_HINT = 19, -- You were unable to glean anything from your examination of the lock.
|
|
MONSTER_CONCEALED_CHEST = 21, -- The monster was concealing a treasure chest!
|
|
},
|
|
splitZones = set{
|
|
xi.zone.ZERUHN_MINES,
|
|
xi.zone.KORROLOKA_TUNNEL,
|
|
xi.zone.DANGRUF_WADI,
|
|
xi.zone.KING_RANPERRES_TOMB,
|
|
xi.zone.ORDELLES_CAVES,
|
|
xi.zone.OUTER_HORUTOTO_RUINS,
|
|
xi.zone.GUSGEN_MINES,
|
|
xi.zone.MAZE_OF_SHAKHRAMI
|
|
},
|
|
|
|
-- Due to storing itemId for chosen casket items as a localvar, simpler than coding quantity as another localvar
|
|
-- These itemIds give 33 instead of a single
|
|
multipleItems = set{
|
|
xi.item.HANDFUL_OF_STONE_ARROWHEADS,
|
|
xi.item.HANDFUL_OF_BONE_ARROWHEADS,
|
|
xi.item.HANDFUL_OF_BRONZE_BOLT_HEADS,
|
|
xi.item.HANDFUL_OF_MYTHRIL_BOLT_HEADS,
|
|
xi.item.HANDFUL_OF_DARKSTEEL_BOLT_HEADS,
|
|
xi.item.HANDFUL_OF_SILVER_ARROWHEADS,
|
|
xi.item.BAG_OF_YAGUDO_FLETCHINGS,
|
|
xi.item.HANDFUL_OF_PLATINUM_ARROWHEADS,
|
|
},
|
|
cs =
|
|
{
|
|
[0] = 1000, [1] = 1003, [2] = 1006, [3] = 1009, [4] = 1012, [5] = 1015,
|
|
[6] = 1018, [7] = 1021, [8] = 1024, [9] = 1027, [10] = 1030, [11] = 1033,
|
|
[12] = 1036, [13] = 1039, [14] = 1042, [15] = 1045, [16] = 1048
|
|
},
|
|
dropTypes =
|
|
{
|
|
TEMP = 1,
|
|
ITEM = 2,
|
|
EVOLITH = 3, -- NOTE: not implemented! item id: 2783
|
|
},
|
|
evolithAugs =
|
|
{
|
|
-- TODO: find all augments for evoliths.
|
|
},
|
|
}
|
|
|
|
-----------------------------------
|
|
-- Desc: Helper function for making it easier to read time between spawns.
|
|
-- TODO: Simplify and deprecate this function, as its only used in timeElapsedCheck
|
|
-----------------------------------
|
|
local function convertTime(rawTime)
|
|
local rawSeconds = tonumber(rawTime)
|
|
local timeTable = { '', '', '' }
|
|
|
|
timeTable[1] = string.format('%02.f', math.floor(rawSeconds / 3600))
|
|
timeTable[2] = string.format('%02.f', math.floor(rawSeconds / 60 - timeTable[1] * 60))
|
|
timeTable[3] = string.format('%02.f', math.floor(rawSeconds - timeTable[1] * 3600 - timeTable[2] * 60))
|
|
|
|
return timeTable
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Check for time elapsed since last spawned
|
|
-- NOTE: will NOT allow a spawn if time since last spanwed is under 5 mins.
|
|
-----------------------------------
|
|
local function timeElapsedCheck(npc)
|
|
local spawnTime = GetSystemTime() + 360000 -- Default time in case no var set.
|
|
local timeTable = { 0, 0, 0 } -- Hours, Minutes, Seconds.
|
|
|
|
if npc == nil then
|
|
return false
|
|
end
|
|
|
|
if npc:getLocalVar('[caskets]SPAWNTIME') then
|
|
spawnTime = npc:getLocalVar('[caskets]SPAWNTIME')
|
|
end
|
|
|
|
local lastSpawned = GetSystemTime() - spawnTime
|
|
|
|
timeTable = convertTime(lastSpawned)
|
|
|
|
if
|
|
tonumber(timeTable[1]) >= 01 or
|
|
tonumber(timeTable[1]) < 01 and
|
|
tonumber(timeTable[2]) >= 05
|
|
then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Grabs an id for a casket if one is available if not, no casket will spawn.
|
|
-----------------------------------
|
|
local function getCasketID(mob)
|
|
local zone = mob:getZone()
|
|
-- Get a list of all entities in this zone that have the name 'Treasure_Casket'
|
|
local caskets = zone:queryEntitiesByName('Treasure_Casket')
|
|
-- If there are none, bail out
|
|
if #caskets == 0 then
|
|
return 0
|
|
end
|
|
|
|
-- Get the ID of the first entry and use that as our base ID to offset against
|
|
local baseChestId = caskets[1]:getID()
|
|
local chestId = 0
|
|
|
|
for i = baseChestId, baseChestId + 15 do
|
|
if timeElapsedCheck(GetNPCByID(i)) then
|
|
if
|
|
GetNPCByID(i):getLocalVar('[caskets]SPAWNSTATUS') == casketInfo.spawnStatus.DESPAWNED or
|
|
GetNPCByID(i):getLocalVar('[caskets]SPAWNSTATUS') == 0
|
|
then
|
|
chestId = i
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if GetNPCByID(chestId) == nil then
|
|
return 0
|
|
end
|
|
|
|
return chestId
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Drop rate check, calculates all drop rate modifiers.
|
|
-----------------------------------
|
|
local function dropChance(player)
|
|
-----------------------------------
|
|
-- NOTES: 10% base drop rate.
|
|
-- Super Kupowers(Myriad Mystery Boxes) adds 10% drop rate to the base rate.
|
|
-- GoV Prowess Increased Treasure Casket Discovery adds 5% per level (max 5 levels)
|
|
-- for a total of 25% increase. -- NOTE this needs to be confirmed!
|
|
-----------------------------------
|
|
--local kupowerMMBEffect = player:getStatusEffect(xi.effect.KUPOWERS_MYRIAD_MYSTERY_BOXES) -- Super Kupowers Myriad Mystery Boxes not implimented yet.
|
|
local casketProwessEffect = player:getStatusEffect(xi.effect.PROWESS_CASKET_RATE)
|
|
local kupowersMMBPower = 0
|
|
local prowessCasketsPower = 0
|
|
|
|
--if player:hasStatusEffect(xi.effect.KUPOWERS_MYRIAD_MYSTERY_BOXES) then -- Super Kupowers Myriad Mystery Boxes not implimented yet.
|
|
-- kupowersMMBPower = kupowerMMBEffect:getPower()
|
|
--end
|
|
|
|
if player:hasStatusEffect(xi.effect.PROWESS_CASKET_RATE) then
|
|
prowessCasketsPower = casketProwessEffect:getPower() / 100
|
|
end
|
|
|
|
local rand = math.random()
|
|
if rand < utils.clamp(xi.settings.main.CASKET_DROP_RATE + kupowersMMBPower + prowessCasketsPower, 0, 1) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Sends the message: 'The monster was concealing a treasure chest!' to all in party/alliance
|
|
-----------------------------------
|
|
local function sendChestDropMessage(player)
|
|
local ID = zones[player:getZoneID()]
|
|
local dropMessage = ID.text.PLAYER_OBTAINS_TEMP_ITEM + casketInfo.messageOffset.MONSTER_CONCEALED_CHEST
|
|
local party = {}
|
|
|
|
party = player:getAlliance()
|
|
|
|
for _, member in pairs(party) do
|
|
if member:getZoneID() == player:getZoneID() then
|
|
member:messageSpecial(dropMessage , 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Despawn a chest and reset its local var's
|
|
-----------------------------------
|
|
local function removeChest(npc)
|
|
npc:setAnimationSub(0, false)
|
|
npc:setStatus(xi.status.DISAPPEAR)
|
|
npc:resetLocalVars()
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Sets all the base localVar's, type of chest and if locked, sets the random number.
|
|
-----------------------------------
|
|
local function setCasketData(player, x, y, z, r, npc, partyID, mobLvl)
|
|
-- Early return.
|
|
if npc == nil then
|
|
return
|
|
end
|
|
|
|
local chestStyle = 965
|
|
local correctNum = math.random(10, 99)
|
|
local attempts = math.random(4, 6)
|
|
|
|
-- Get casket type.
|
|
local kupowersBonus = 0 -- TODO: Kupowers add a 20% chance.
|
|
if math.random(1, 100) <= 15 + kupowersBonus then
|
|
chestStyle = 966 -- Brown locked
|
|
else
|
|
chestStyle = 965 -- Blue
|
|
end
|
|
|
|
npc:resetLocalVars()
|
|
npc:setAnimation(0)
|
|
npc:setAnimationSub(4)
|
|
-----------------------------------
|
|
-- Chest data
|
|
-----------------------------------
|
|
npc:setLocalVar('[caskets]PARTYID', partyID)
|
|
npc:setLocalVar('[caskets]ITEMS_SET', 0)
|
|
npc:setLocalVar('[caskets]MOBLVL', mobLvl)
|
|
|
|
-- Brown.
|
|
if chestStyle == 966 then
|
|
npc:setLocalVar('[caskets]ATTEMPTS', attempts)
|
|
npc:setLocalVar('[caskets]CORRECT_NUM', correctNum)
|
|
npc:setLocalVar('[caskets]FAILED_ATEMPTS', 0)
|
|
npc:setLocalVar('[caskets]LOCKED', 1)
|
|
npc:setLocalVar('[caskets]LOOT_TYPE', casketInfo.dropTypes.ITEM)
|
|
npc:setLocalVar('[caskets]HINTS_TABLE', 1234567)
|
|
else
|
|
npc:setLocalVar('[caskets]LOCKED', 0)
|
|
npc:setLocalVar('[caskets]LOOT_TYPE', casketInfo.dropTypes.TEMP)
|
|
end
|
|
|
|
npc:setLocalVar('[caskets]SPAWNSTATUS', casketInfo.spawnStatus.SPAWNED_CLOSED)
|
|
npc:setLocalVar('[caskets]SPAWNTIME', GetSystemTime())
|
|
npc:setPos(x, y, z, r)
|
|
npc:setStatus(xi.status.NORMAL)
|
|
npc:entityAnimationPacket(xi.animationString.STATUS_VISIBLE)
|
|
npc:setModelId(chestStyle)
|
|
sendChestDropMessage(player)
|
|
-----------------------------------
|
|
-- Despawn chest after 3 Mins
|
|
-----------------------------------
|
|
npc:timer(1000 * 60 * 3, function(npcArg)
|
|
removeChest(npcArg)
|
|
end)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Checks to see if all the items have been removed from the casket then removes it.
|
|
-----------------------------------
|
|
local function checkItemChestIsEmpty(npc)
|
|
local item1 = npc:getLocalVar('[caskets]ITEM1')
|
|
local item2 = npc:getLocalVar('[caskets]ITEM2')
|
|
local item3 = npc:getLocalVar('[caskets]ITEM3')
|
|
local item4 = npc:getLocalVar('[caskets]ITEM4')
|
|
|
|
if item1 == 0 and item2 == 0 and item3 == 0 and item4 == 0 then
|
|
removeChest(npc)
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- ******Additional Functions******
|
|
-----------------------------------
|
|
|
|
-----------------------------------
|
|
-- Desc: Messages sent to all players in a party in the zone
|
|
-----------------------------------
|
|
local function messageChest(player, messageString, param1, param2, param3, param4, npc)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local baseMessage = ID.text.PLAYER_OBTAINS_TEMP_ITEM
|
|
local msg = 0
|
|
|
|
if messageString == 'UNABLE_TO_OPEN_LOCK' then
|
|
msg = baseMessage + casketInfo.messageOffset.UNABLE_TO_OPEN_LOCK
|
|
elseif messageString == 'OPENED_LOCK' then
|
|
msg = baseMessage + casketInfo.messageOffset.OPENED_LOCK
|
|
elseif messageString == 'PLAYER_OBTAINS_ITEM' then
|
|
msg = ID.text.PLAYER_OBTAINS_ITEM
|
|
elseif messageString == 'PLAYER_OBTAINS_TEMP_ITEM' then
|
|
msg = ID.text.PLAYER_OBTAINS_TEMP_ITEM
|
|
end
|
|
|
|
local alliance = player:getAlliance()
|
|
for _, member in pairs(alliance) do
|
|
if member:getZoneID() == player:getZoneID() then
|
|
member:messageName(msg, player, param1, param2, param3, param4, nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Checks attempts and despawns the chest if all attempts have been used up.
|
|
-----------------------------------
|
|
local function checkRemainingAttempts(player, npc, remaining, correctNumber)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local baseMessage = ID.text.PLAYER_OBTAINS_TEMP_ITEM
|
|
|
|
if remaining == 1 then
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.CORRECT_NUMBER_WAS, correctNumber, 0, 0, 0, 0)
|
|
messageChest(player, 'UNABLE_TO_OPEN_LOCK', 0, 0, 0, 0, npc)
|
|
removeChest(npc)
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Removes hint so they are not repeated, is no hints left, this enables the message,
|
|
-- 'You were unable to glean anything from your examination of the lock.'
|
|
-----------------------------------
|
|
local function removeHint(npc, hintNum)
|
|
local hintVar = npc:getLocalVar('[caskets]HINTS_TABLE')
|
|
local hintString = tostring(hintVar)
|
|
local newHintString = ''
|
|
|
|
if #hintString > 1 then
|
|
newHintString = hintString:gsub(tostring(hintNum), '')
|
|
else
|
|
newHintString = '0'
|
|
end
|
|
|
|
npc:setLocalVar('[caskets]HINTS_TABLE', tonumber(newHintString))
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Sets the items id in a local variable for the casket.
|
|
-----------------------------------
|
|
local function setItems(npc, item1, item2, item3, item4)
|
|
npc:setLocalVar('[caskets]ITEM1', item1)
|
|
npc:setLocalVar('[caskets]ITEM2', item2)
|
|
npc:setLocalVar('[caskets]ITEM3', item3)
|
|
npc:setLocalVar('[caskets]ITEM4', item4)
|
|
npc:setLocalVar('[caskets]ITEMS_SET', 1)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Sets the temp items id in a local variable for the casket.
|
|
-----------------------------------
|
|
local function setTempItems(npc, temp1, temp2, temp3)
|
|
npc:setLocalVar('[caskets]TEMP1', temp1)
|
|
npc:setLocalVar('[caskets]TEMP2', temp2)
|
|
npc:setLocalVar('[caskets]TEMP3', temp3)
|
|
npc:setLocalVar('[caskets]ITEMS_SET', 1)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Grab random drops from zone item or temp tables depending on type of chest
|
|
-----------------------------------
|
|
local function getDrops(npc, dropType, zoneId)
|
|
if npc:getLocalVar('[caskets]ITEMS_SET') == 1 then
|
|
return
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Temp drops
|
|
-----------------------------------
|
|
if dropType == casketInfo.dropTypes.TEMP then
|
|
-- Get item table.
|
|
local tempDrops = xi.casket_loot.casketItems[zoneId].temps
|
|
if casketInfo.splitZones[zoneId] then
|
|
local mobLvl = npc:getLocalVar('[caskets]MOBLVL')
|
|
if mobLvl > 50 then
|
|
tempDrops = xi.casket_loot.casketItems[zoneId].tempsHi
|
|
else
|
|
tempDrops = xi.casket_loot.casketItems[zoneId].tempsLow
|
|
end
|
|
end
|
|
|
|
-- Get number of items in casket.
|
|
local randomTable = { 1, 3, 1, 2, 1, 2, 1, 1, 3, 1, 2, 1 }
|
|
local itemCount = utils.randomEntry(randomTable)
|
|
|
|
local temps = { 0, 0, 0 }
|
|
|
|
-- roll for items
|
|
for i = 1, itemCount do
|
|
temps[i] = xi.itemUtils.pickItemRandom(tempDrops)
|
|
end
|
|
|
|
setTempItems(npc, temps[1], temps[2], temps[3])
|
|
-----------------------------------
|
|
-- Item drops
|
|
-----------------------------------
|
|
elseif dropType == casketInfo.dropTypes.ITEM then
|
|
-- Get item table.
|
|
local drops = xi.casket_loot.casketItems[zoneId].items
|
|
if casketInfo.splitZones[zoneId] then
|
|
local mobLvl = npc:getLocalVar('[caskets]MOBLVL')
|
|
if mobLvl > 50 then
|
|
drops = xi.casket_loot.casketItems[zoneId].itemsHi
|
|
else
|
|
drops = xi.casket_loot.casketItems[zoneId].itemsLow
|
|
end
|
|
end
|
|
|
|
-- Get number of items in casket.
|
|
local randomTable = { 1, 4, 1, 3, 1, 1, 2, 1, 3, 1, 2, 1 }
|
|
local itemCount = utils.randomEntry(randomTable)
|
|
|
|
local items = { 0, 0, 0, 0 }
|
|
|
|
-- roll for items and give a chance for a regional item for every dropped item
|
|
for i = 1, itemCount do
|
|
local itemId = xi.itemUtils.pickItemRandom(drops)
|
|
|
|
if math.random(1, 100) <= 5 then
|
|
items[1] = utils.randomEntry(xi.casket_loot.casketItems[zoneId].regionalItems)
|
|
else
|
|
items[i] = itemId
|
|
end
|
|
end
|
|
|
|
setItems(npc, items[1], items[2], items[3], items[4])
|
|
-----------------------------------
|
|
-- Evolith drops
|
|
-----------------------------------
|
|
elseif dropType == casketInfo.dropTypes.EVOLITH then
|
|
-- local evolith = 2783
|
|
-- NOTE: Not implimented yet and will be incorperated into items once implimented.
|
|
-- this is mainly here as a means of testing before implimentation.
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Temp item functions
|
|
-----------------------------------
|
|
|
|
-----------------------------------
|
|
-- Desc: Returns an temp items id based on the the local variable i.e. npc:getLocalVariable('TEMP1').
|
|
-----------------------------------
|
|
local function getTempDrop(npc, tempNum)
|
|
local query = string.format('[caskets]TEMP' ..tempNum.. '')
|
|
local var = npc:getLocalVar(query)
|
|
|
|
if var == nil then
|
|
return 0
|
|
else
|
|
return var
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Checks to see if the casket is empty after a player removes an item, if so, despawns the casket
|
|
-----------------------------------
|
|
local function checkTempChestIsEmpty(npc)
|
|
local temp1 = npc:getLocalVar('[caskets]TEMP1')
|
|
local temp2 = npc:getLocalVar('[caskets]TEMP2')
|
|
local temp3 = npc:getLocalVar('[caskets]TEMP3')
|
|
|
|
if temp1 == 0 and temp2 == 0 and temp3 == 0 then
|
|
removeChest(npc)
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Gives the player the temp item from a casket based on the selection of the csid
|
|
-----------------------------------
|
|
local function giveTempItem(player, npc, tempNum, subOption)
|
|
local tempQuery = string.format('[caskets]TEMP' ..tempNum.. '')
|
|
local tempID = npc:getLocalVar(tempQuery)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local spawnStatus = npc:getLocalVar('[caskets]SPAWNSTATUS')
|
|
|
|
if spawnStatus == casketInfo.spawnStatus.DESPAWNED then
|
|
return
|
|
end
|
|
|
|
-- 2 = "do not obtain"
|
|
-- 1 = "obtain"
|
|
-- 0 = "None of them"
|
|
if subOption == 2 or subOption == 0 then
|
|
return
|
|
end
|
|
|
|
if tempID == 0 then
|
|
player:messageSpecial(ID.text.UNABLE_TO_OBTAIN_ITEM)
|
|
return
|
|
else
|
|
if player:hasItem(tempID, xi.inventoryLocation.TEMPITEMS) then
|
|
return player:messageSpecial(ID.text.ALREADY_POSSESS_TEMP)
|
|
else
|
|
if player:addTempItem(tempID) then
|
|
messageChest(player, 'PLAYER_OBTAINS_TEMP_ITEM', tempID, 0, 0, 0)
|
|
npc:setLocalVar(tempQuery, 0)
|
|
checkTempChestIsEmpty(npc)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Basic item functions
|
|
-----------------------------------
|
|
|
|
-----------------------------------
|
|
-- Desc: Returns an items id based on the the local variable i.e. npc:getLocalVariable('ITEM1').
|
|
-----------------------------------
|
|
local function getChestItem(npc, slot)
|
|
local query = string.format('[caskets]ITEM' ..slot.. '')
|
|
local var = npc:getLocalVar(query)
|
|
|
|
if var == nil then
|
|
return 0
|
|
else
|
|
return var
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Gives the player the item from a casket based on the selection of the csid
|
|
-----------------------------------
|
|
local function giveItem(player, npc, itemNum, subOption)
|
|
local itemQuery = string.format('[caskets]ITEM' ..itemNum.. '')
|
|
local itemID = npc:getLocalVar(itemQuery)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local spawnStatus = npc:getLocalVar('[caskets]SPAWNSTATUS')
|
|
|
|
if spawnStatus == casketInfo.spawnStatus.DESPAWNED then
|
|
return
|
|
end
|
|
|
|
-- 2 = "do not obtain"
|
|
-- 1 = "obtain"
|
|
-- 0 = "None of them"
|
|
if subOption == 2 or subOption == 0 then
|
|
return
|
|
end
|
|
|
|
if itemID == 0 then
|
|
player:messageSpecial(ID.text.UNABLE_TO_OBTAIN_ITEM)
|
|
return
|
|
else
|
|
if player:getFreeSlotsCount() == 0 then
|
|
player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINED, itemID)
|
|
return
|
|
elseif player:getFreeSlotsCount() > 0 then
|
|
if itemID ~= 0 then
|
|
local quantity = 1
|
|
if casketInfo.multipleItems[itemID] then
|
|
quantity = 33
|
|
end
|
|
|
|
if player:addItem(itemID, quantity) then
|
|
-- TODO is the message supposed to give some indication if quantity > 1?
|
|
messageChest(player, 'PLAYER_OBTAINS_ITEM', itemID, 0, 0, 0)
|
|
npc:setLocalVar(itemQuery, 0)
|
|
checkItemChestIsEmpty(npc)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Desc: Casket spawn checks, runs through all checks before spawning
|
|
-----------------------------------
|
|
xi.caskets.spawnCasket = function(player, mob, x, y, z, r)
|
|
local chestId = getCasketID(mob)
|
|
local npc = GetNPCByID(chestId)
|
|
local chestOwner = player:getLeaderID()
|
|
|
|
if chestId == 0 then
|
|
return
|
|
end
|
|
|
|
if dropChance(player) then
|
|
setCasketData(player, x, y, z, r, npc, chestOwner, mob:getMainLvl())
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Main public casket functions
|
|
-----------------------------------
|
|
xi.caskets.onTrigger = function(player, npc)
|
|
-----------------------------------
|
|
-- Basic chest var's
|
|
-----------------------------------
|
|
local chestId = npc:getID() -- ID of the chest
|
|
local dropType = npc:getLocalVar('[caskets]LOOT_TYPE') -- Chest Type from casketInfo.dropTypes
|
|
local locked = npc:getLocalVar('[caskets]LOCKED') -- enter two-digit combination (10~99).
|
|
local chestOwner = npc:getLocalVar('[caskets]PARTYID') -- the id of the party that has rights to the chest.
|
|
local leaderId = player:getLeaderID()
|
|
--local aumentflag = 0x0202 -- Used for Evoliths (not implemented yet).
|
|
local zone = npc:getZone()
|
|
-- Get a list of all entities in this zone that have the name 'Treasure_Casket'
|
|
local caskets = zone:queryEntitiesByName('Treasure_Casket')
|
|
-- Get the ID of the first entry and use that as our base ID to offset against
|
|
local eventBase = caskets[1]:getID() -- base id of the current chest.
|
|
local lockedEvent = casketInfo.cs[chestId - eventBase] + 2 -- Chest locked cs's.
|
|
local unlockedEvent = casketInfo.cs[chestId - eventBase] -- Chest unlocked cs's.
|
|
|
|
-----------------------------------
|
|
-- Locked chest var's
|
|
-----------------------------------
|
|
local attemptsAllowed = npc:getLocalVar('[caskets]ATTEMPTS')
|
|
local failedAtempts = npc:getLocalVar('[caskets]FAILED_ATEMPTS')
|
|
local remainingAttempts = attemptsAllowed - failedAtempts
|
|
|
|
if leaderId ~= chestOwner then
|
|
return
|
|
end
|
|
|
|
getDrops(npc, dropType, player:getZoneID())
|
|
|
|
-----------------------------------
|
|
-- Chest Locked
|
|
-----------------------------------
|
|
if locked ~= 0 then
|
|
player:startEvent(lockedEvent, remainingAttempts, 0, 0, 0, 0, 0, 0, 0) -- Start the minigame if locked.
|
|
else
|
|
-----------------------------------
|
|
-- Chest Unlocked
|
|
-----------------------------------
|
|
if npc:getLocalVar('[caskets]SPAWNSTATUS') == casketInfo.spawnStatus.SPAWNED_CLOSED then -- is the chest shut?, then open it.
|
|
npc:setAnimationSub(1)
|
|
npc:setLocalVar('[caskets]SPAWNSTATUS', casketInfo.spawnStatus.SPAWNED_OPEN)
|
|
-- RoE Timed Record #4019 - Crack Tresure Caskets
|
|
if player:getEminenceProgress(4019) then
|
|
xi.roe.onRecordTrigger(player, 4019)
|
|
end
|
|
end
|
|
|
|
if dropType == casketInfo.dropTypes.TEMP then
|
|
player:startEvent(unlockedEvent + 1,
|
|
getTempDrop(npc, 1),
|
|
getTempDrop(npc, 2),
|
|
getTempDrop(npc, 3),
|
|
0, 0, 0, 0, 0)
|
|
elseif dropType == casketInfo.dropTypes.ITEM then
|
|
player:startEvent(unlockedEvent,
|
|
getChestItem(npc, 1),
|
|
getChestItem(npc, 2),
|
|
getChestItem(npc, 3),
|
|
getChestItem(npc, 4),
|
|
0, 0, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Retail notes: from wiki
|
|
-- Thieves can use tools to gain a free hint, without expending one of their attempts.
|
|
-- The only clues that you can obtain via Thief's Tools is a hint that tells you it's between 2 numbers,
|
|
-- (e.g. its between 24 and 58) its usually a good idea to start with this clue.
|
|
-- Multiple tools may be used, however there is a low rate of success after the first.
|
|
-----------------------------------
|
|
xi.caskets.onTrade = function(player, npc, trade)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local baseMessage = ID.text.PLAYER_OBTAINS_TEMP_ITEM
|
|
local locked = npc:getLocalVar('[caskets]LOCKED')
|
|
local correctNumber = npc:getLocalVar('[caskets]CORRECT_NUM')
|
|
local chestOwner = npc:getLocalVar('[caskets]PARTYID') -- the id of the player, party or alliance that has rights to the chest.
|
|
local leaderId = player:getLeaderID()
|
|
|
|
-- NOTE: The client blocks actions like this while invisible, but it's very easy to inject an action packet to get
|
|
-- around this restriction. Strip invisible to make sure that case is covered.
|
|
player:delStatusEffect(xi.effect.INVISIBLE)
|
|
|
|
if leaderId ~= chestOwner then
|
|
return
|
|
end
|
|
|
|
if locked == 1 then
|
|
if
|
|
player:getMainJob() == xi.job.THF and
|
|
npcUtil.tradeHasExactly(trade, xi.item.SET_OF_THIEFS_TOOLS)
|
|
then
|
|
local splitNumbers = {}
|
|
local tradeAttempt = math.random()
|
|
local firstAttempt = npc:getLocalVar('[caskets]HINT_TRADE')
|
|
local canGetHint = false
|
|
|
|
for digit in string.gmatch(tostring(correctNumber), '%d') do
|
|
table.insert(splitNumbers, tonumber(digit))
|
|
end
|
|
|
|
if firstAttempt == 0 or firstAttempt == nil then
|
|
npc:setLocalVar('[caskets]HINT_TRADE', 1)
|
|
canGetHint = true
|
|
else
|
|
if tradeAttempt < 0.2 then
|
|
canGetHint = true
|
|
else
|
|
canGetHint = false
|
|
end
|
|
end
|
|
|
|
if canGetHint then
|
|
local highNum = 0
|
|
local lowNum = 0
|
|
|
|
if splitNumbers[1] == 1 then
|
|
lowNum = 10
|
|
highNum = 20 + math.random(1, 9)
|
|
elseif splitNumbers[1] > 1 and splitNumbers[1] < 9 then
|
|
lowNum = splitNumbers[1] * 10 - 10 + math.random(1, 9)
|
|
highNum = splitNumbers[1] * 10 + 10 + math.random(1, 9)
|
|
elseif splitNumbers[1] == 9 then
|
|
lowNum = 80 + math.random(1, 9)
|
|
highNum = 99
|
|
end
|
|
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.COMBINATION_GREATER_LESS, lowNum, highNum, 0, 0)
|
|
else
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.UNABLE_TO_GET_HINT, 0, 0, 0, 0)
|
|
end
|
|
|
|
player:confirmTrade()
|
|
end
|
|
end
|
|
end
|
|
|
|
xi.caskets.onEventFinish = function(player, csid, option, npc)
|
|
local zoneId = player:getZoneID()
|
|
local ID = zones[zoneId]
|
|
local baseMessage = ID.text.PLAYER_OBTAINS_TEMP_ITEM
|
|
-----------------------------------
|
|
-- Basic chest var's
|
|
-----------------------------------
|
|
local chestObj = player:getEventTarget()
|
|
local spawnStatus = chestObj:getLocalVar('[caskets]SPAWNSTATUS')
|
|
local locked = chestObj:getLocalVar('[caskets]LOCKED')
|
|
local dropType = chestObj:getLocalVar('[caskets]LOOT_TYPE')
|
|
local lockedChoice = bit.lshift(1, option -1)
|
|
local inputNumber = bit.rshift(option, 16)
|
|
|
|
-----------------------------------
|
|
-- Chest Locked var's
|
|
-----------------------------------
|
|
local correctNumber = chestObj:getLocalVar('[caskets]CORRECT_NUM')
|
|
local attemptsAllowed = chestObj:getLocalVar('[caskets]ATTEMPTS')
|
|
local failedAtempts = chestObj:getLocalVar('[caskets]FAILED_ATEMPTS')
|
|
local remainingAttempts = attemptsAllowed - failedAtempts
|
|
|
|
-----------------------------------
|
|
-- Minigame
|
|
-----------------------------------
|
|
local splitNumbers = {}
|
|
local hintsVar = chestObj:getLocalVar('[caskets]HINTS_TABLE')
|
|
local availableHints = {}
|
|
|
|
if hintsVar ~= 0 then
|
|
for hint in string.gmatch(tostring(hintsVar), '%d') do
|
|
table.insert(availableHints, hint)
|
|
end
|
|
end
|
|
|
|
for digit in string.gmatch(tostring(correctNumber), '%d') do
|
|
table.insert(splitNumbers, tonumber(digit))
|
|
end
|
|
|
|
if locked == 1 then
|
|
if option > 0 and spawnStatus ~= casketInfo.spawnStatus.SPAWNED_CLOSED then -- prevent minigame from working if chest is opened.
|
|
return
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Hints
|
|
-----------------------------------
|
|
if lockedChoice == 2 then -- Examine chest
|
|
if option == 258 then
|
|
local randText = tonumber(availableHints[math.random(#availableHints)])
|
|
|
|
if randText == 0 or randText == nil then
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.UNABLE_TO_GET_HINT, 0, 0, 0, 0)
|
|
return
|
|
elseif randText <= 2 then
|
|
local oddEventParam = splitNumbers[randText] % 2
|
|
local messageId = baseMessage + casketInfo.messageOffset.HUNCH_SECOND_EVEN_ODD - (randText - 2)
|
|
|
|
player:messageSpecial(messageId, oddEventParam, 0, 0, 0)
|
|
chestObj:setLocalVar('[caskets]FAILED_ATEMPTS', failedAtempts + 1)
|
|
elseif randText <= 4 then
|
|
-- NOTE: Second digit ID is one lower than First Digit ID, but our randText is opposite that. If randText == 3, then
|
|
-- we expect it to be the FIRST_DIGIT_IS message. Therefore, the below function intends to add 1 to second digit
|
|
-- if it is the first.
|
|
|
|
local splitIndex = randText - 2
|
|
local messageId = baseMessage + casketInfo.messageOffset.SECOND_DIGIT_IS - (splitIndex - 2)
|
|
|
|
if splitNumbers[splitIndex] <= 6 then
|
|
player:messageSpecial(messageId,
|
|
splitNumbers[splitIndex],
|
|
splitNumbers[splitIndex] + 1,
|
|
splitNumbers[splitIndex] + 2, 0)
|
|
elseif splitNumbers[splitIndex] == 9 then
|
|
player:messageSpecial(messageId,
|
|
splitNumbers[splitIndex] - 2,
|
|
splitNumbers[splitIndex] - 1,
|
|
splitNumbers[splitIndex], 0)
|
|
else
|
|
player:messageSpecial(messageId,
|
|
splitNumbers[splitIndex] - 1,
|
|
splitNumbers[splitIndex],
|
|
splitNumbers[splitIndex] + 1, 0)
|
|
end
|
|
|
|
chestObj:setLocalVar('[caskets]FAILED_ATEMPTS', failedAtempts + 1)
|
|
elseif randText <= 6 then
|
|
local splitIndex = randText - 4
|
|
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.ONE_OF_TWO_DIGITS_IS, splitNumbers[splitIndex], 0, 0, 0)
|
|
chestObj:setLocalVar('[caskets]FAILED_ATEMPTS', failedAtempts + 1)
|
|
elseif randText == 7 then
|
|
local highNum = 0
|
|
local lowNum = 0
|
|
|
|
if splitNumbers[1] == 1 then
|
|
lowNum = 10
|
|
highNum = 20 + math.random(1, 9)
|
|
elseif splitNumbers[1] == 9 then
|
|
lowNum = 80 + math.random(1, 9)
|
|
highNum = 99
|
|
else
|
|
lowNum = splitNumbers[1] * 10 - 10 + math.random(1, 9)
|
|
highNum = splitNumbers[1] * 10 + 10 + math.random(1, 9)
|
|
end
|
|
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.COMBINATION_GREATER_LESS, lowNum, highNum, 0, 0)
|
|
chestObj:setLocalVar('[caskets]FAILED_ATEMPTS', failedAtempts + 1)
|
|
else
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.UNABLE_TO_GET_HINT, 0, 0, 0, 0)
|
|
end
|
|
|
|
checkRemainingAttempts(player, chestObj, remainingAttempts, correctNumber)
|
|
removeHint(chestObj, randText)
|
|
end
|
|
|
|
-----------------------------------
|
|
-- Inputs
|
|
-----------------------------------
|
|
elseif lockedChoice == 1 then -- Input a number
|
|
if inputNumber > 9 and inputNumber < 100 then
|
|
if locked == 0 then
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.NO_COMBINATION, 0, 0, 0, 0)
|
|
elseif inputNumber == correctNumber then
|
|
messageChest(player, 'OPENED_LOCK', 0 , 0, 0, 0, chestObj)
|
|
chestObj:setLocalVar('[caskets]LOCKED', 0)
|
|
|
|
if chestObj:getLocalVar('[caskets]SPAWNSTATUS') == casketInfo.spawnStatus.SPAWNED_CLOSED then -- is the chest shut?, then open it.
|
|
chestObj:setAnimationSub(1)
|
|
chestObj:setLocalVar('[caskets]SPAWNSTATUS', casketInfo.spawnStatus.SPAWNED_OPEN)
|
|
|
|
-- RoE Timed Record #4019 - Crack Tresure Caskets (Progress is verified in onRecordTrigger function)
|
|
xi.roe.onRecordTrigger(player, 4019)
|
|
end
|
|
else
|
|
local isGreater = inputNumber > correctNumber and 1 or 0
|
|
|
|
player:messageSpecial(baseMessage + casketInfo.messageOffset.HUNCH_GREATER_LESS, inputNumber, isGreater, 0, 0, 0)
|
|
chestObj:setLocalVar('[caskets]FAILED_ATEMPTS', failedAtempts + 1)
|
|
checkRemainingAttempts(player, chestObj, remainingAttempts, correctNumber)
|
|
end
|
|
end
|
|
end
|
|
|
|
elseif locked == 0 then
|
|
local itemPos = bit.band(option, 0x7)
|
|
local subOption = bit.band(bit.rshift(option, 16), 0x3) -- 2 bit mask
|
|
|
|
if dropType == casketInfo.dropTypes.TEMP then
|
|
giveTempItem(player, chestObj, itemPos, subOption)
|
|
elseif dropType == casketInfo.dropTypes.ITEM then
|
|
giveItem(player, chestObj, itemPos, subOption)
|
|
end
|
|
end
|
|
end
|