261 lines
11 KiB
Lua
261 lines
11 KiB
Lua
-----------------------------------
|
|
-- Chocobo functions
|
|
-- Info from:
|
|
-- http://wiki.ffxiclopedia.org/wiki/Chocobo_Renter
|
|
-- http://ffxi.allakhazam.com/wiki/Traveling_in_Vana'diel
|
|
-----------------------------------
|
|
require('scripts/globals/missions')
|
|
-----------------------------------
|
|
xi = xi or {}
|
|
xi.chocobo = xi.chocobo or {}
|
|
|
|
--[[
|
|
Description:
|
|
[1] Level required to rent a chocobo
|
|
[2] Current sales count
|
|
[3] is Premium priced, out of town and Jeuno have higher costs, everything else is lower
|
|
[4] Shadowreign zone flag
|
|
[5] Position player is sent to after event, if applicable
|
|
--]]
|
|
|
|
xi.chocobo.chocoboInfo =
|
|
{
|
|
[xi.zone.AL_ZAHBI] = { levelReq = 20, sales = 0, premiumCost = false, past = false, pos = { 610, -24, 356, 128, 51 } },
|
|
[xi.zone.WAJAOM_WOODLANDS] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.SOUTHERN_SAN_DORIA_S] = { levelReq = 15, sales = 0, premiumCost = false, past = true , pos = { 94, -62, 266, 40, 81 } },
|
|
[xi.zone.JUGNER_FOREST_S] = { levelReq = 20, sales = 0, premiumCost = true, past = true , pos = nil },
|
|
[xi.zone.BASTOK_MARKETS_S] = { levelReq = 15, sales = 0, premiumCost = false, past = true , pos = { 380, 0, 147, 192, 88 } },
|
|
[xi.zone.PASHHOW_MARSHLANDS_S] = { levelReq = 20, sales = 0, premiumCost = true, past = true , pos = nil },
|
|
[xi.zone.WINDURST_WATERS_S] = { levelReq = 15, sales = 0, premiumCost = false, past = true , pos = { 320, -4, -46, 192, 95 } },
|
|
[xi.zone.MERIPHATAUD_MOUNTAINS_S] = { levelReq = 20, sales = 0, premiumCost = true, past = true , pos = nil },
|
|
[xi.zone.LA_THEINE_PLATEAU] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.KONSCHTAT_HIGHLANDS] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.EASTERN_ALTEPA_DESERT] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.TAHRONGI_CANYON] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.YHOATOR_JUNGLE] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = nil },
|
|
[xi.zone.SOUTHERN_SAN_DORIA] = { levelReq = 15, sales = 0, premiumCost = false, past = false, pos = { -126, -62, 274, 101, 100 } },
|
|
[xi.zone.BASTOK_MINES] = { levelReq = 15, sales = 0, premiumCost = false, past = false, pos = { 580, 0, -305, 64, 107 } },
|
|
[xi.zone.WINDURST_WOODS] = { levelReq = 15, sales = 0, premiumCost = false, past = false, pos = { -122, -4, -520, 0, 116 } },
|
|
[xi.zone.UPPER_JEUNO] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = { 486, 8, -160, 128, 105 } },
|
|
[xi.zone.LOWER_JEUNO] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = { 340, 24, 608, 112, 110 } },
|
|
[xi.zone.PORT_JEUNO] = { levelReq = 20, sales = 0, premiumCost = true, past = false, pos = { -574, 2, 400, 0, 120 } },
|
|
[xi.zone.RABAO] = { levelReq = 20, sales = 0, premiumCost = false, past = false, pos = { 420, 8, 360, 64, 125 } },
|
|
[xi.zone.KAZHAM] = { levelReq = 20, sales = 0, premiumCost = false, past = false, pos = { -240, 0, 528, 64, 123 } },
|
|
[xi.zone.NORG] = { levelReq = 20, sales = 0, premiumCost = false, past = false, pos = { -456, 17, -348, 0, 123 } },
|
|
}
|
|
|
|
local timeServerCounter = 0
|
|
|
|
-- This runs on every zone in the xi.chocobo.chocoboInfo global.
|
|
-- This will also run for every unloaded zone (on another process), but the computation is so cheap it may not matter.
|
|
---@return nil
|
|
xi.chocobo.onTimeServerTick = function()
|
|
-- Run every (2,400 * 25) ms (1 minute)
|
|
if timeServerCounter % 25 == 0 then
|
|
for idx, _ in pairs(xi.chocobo.chocoboInfo) do
|
|
local sales = xi.chocobo.chocoboInfo[idx].sales
|
|
|
|
-- Reduce sales by 1, but don't go below zero
|
|
xi.chocobo.chocoboInfo[idx].sales = math.max(0, sales - 1)
|
|
end
|
|
end
|
|
|
|
timeServerCounter = timeServerCounter + 1
|
|
end
|
|
|
|
---@param zoneId integer
|
|
---@return nil
|
|
xi.chocobo.increaseSales = function(zoneId)
|
|
-- Increase sales to boost price
|
|
xi.chocobo.chocoboInfo[zoneId].sales = xi.chocobo.chocoboInfo[zoneId].sales + 1
|
|
end
|
|
|
|
---@param player CBaseEntity
|
|
---@return integer
|
|
xi.chocobo.getPrice = function(player)
|
|
local zoneId = player:getZoneID()
|
|
local sales = xi.chocobo.chocoboInfo[zoneId].sales
|
|
local premiumCost = xi.chocobo.chocoboInfo[zoneId].premiumCost
|
|
local basePrice = premiumCost and 100 or 50
|
|
local levelMult = premiumCost and 2 or 1
|
|
local level = player and player:getMainLvl() or 100 -- If we don't have a player, assume lvl 100
|
|
|
|
-- Calculate base price, such as 100 + 99 * 2
|
|
basePrice = math.floor(basePrice + level * levelMult) -- Floor just in case...
|
|
|
|
-- Add in the 5% per sale cost
|
|
return basePrice + math.floor(basePrice * 0.05 * sales)
|
|
end
|
|
|
|
---@param zone CZone
|
|
---@return nil
|
|
xi.chocobo.initZone = function(zone)
|
|
local zoneId = zone:getID()
|
|
|
|
if xi.chocobo.chocoboInfo[zoneId] then
|
|
xi.chocobo.chocoboInfo[zoneId].sales = 0
|
|
else
|
|
printf('[warning] bad zoneId %i in xi.chocobo.initZone (%s)', zoneId, zone:getName())
|
|
end
|
|
end
|
|
|
|
---@param player CBaseEntity
|
|
---@param npc CBaseEntity
|
|
---@param trade CTradeContainer
|
|
---@param eventSucceed integer
|
|
---@param eventFail integer
|
|
---@return nil
|
|
xi.chocobo.renterOnTrade = function(player, npc, trade, eventSucceed, eventFail)
|
|
local zoneId = player:getZoneID()
|
|
local info = xi.chocobo.chocoboInfo[zoneId]
|
|
|
|
if not info then
|
|
printf('[warning] player %s passed bad zoneId %i in xi.chocobo.renterOntrade', player:getName(), zoneId)
|
|
return
|
|
end
|
|
|
|
local validChocopassZones = set({
|
|
xi.zone.WINDURST_WOODS,
|
|
xi.zone.BASTOK_MINES,
|
|
xi.zone.SOUTHERN_SAN_DORIA,
|
|
})
|
|
|
|
if
|
|
npcUtil.tradeHasExactly(trade, xi.item.FREE_CHOCOPASS) and
|
|
validChocopassZones[zoneId] -- Chocopass does nothing for Non-Starter Cities
|
|
then
|
|
local currency = player:getGil()
|
|
local price = 0
|
|
player:setLocalVar('Chocopass', 1)
|
|
player:setLocalVar('ChocopassDuration', 180)
|
|
player:startEvent(eventSucceed, price, currency)
|
|
elseif npcUtil.tradeHasExactly(trade, xi.item.CHOCOBO_TICKET) then
|
|
if not info.past then -- Does nothing in past zones
|
|
if
|
|
player:getMainLvl() >= 20 and
|
|
player:hasKeyItem(xi.ki.CHOCOBO_LICENSE)
|
|
then
|
|
local currency = player:getGil()
|
|
local price = 0
|
|
local duration = 1800 + (player:getMod(xi.mod.CHOCOBO_RIDING_TIME) * 60)
|
|
|
|
player:setLocalVar('Chocopass', 1)
|
|
player:setLocalVar('ChocopassDuration', duration)
|
|
player:startEvent(eventSucceed, price, currency)
|
|
else
|
|
player:startEvent(eventFail) -- Fail Closed
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param player CBaseEntity
|
|
---@param npc CBaseEntity
|
|
---@param eventSucceed integer
|
|
---@param eventFail integer
|
|
---@return nil
|
|
xi.chocobo.renterOnTrigger = function(player, npc, eventSucceed, eventFail)
|
|
local mLvl = player:getMainLvl()
|
|
local zoneId = player:getZoneID()
|
|
local info = xi.chocobo.chocoboInfo[zoneId]
|
|
local canRace = xi.chocoboGame.raceCheck(player, npc)
|
|
|
|
if info then
|
|
if
|
|
player:hasKeyItem(xi.ki.CHOCOBO_LICENSE) and
|
|
mLvl >= info.levelReq and
|
|
(player:hasCompletedMission(xi.mission.log_id.WOTG, xi.mission.id.wotg.BACK_TO_THE_BEGINNING) or not info.past)
|
|
then
|
|
if canRace then -- Check if NPC can start A Chocobo Riding Game
|
|
xi.chocoboGame.startRaceEvent(player, canRace, eventSucceed)
|
|
else
|
|
local price = xi.chocobo.getPrice(player)
|
|
player:setLocalVar('[CHOCOBO]price', price)
|
|
|
|
local currency = 0
|
|
if info.past then
|
|
currency = player:getCurrency('allied_notes')
|
|
else
|
|
currency = player:getGil()
|
|
end
|
|
|
|
local lowLevel = (mLvl < 20) and 1 or 0
|
|
|
|
player:startEvent(eventSucceed, price, currency, lowLevel)
|
|
end
|
|
else
|
|
player:startEvent(eventFail)
|
|
end
|
|
else
|
|
printf('[warning] player %s passed bad zoneId %i in xi.chocobo.renterOnTrigger', player:getName(), zoneId)
|
|
end
|
|
end
|
|
|
|
---@param player CBaseEntity
|
|
---@param csid integer
|
|
---@param option integer
|
|
---@param eventSucceed integer
|
|
---@return nil
|
|
xi.chocobo.renterOnEventFinish = function(player, csid, option, eventSucceed)
|
|
local chocoGame = player:getCharVar('[ChocoGame]DestCity')
|
|
|
|
if csid == eventSucceed and option == 0 then
|
|
local mLvl = player:getMainLvl()
|
|
local zoneId = player:getZoneID()
|
|
local info = xi.chocobo.chocoboInfo[zoneId]
|
|
local trade = player:getLocalVar('Chocopass')
|
|
local duration = 900
|
|
|
|
if not info then
|
|
printf('[warning] player %s passed bad zoneId %i in xi.chocobo.renterOnEventFinish', player:getName(), zoneId)
|
|
return
|
|
end
|
|
|
|
if trade == 1 then -- If the player used a Chocopass/Chocobo Ticket
|
|
duration = player:getLocalVar('ChocopassDuration')
|
|
player:tradeComplete()
|
|
|
|
player:setLocalVar('Chocopass', 0)
|
|
player:setLocalVar('ChocopassDuration', 0)
|
|
else -- Regular rental
|
|
local price = player:getLocalVar('[CHOCOBO]price')
|
|
player:setLocalVar('[CHOCOBO]price', 0)
|
|
|
|
if mLvl >= 20 then
|
|
duration = 1800 + (player:getMod(xi.mod.CHOCOBO_RIDING_TIME) * 60)
|
|
end
|
|
|
|
if chocoGame ~= 0 then -- Start A Chocobo Riding Game
|
|
xi.chocoboGame.beginRace(player, option)
|
|
elseif -- Deduct normal chocobo cost
|
|
price and
|
|
(info.past and player:getCurrency('allied_notes') >= price) or
|
|
(not info.past and player:delGil(price))
|
|
then
|
|
if info.past then
|
|
player:delCurrency('allied_notes', price)
|
|
end
|
|
|
|
xi.chocobo.increaseSales(zoneId)
|
|
else
|
|
printf('[warning] player %s reached succeed without enough currency in xi.chocobo.renterOnEventFinish',
|
|
player:getName())
|
|
return
|
|
end
|
|
end
|
|
|
|
player:addStatusEffect(xi.effect.MOUNTED, { duration = duration, origin = player, silent = true })
|
|
|
|
-- Renting a chocobo force despawns every type of pets
|
|
-- Note: This does not honor cooldown reductions offered otherwise when dismissing pets with full life.
|
|
-- TODO: Supposed to despawn without animation
|
|
player:despawnPet()
|
|
|
|
if info.pos then
|
|
player:setPos(unpack(info.pos))
|
|
end
|
|
elseif chocoGame ~= 0 and csid == eventSucceed then
|
|
player:despawnPet()
|
|
xi.chocoboGame.beginRace(player, option)
|
|
end
|
|
end
|