Escutcheons Weapon unlock Soul plate Soul reflectors Fish Co-Authored-By: atom0s <atom0s@users.noreply.github.com>
427 lines
16 KiB
Lua
427 lines
16 KiB
Lua
-----------------------------------
|
|
-- Fish Ranking Contest Functions
|
|
-----------------------------------
|
|
xi = xi or {}
|
|
xi.fishingContest = xi.fishingContest or {}
|
|
|
|
xi.fishingContest.fish =
|
|
{
|
|
-- NOTE: If you don't want certain fish included in the contest (era restrictions, or whatever),
|
|
-- then you must comment out the lines containing unwanted fish. The C++ core calls the
|
|
-- random function in this file to select a fish for each contest, which uses this table.
|
|
|
|
{ id = xi.item.GRIMMONITE, name = 'grimmonite' , realName = 'Grimmonite' },
|
|
{ id = xi.item.RYUGU_TITAN, name = 'ryugu_titan' , realName = 'Ryugu Titan' },
|
|
{ id = xi.item.GIANT_DONKO_1, name = 'giant_donko' , realName = 'Giant Donko' },
|
|
{ id = xi.item.JUNGLE_CATFISH, name = 'jungle_catfish' , realName = 'Jungle Catfish' },
|
|
{ id = xi.item.GIANT_CHIRAI, name = 'giant_chirai' , realName = 'Giant Chirai' },
|
|
{ id = xi.item.CAVE_CHERAX, name = 'cave_cherax' , realName = 'Cave Cherax' },
|
|
{ id = xi.item.ARMORED_PISCES, name = 'armored_pisces' , realName = 'Armored Pisces' },
|
|
{ id = xi.item.TRICORN, name = 'tricorn' , realName = 'Tricorn' },
|
|
{ id = xi.item.EMPEROR_FISH, name = 'emperor_fish' , realName = 'Emperor Fish' },
|
|
{ id = xi.item.MONKE_ONKE_1, name = 'monke-onke' , realName = 'Monke-Onke' },
|
|
{ id = xi.item.TAKITARO, name = 'takitaro' , realName = 'Takitaro' },
|
|
{ id = xi.item.GIANT_CATFISH_1, name = 'giant_catfish' , realName = 'Giant Catfish' },
|
|
{ id = xi.item.BLADEFISH_1, name = 'bladefish' , realName = 'Bladefish' },
|
|
{ id = xi.item.GIGANT_SQUID, name = 'gigant_squid' , realName = 'Gigant Squid' },
|
|
{ id = xi.item.SEA_ZOMBIE, name = 'sea_zombie' , realName = 'Sea Zombie' },
|
|
{ id = xi.item.TITANICTUS, name = 'titanictus' , realName = 'Titanictus' },
|
|
{ id = xi.item.GAVIAL_FISH, name = 'gavial_fish' , realName = 'Gavial Fish' },
|
|
{ id = xi.item.THREE_EYED_FISH_1, name = 'three-eyed_fish' , realName = 'Three-eyed Fish' },
|
|
{ id = xi.item.BHEFHEL_MARLIN_1, name = 'bhefhel_marlin' , realName = 'Bhefhel Marlin' },
|
|
{ id = xi.item.GUGRU_TUNA_1, name = 'gugru_tuna' , realName = 'Gugru Tuna' },
|
|
{ id = xi.item.TITANIC_SAWFISH, name = 'titanic_sawfish' , realName = 'Titanic Sawfish' },
|
|
{ id = xi.item.GUGRUSAURUS, name = 'gugrusaurus' , realName = 'Gugrusaurus' },
|
|
{ id = xi.item.LIK, name = 'lik' , realName = 'Lik' },
|
|
}
|
|
|
|
xi.fishingContest.criteria =
|
|
{
|
|
SIZE = 0,
|
|
WEIGHT = 1,
|
|
BOTH = 2,
|
|
}
|
|
|
|
xi.fishingContest.measure =
|
|
{
|
|
GREATEST = 0,
|
|
SMALLEST = 1,
|
|
}
|
|
|
|
xi.fishingContest.status =
|
|
{
|
|
CONTESTING = 0, -- Usually used for building manual contest, but a simple 5 minute buffer
|
|
OPENING = 1, -- 30 mins, warning to players that contest is opening soon
|
|
ACCEPTING = 2, -- 2 week window during which trades are accepted
|
|
RELEASING = 3, -- Submissions closed, preparing to release the results
|
|
PRESENTING = 4, -- Results posted, contestants may claim their prizes
|
|
HIATUS = 5, -- Approx 35 minute window after presentations
|
|
CLOSED = 6, -- Only used when contest is closed and inactive. No automatic progression from this stage
|
|
}
|
|
|
|
xi.fishingContest.reward =
|
|
{
|
|
-- Defines the reward at each rank
|
|
[ 1] = { gil = 50000, item = xi.item.PELICAN_RING, title = xi.title.GOLD_HOOK },
|
|
[ 2] = { gil = 25000, item = xi.item.PELICAN_RING, title = xi.title.MYTHRIL_HOOK },
|
|
[ 3] = { gil = 10000, item = xi.item.PELICAN_RING, title = xi.title.SILVER_HOOK },
|
|
[ 4] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[ 5] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[ 6] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[ 7] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[ 8] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[ 9] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[10] = { gil = 1000, item = xi.item.PELICAN_RING, title = xi.title.COPPER_HOOK },
|
|
[11] = { item = xi.item.PELICAN_RING, },
|
|
[12] = { item = xi.item.PELICAN_RING, },
|
|
[13] = { item = xi.item.PELICAN_RING, },
|
|
[14] = { item = xi.item.PELICAN_RING, },
|
|
[15] = { item = xi.item.PELICAN_RING, },
|
|
[16] = { item = xi.item.PELICAN_RING, },
|
|
[17] = { item = xi.item.PELICAN_RING, },
|
|
[18] = { item = xi.item.PELICAN_RING, },
|
|
[19] = { item = xi.item.PELICAN_RING, },
|
|
[20] = { item = xi.item.PELICAN_RING, },
|
|
}
|
|
|
|
xi.fishingContest.interval =
|
|
{
|
|
-- These intervals indicate how long each phase lasts before being automatically progressed.
|
|
-- Total cycle is 28 days, 35 minutes based on captures. May be varied if contest is manually controlled
|
|
[xi.fishingContest.status.CONTESTING] = 300, -- 00 days 00 hours 05 mins
|
|
[xi.fishingContest.status.OPENING] = 1500, -- 00 days 00 hours 25 mins
|
|
[xi.fishingContest.status.ACCEPTING] = 1209600, -- 14 days 00 hours 00 mins
|
|
[xi.fishingContest.status.RELEASING] = 1800, -- 00 days 00 hours 30 mins
|
|
[xi.fishingContest.status.PRESENTING] = 1206000, -- 13 days 23 hours 00 minutes
|
|
[xi.fishingContest.status.HIATUS] = 2100, -- 00 days 00 hours 35 minutes
|
|
[xi.fishingContest.status.CLOSED] = 0xFFFFFFFF, -- Must be manually controlled
|
|
}
|
|
|
|
-----------------------------------
|
|
-- LOCAL FUNCTIONS
|
|
-----------------------------------
|
|
|
|
-- Determine the slot that contains the fish being traded
|
|
local function findFishSlot(trade, fish)
|
|
for i = 0, trade:getSlotCount() - 1 do
|
|
if trade:getItemId(i) == fish then
|
|
return i
|
|
end
|
|
end
|
|
|
|
return 0
|
|
end
|
|
|
|
local function giveFish(player, params)
|
|
params = params or {}
|
|
local ID = zones[player:getZoneID()]
|
|
local fishid = params.id
|
|
|
|
-- does player have enough inventory space?
|
|
if player:getFreeSlotsCount() < 1 then
|
|
local messageId = ID.text.ITEM_CANNOT_BE_OBTAINED + 4
|
|
player:messageSpecial(messageId, fishid)
|
|
return false
|
|
end
|
|
|
|
-- give items to player
|
|
local fishItem = player:addItem({ id = fishid, quantity = params.quantity or 1 })
|
|
if fishItem then
|
|
if params.exdata then
|
|
fishItem:setExData(params.exdata)
|
|
end
|
|
|
|
player:messageSpecial(ID.text.ITEM_OBTAINED, fishid)
|
|
else
|
|
player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINED, fishid)
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function getAwardInteger(awardTable)
|
|
return bit.lshift(awardTable['rank4to10'], 24) + bit.lshift(awardTable['rank3'], 16) + bit.lshift(awardTable['rank2'], 8) + awardTable['rank1']
|
|
end
|
|
|
|
-- Determine if the player has a reward pending in the current contest
|
|
-- This will not permit rewarding for ongoing contests until it is ACTUALLY at the presenting stage.
|
|
local function isRewardAvailable(player)
|
|
local awards = player:getContestRewardStatus()
|
|
|
|
if awards ~= nil then
|
|
if
|
|
awards['rank'] > 0 and
|
|
awards['rank'] <= 20
|
|
then
|
|
return 1
|
|
end
|
|
end
|
|
|
|
return 0
|
|
end
|
|
|
|
-- This returns the data necessary to send the packet with the 'time remaining until stage change' data to the player
|
|
local function getTimeRemaining(changeTime)
|
|
local timeTable = {}
|
|
local currentTime = GetSystemTime()
|
|
if changeTime > currentTime then
|
|
local timeDelta = changeTime - currentTime
|
|
timeTable =
|
|
{
|
|
['days'] = math.floor(timeDelta / (60 * 60 * 24)),
|
|
['hours'] = math.floor((timeDelta / (60 * 60)) % 24),
|
|
['minutes'] = math.floor((timeDelta / 60) % 60),
|
|
}
|
|
else
|
|
timeTable =
|
|
{
|
|
['days'] = 0,
|
|
['hours'] = 0,
|
|
['minutes'] = 0,
|
|
}
|
|
end
|
|
|
|
return timeTable
|
|
end
|
|
|
|
-- Provides the score of the submission
|
|
local function scoreFish(length, weight, criteria)
|
|
local score = 0
|
|
if criteria == xi.fishingContest.criteria.SIZE then
|
|
score = length
|
|
elseif criteria == xi.fishingContest.criteria.WEIGHT then
|
|
score = weight
|
|
else
|
|
score = length + weight
|
|
end
|
|
|
|
return score
|
|
end
|
|
|
|
-- Determines the rewards to be granted based on rank and share
|
|
local function getPlayerReward(player)
|
|
local status = player:getContestRewardStatus()
|
|
|
|
if status ~= nil then
|
|
local rank = status['rank']
|
|
local share = status['share'] or 1
|
|
if
|
|
rank > 0 and
|
|
rank <= 20
|
|
then
|
|
local reward = xi.fishingContest.reward[rank]
|
|
if reward.gil then
|
|
reward.gil = reward.gil / share
|
|
end
|
|
|
|
return reward
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-----------------------------------
|
|
-- GLOBAL FUNCTIONS
|
|
-----------------------------------
|
|
|
|
xi.fishingContest.selectContestFish = function()
|
|
return utils.randomEntry(xi.fishingContest.fish)['id']
|
|
end
|
|
|
|
xi.fishingContest.getStageChangeTime = function(stage, stageStartTime)
|
|
-- If the value is invalid, send back the 'never change' time
|
|
if
|
|
stage >= xi.fishingContest.status.CONTESTING and
|
|
stage < xi.fishingContest.status.CLOSED
|
|
then
|
|
return stageStartTime + xi.fishingContest.interval[stage]
|
|
end
|
|
|
|
return xi.fishingContest.interval[xi.fishingContest.status.CLOSED]
|
|
end
|
|
|
|
xi.fishingContest.onStageProgress = function(newStage)
|
|
-- TODO: Capture NPC text on stage progressions
|
|
end
|
|
|
|
xi.fishingContest.onTrigger = function(player, npc)
|
|
local contest = GetFishingContest()
|
|
if contest == nil then
|
|
return
|
|
end
|
|
|
|
player:startEvent(10006, {
|
|
[0] = contest['status'] or xi.fishingContest.status.CLOSED,
|
|
[1] = contest['fishid'] or 0, -- Fish ID for the contest
|
|
[2] = contest['criteria'] or 0, -- 0 = Size, 1 = Weight, 2 = Size + Weight
|
|
[3] = contest['measure'] or 0, -- 0 = greatest, 1 = smallest
|
|
})
|
|
end
|
|
|
|
xi.fishingContest.onTrade = function(player, npc, trade)
|
|
-- Handle fish submission
|
|
local contest = GetFishingContest()
|
|
if contest == nil then
|
|
return
|
|
end
|
|
|
|
-- Check to see if the traded fish is the right one
|
|
if
|
|
contest['status'] == xi.fishingContest.status.ACCEPTING and
|
|
npcUtil.tradeHasExactly(trade, contest['fishid'])
|
|
then
|
|
local fishItem = trade:getItem(findFishSlot(trade, contest['fishid']))
|
|
local fishData = fishItem:getExData()
|
|
|
|
if fishData == nil then
|
|
return
|
|
elseif
|
|
fishData.isRanked or
|
|
fishData.size == 0 or
|
|
fishData.weight == 0
|
|
then
|
|
-- Fish has already been ranked previously
|
|
player:startEvent(10007, { [4] = 1 })
|
|
else
|
|
-- Fish is a valid entry, not previously ranked
|
|
-- Player local vars used to hold submission data until end of event
|
|
player:setLocalVar('[FishContest]Length', fishData.size)
|
|
player:setLocalVar('[FishContest]Weight', fishData.weight)
|
|
|
|
player:startEvent(10007, {
|
|
[5] = scoreFish(fishData.size, fishData.weight, contest['criteria']),
|
|
[6] = player:getContestScore(),
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
xi.fishingContest.onEventUpdate = function(player, csid, option, npc)
|
|
local contest = GetFishingContest()
|
|
if contest == nil then
|
|
return
|
|
end
|
|
|
|
if csid == 10006 then
|
|
if option == 147 then
|
|
-- Award History
|
|
player:updateEvent({ [5] = getAwardInteger(player:getContestRankHistory()) })
|
|
|
|
elseif option == 148 then
|
|
-- Pushed by client after informing of ranking status? Leaving open for now
|
|
|
|
elseif option == 149 then
|
|
-- View Current Rules | Ranking Results | Receive Award
|
|
if contest['status'] == xi.fishingContest.status.ACCEPTING then
|
|
player:updateEvent({
|
|
[0] = contest['status'],
|
|
[1] = contest['fishid'],
|
|
[2] = contest['criteria'],
|
|
[3] = contest['measure'],
|
|
})
|
|
elseif contest['status'] == xi.fishingContest.status.PRESENTING then
|
|
player:updateEvent({
|
|
[0] = contest['status'],
|
|
[1] = contest['fishid'],
|
|
[2] = contest['criteria'],
|
|
[3] = contest['measure'],
|
|
[4] = isRewardAvailable(player),
|
|
})
|
|
end
|
|
elseif option == 150 then
|
|
-- Initial option
|
|
local time = getTimeRemaining(contest['changetime'])
|
|
if contest['status'] == xi.fishingContest.status.ACCEPTING then
|
|
player:updateEvent({
|
|
[0] = contest['status'],
|
|
[1] = contest['fishid'],
|
|
[2] = contest['criteria'],
|
|
[3] = contest['measure'],
|
|
[4] = isRewardAvailable(player),
|
|
[5] = time['days'],
|
|
[6] = time['hours'],
|
|
[7] = time['minutes'],
|
|
})
|
|
elseif contest['status'] == xi.fishingContest.status.PRESENTING then
|
|
player:updateEvent({
|
|
[0] = contest['status'],
|
|
[1] = contest['fishid'],
|
|
[2] = contest['criteria'],
|
|
[3] = contest['measure'],
|
|
[4] = isRewardAvailable(player),
|
|
[5] = time['days'],
|
|
[6] = time['hours'],
|
|
[7] = time['minutes'],
|
|
})
|
|
end
|
|
elseif option == 151 then
|
|
-- Confirm request to confirm entry
|
|
player:updateEvent({
|
|
[0] = contest['status'],
|
|
[1] = contest['fishid'],
|
|
[2] = contest['criteria'],
|
|
[3] = contest['measure'],
|
|
[5] = player:getContestScore(),
|
|
})
|
|
elseif option == 152 then
|
|
-- Confirm request to withdraw entry
|
|
player:withdrawContestFish()
|
|
end
|
|
|
|
elseif csid == 10007 then
|
|
if option == 144 then
|
|
if player:getGil() < 500 then
|
|
-- Player does not have enough gil
|
|
player:messageSpecial(zones[player:getZoneID()].text.INSUFFICIENT_GIL)
|
|
return
|
|
else
|
|
player:updateEvent()
|
|
end
|
|
elseif option == 145 then
|
|
player:updateEvent()
|
|
end
|
|
end
|
|
end
|
|
|
|
xi.fishingContest.onEventFinish = function(player, csid, option, npc)
|
|
local contest = GetFishingContest()
|
|
if contest == nil then
|
|
return
|
|
end
|
|
|
|
if csid == 10007 then
|
|
-- Give the player back a fish with the same stats, but tagged for Ranking
|
|
if option == 145 then
|
|
local length = player:getLocalVar('[FishContest]Length')
|
|
local weight = player:getLocalVar('[FishContest]Weight')
|
|
local obtained = giveFish(player, { id = contest['fishid'],
|
|
quantity = 1,
|
|
exdata = { size = length, weight = weight, isRanked = true } })
|
|
if obtained then
|
|
player:confirmTrade()
|
|
player:delGil(500) -- Pay the registration fee of 500 gil.
|
|
player:submitContestFish(scoreFish(length, weight, contest['criteria']))
|
|
player:setLocalVar('[FishContest]Length', 0)
|
|
player:setLocalVar('[FishContest]Weight', 0)
|
|
end
|
|
end
|
|
|
|
elseif csid == 10006 then
|
|
if
|
|
option == 149 and
|
|
isRewardAvailable(player) > 0 and
|
|
contest['status'] == xi.fishingContest.status.PRESENTING
|
|
then
|
|
-- Issue the player a reward and flag it as awarded.
|
|
local reward = getPlayerReward(player)
|
|
if
|
|
reward ~= nil and
|
|
npcUtil.giveReward(player, reward)
|
|
then
|
|
player:claimContestReward()
|
|
end
|
|
end
|
|
end
|
|
end
|