From 7f3d94b50e87756cc30a1aeb266abbeda1518b29 Mon Sep 17 00:00:00 2001 From: sruon Date: Tue, 14 Apr 2026 00:26:38 -0600 Subject: [PATCH] Cleanup teleport NPC scripts --- scripts/globals/homepoint.lua | 26 ++- scripts/globals/sparkshop.lua | 37 ++++- scripts/globals/waypoint.lua | 155 +++++++++++++++++- .../npcs/Runic_Portal.lua | 40 ++++- .../npcs/Sharin-Garin.lua | 12 +- 5 files changed, 250 insertions(+), 20 deletions(-) diff --git a/scripts/globals/homepoint.lua b/scripts/globals/homepoint.lua index 098f6515581..d9d2d48c917 100644 --- a/scripts/globals/homepoint.lua +++ b/scripts/globals/homepoint.lua @@ -157,11 +157,26 @@ local function goToHP(player, choice, index) local origin = player:getLocalVar('originIndex') local hasKI = player:hasKeyItem(xi.ki.RHAPSODY_IN_WHITE) + if homepointData[origin] == nil then + return + end + if choice == selection.SAME_ZONE then -- For zones like Sky and Uleguerand Range, this will force gil deletion -- Positioning within same zone handled by client, no need to setPos player:delGil(getCost(origin, origin, hasKI)) elseif choice == selection.TELEPORT then + if homepointData[index] == nil then + return + end + + local hpBit = index % 32 + local hpSet = math.floor(index / 32) + + if not player:hasTeleport(xi.teleport.type.HOMEPOINT, hpBit, hpSet) then + return + end + player:delGil(getCost(origin, index, hasKI)) player:setPos(unpack(homepointData[index].dest)) end @@ -203,6 +218,10 @@ xi.homepoint.onEventUpdate = function(player, csid, option, npc) if choice >= selection.SET_LAYOUT and choice <= selection.REP_FAVORITE then local index = bit.rshift(bit.lshift(option, 8), 24) -- Ret HP # + if homepointData[index] == nil then + return + end + if choice == selection.ADD_FAVORITE then local temp = 0 for x = 1, 9 do @@ -222,7 +241,12 @@ xi.homepoint.onEventUpdate = function(player, csid, option, npc) end end elseif choice == selection.REP_FAVORITE then - favs[bit.rshift(option, 24) + 1] = index + local slot = bit.rshift(option, 24) + 1 + if slot < 1 or slot > 9 then + return + end + + favs[slot] = index elseif choice == selection.SET_LAYOUT then -- 1 = Sort by content/expansion else sort by region favs[10] = bit.rshift(option, 16) == 1 and 1 or 0 diff --git a/scripts/globals/sparkshop.lua b/scripts/globals/sparkshop.lua index de8aacee49b..f06cce70211 100644 --- a/scripts/globals/sparkshop.lua +++ b/scripts/globals/sparkshop.lua @@ -659,7 +659,13 @@ function xi.sparkshop.onEventUpdate(player, csid, option, npc) -- 2. Grant Currency based on Vouchers spent (Category == 20) -- 3. Grant Provision Items based on Vouchers spent (Category == 30) if category <= 10 or category == 12 then - local item = optionToItem[category][selection] + local itemCategory = optionToItem[category] + local item = itemCategory and itemCategory[selection] + + if not item then + return + end + local cost = item.cost * qty -- makes sure player has room for three stacks of tomes @@ -670,13 +676,11 @@ function xi.sparkshop.onEventUpdate(player, csid, option, npc) end -- handles eminent ammo - if item.id == 21302 or item.id == 21316 or item.id == 21331 then - qty = 99 - cost = 5000 - - elseif item.id == 21355 then - qty = 99 - cost = 7000 + local emminentAmmoCosts = { [21302] = 5000, [21316] = 5000, [21331] = 5000, [21355] = 7000 } + local ammoCost = emminentAmmoCosts[item.id] + if ammoCost then + qty = 99 + cost = ammoCost end -- verifies and finishes transaction @@ -697,8 +701,12 @@ function xi.sparkshop.onEventUpdate(player, csid, option, npc) player:updateEvent(sparks, 0, 0, 0, 0, remainingLimit) elseif category == 20 then - local copperVouchersStored = player:getCurrency('aman_vouchers') local currency = optionToItem[category][selection] + if currency == nil then + return + end + + local copperVouchersStored = player:getCurrency('aman_vouchers') if copperVouchersStored >= qty then player:delCurrency('aman_vouchers', qty) @@ -723,8 +731,19 @@ function xi.sparkshop.onEventUpdate(player, csid, option, npc) player:updateEvent(sparks, player:getCurrency('aman_vouchers')) elseif category == 30 then + local validProvisions = + { + [xi.item.PLUTON] = true, + [xi.item.BEITETSU] = true, + [xi.item.RIFTBORN_BOULDER] = true, + } + local copperVouchersStored = player:getCurrency('aman_vouchers') + if not validProvisions[selection] then + return + end + if copperVouchersStored >= qty then if player:addItem({ id = selection, quantity = 2 * qty, silent = true }) then player:delCurrency('aman_vouchers', qty) diff --git a/scripts/globals/waypoint.lua b/scripts/globals/waypoint.lua index 871d76e8b0c..8842b8390b0 100644 --- a/scripts/globals/waypoint.lua +++ b/scripts/globals/waypoint.lua @@ -133,6 +133,21 @@ end local tableIndexToWaypoint = buildTeleportLookup() +local groupStartIndex = +{ + [1] = 0, -- Western Adoulin + [2] = 20, -- Eastern Adoulin + [3] = 40, -- Ceizak Battlegrounds + [4] = 30, -- Yahse Hunting Grounds + [5] = 50, -- Foret de Hennetiel + [6] = 60, -- Morimar Basalt Fields + [7] = 70, -- Yorcia Weald + [8] = 80, -- Marjami Ravine + [9] = 90, -- Kamihr Drifts + [10] = 100, -- Lower Jeuno + [11] = 300, -- Enigmatic Devices +} + local runeKeyItems = { xi.ki.SAN_DORIA_WARP_RUNE, @@ -326,25 +341,151 @@ end -- destinationGroup = bit.band(bit.rshift(option, 7), 0xF) -- destinationOffset = bit.band(bit.rshift(option, 11), 0xF) +local function isFieldWaypoint(groupId) + return groupId ~= nil and groupId >= 3 and groupId <= 9 +end + +local function isAdoulinCity(groupId) + return groupId == 1 or groupId == 2 +end + +local function getWaypointTravelCost(sourceIndex, destIndex) + local dest = waypointInfo[destIndex] + if dest == nil then + return 0 + end + + if destIndex >= 300 then + return 150 -- Enigmatic Devices + end + + if destIndex >= 200 then + return 100 -- Warp Runes + end + + local source = waypointInfo[sourceIndex] + if source == nil then + return 0 + end + + local sourceZone = source[4][5] + local destZone = dest[4][5] + local sameZone = sourceZone == destZone or (isAdoulinCity(source[2]) and isAdoulinCity(dest[2])) + + if isFieldWaypoint(dest[2]) then + return sameZone and 2 or 50 -- Field: 2 intra-zone, 50 cross-zone + else + return sameZone and 1 or 15 -- City: 1 intra-zone, 15 cross-zone + end +end + xi.waypoint.onEventUpdate = function(player, csid, option, npc) local ID = zones[player:getZoneID()] - local travelCost = bit.rshift(option, 21) - if player:getCurrency('kinetic_unit') >= travelCost then - player:updateEvent(0, 0, 0, 0, 0, 0, 0, 1) - player:delCurrency('kinetic_unit', travelCost) - player:messageSpecial(ID.text.EXPENDED_KINETIC_UNITS, travelCost) - else + local destGroup = bit.band(bit.rshift(option, 7), 0xF) + local destOffset = bit.band(bit.rshift(option, 11), 0xF) + local start = groupStartIndex[destGroup] + local destIndex = start and start + destOffset or nil + local waypoint = destIndex and waypointInfo[destIndex] + + if waypoint == nil then + return + end + + -- Validate destination is unlocked + if waypoint[5] ~= nil then + if not player:hasTeleport(xi.teleport.type.WAYPOINT, waypoint[5]) then + return + end + elseif destIndex >= 200 and destIndex <= 210 then + local runeIndex = destIndex - 199 + if + runeKeyItems[runeIndex] and + not player:hasKeyItem(runeKeyItems[runeIndex]) + then + return + end + end + + local sourceIndex = getWaypointIndex(npc) + local source = waypointInfo[sourceIndex] + local sourceGroup = source and source[2] + local destWaypointGrp = waypoint[2] + + -- Field destinations only reachable from Adoulin city waypoints + if isFieldWaypoint(destWaypointGrp) and not isAdoulinCity(sourceGroup) then + return + end + + -- Lower Jeuno can only reach Adoulin cities + if sourceGroup == 10 and not isAdoulinCity(destWaypointGrp) then + return + end + + local travelCost = getWaypointTravelCost(sourceIndex, destIndex) + + if player:getCurrency('kinetic_unit') < travelCost then player:messageSpecial(ID.text.INSUFFICIENT_UNITS) + return end + + player:delCurrency('kinetic_unit', travelCost) + player:setLocalVar('waypointPaid', 1) + player:messageSpecial(ID.text.EXPENDED_KINETIC_UNITS, travelCost) + player:updateEvent(0, 0, 0, 0, 0, 0, 0, 1) end xi.waypoint.onEventFinish = function(player, csid, option, npc) if option > 0 and option <= 303 then + local waypoint = waypointInfo[option] + if waypoint == nil then + return + end + + -- Validate destination is unlocked + if waypoint[5] ~= nil then + if not player:hasTeleport(xi.teleport.type.WAYPOINT, waypoint[5]) then + return + end + elseif option >= 200 and option <= 210 then + local runeIndex = option - 199 + if + runeKeyItems[runeIndex] and + not player:hasKeyItem(runeKeyItems[runeIndex]) + then + return + end + end + + -- Validate route + local sourceIndex = getWaypointIndex(npc) + local source = waypointInfo[sourceIndex] + local sourceGroup = source and source[2] + local destGroup = waypoint[2] + + if isFieldWaypoint(destGroup) and not isAdoulinCity(sourceGroup) then + return + end + + if sourceGroup == 10 and not isAdoulinCity(destGroup) then + return + end + + if player:getLocalVar('waypointPaid') == 0 then + local travelCost = getWaypointTravelCost(sourceIndex, option) + if player:getCurrency('kinetic_unit') < travelCost then + return + end + + player:delCurrency('kinetic_unit', travelCost) + end + + player:setLocalVar('waypointPaid', 0) + if player:getCurrentMission(xi.mission.log_id.SOA) == xi.mission.id.soa.ONWARD_TO_ADOULIN then player:setPos(169.638, 0.491, -27.128, 207, xi.zone.CEIZAK_BATTLEGROUNDS) else - player:setPos(unpack(waypointInfo[option][4])) + player:setPos(unpack(waypoint[4])) end elseif option == 1000 then -- Decline Confirmation (Default Off) diff --git a/scripts/zones/Aht_Urhgan_Whitegate/npcs/Runic_Portal.lua b/scripts/zones/Aht_Urhgan_Whitegate/npcs/Runic_Portal.lua index 2340b9b8e5b..ccb5c5ce4ef 100644 --- a/scripts/zones/Aht_Urhgan_Whitegate/npcs/Runic_Portal.lua +++ b/scripts/zones/Aht_Urhgan_Whitegate/npcs/Runic_Portal.lua @@ -80,8 +80,38 @@ entity.onEventFinish = function(player, csid, option, npc) [1006] = xi.teleport.id.NYZUL_SP, } + local runicPortals = player:getTeleport(xi.teleport.type.RUNIC_PORTAL) + local portalBits = + { + [1] = 0x02, -- Azouph + [2] = 0x04, -- Dvucca + [3] = 0x08, -- Mamool + [4] = 0x10, -- Halvung + [5] = 0x20, -- Ilrusi + [6] = 0x40, -- Nyzul + } + if csid == 101 and option > 100 and option < 1007 then + -- Map option back to portal index for bitmask check + local portalIndex if option >= 101 and option <= 106 then + portalIndex = option - 100 + elseif option >= 1001 and option <= 1006 then + portalIndex = option - 1000 + end + + if + portalIndex == nil or + bit.band(runicPortals, portalBits[portalIndex]) == 0 + then + return + end + + if option >= 101 and option <= 106 then + if not player:hasKeyItem(xi.ki.RUNIC_PORTAL_USE_PERMIT) then + return + end + player:delKeyItem(xi.ki.RUNIC_PORTAL_USE_PERMIT) xi.teleport.to(player, portalPick[option]) elseif option >= 1001 and option <= 1006 then @@ -92,7 +122,15 @@ entity.onEventFinish = function(player, csid, option, npc) player:messageSpecial(ID.text.SUFFICIENT_IMPERIAL_STANDING) end end - elseif csid == 101 and option >= 1 and option <= 6 then -- Captains dont lose permit + elseif csid == 101 and option >= 1 and option <= 6 then + if not player:hasKeyItem(xi.ki.CAPTAIN_WILDCAT_BADGE) then + return + end + + if bit.band(runicPortals, portalBits[option]) == 0 then + return + end + xi.teleport.to(player, portalPick[option]) elseif csid >= 120 and csid <= 125 and option == 1 then xi.teleport.to(player, portalPick[csid]) diff --git a/scripts/zones/Aht_Urhgan_Whitegate/npcs/Sharin-Garin.lua b/scripts/zones/Aht_Urhgan_Whitegate/npcs/Sharin-Garin.lua index 771662ee4c5..ef9efa1e466 100644 --- a/scripts/zones/Aht_Urhgan_Whitegate/npcs/Sharin-Garin.lua +++ b/scripts/zones/Aht_Urhgan_Whitegate/npcs/Sharin-Garin.lua @@ -19,13 +19,21 @@ entity.onTrigger = function(player, npc) end entity.onEventFinish = function(player, csid, option, npc) + if csid ~= 140 or xi.besieged.getAstralCandescence() == 0 then + return + end + if - csid == 140 and option == 1 and + player:getCurrency('imperial_standing') >= 200 and npcUtil.giveKeyItem(player, xi.ki.RUNIC_PORTAL_USE_PERMIT) then player:delCurrency('imperial_standing', 200) - elseif csid == 140 and option == 2 then + elseif + option == 2 and + player:hasKeyItem(xi.ki.CAPTAIN_WILDCAT_BADGE) and + not player:hasKeyItem(xi.ki.RUNIC_PORTAL_USE_PERMIT) + then npcUtil.giveKeyItem(player, xi.ki.RUNIC_PORTAL_USE_PERMIT) end end