landsandboat/scripts/globals/chocobo_racing.lua
2025-09-19 13:43:05 +02:00

262 lines
13 KiB
Lua

-----------------------------------
-- Chocobo Racing
-- https://www.bg-wiki.com/ffxi/Category:Chocobo_Racing
-- https://ffxiclopedia.fandom.com/wiki/Chocobo_Racing_Guide
-----------------------------------
require('scripts/globals/packet')
-----------------------------------
xi = xi or {}
xi.chocoboRacing = xi.chocoboRacing or {}
-- FOR HEAVILY-IN-DEVELOPMET TESTING, you can force these setting:
-- TODO: When ready for release, publish these to main settings files.
xi.settings.main.ENABLE_CHOCOBO_RACING = false
xi.settings.main.DEBUG_CHOCOBO_RACING = false
-- Notes:
-- Since there is a timed element, packet elements, and a lot of data to fill in,
-- it makes sense to move pretty much everything apart from the CS handling down
-- into core. While developing, things can stay up here, but as this approaches
-- a stable state everything should be pushed down.
-- To Run:
-- !exec xi.chocoboRacing.startRace()
-- https://github.com/atom0s/XiPackets/blob/main/world/server/0x0069/README.md
-- From your capture, paste in the contents of: packetviewer/incoming/0x069.log
local combinedPacketData = [[
[2024-06-08 16:51:00]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6C 1B 01 00 08 01 C8 00 00 00 00 00 00 80 0 | idl.............
1 | CF F7 8F C0 85 6B C2 C2 00 00 00 00 32 32 64 00 1 | .....k......22d.
2 | 06 1B 00 00 00 00 00 20 00 00 00 00 00 00 00 00 2 | ....... ........
3 | 01 00 05 02 17 10 17 20 17 30 17 40 17 50 00 60 3 | ....... .0.@.P.`
4 | 00 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 | .p..............
5 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5 | ................
6 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6 | ................
7 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7 | ................
8 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8 | ................
9 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 9 | ................
A | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A | ................
B | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B | ................
C | 00 00 00 00 00 00 00 00 -- -- -- -- -- -- -- -- C | ........--------
[2024-06-08 16:51:01]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6D 1B 02 00 60 00 41 00 00 00 00 00 00 00 0 | idm...`.A.....`.
1 | 00 00 08 15 41 00 00 00 00 00 00 00 00 00 00 00 1 | ....A.....`....!
2 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2 | b.........V.!...
3 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3 | ......$.b.......
4 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 | ..""A.....`...".
5 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5 | b.....@@.. .0...
6 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6 | ......"#........
7 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7 | ................
8 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8 | ................
9 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 9 | ................
A | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A | ................
B | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B | ................
C | 00 00 00 00 00 00 00 00 -- -- -- -- -- -- -- -- C | ........--------
[2024-06-08 16:51:01]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6D 1B 03 00 C0 00 88 88 88 88 89 66 98 85 0 | idm..........f..
1 | 00 B3 00 20 89 66 98 85 89 77 99 86 00 00 00 00 1 | ... .f...w......
2 | 89 77 99 86 89 77 99 86 00 00 00 00 89 77 99 86 2 | .w...w.......w..
3 | 88 77 99 86 80 80 00 03 88 77 99 86 88 77 99 86 3 | .w.......w...w..
4 | 00 00 00 00 88 77 99 86 78 67 88 88 02 40 00 04 4 | .....w..xg...@..
5 | 78 67 88 88 77 67 88 78 00 00 00 00 77 67 88 78 5 | xg..wg.x....wg.x
6 | 77 67 88 87 08 08 00 02 77 67 88 87 77 67 78 87 6 | wg......wg..wgx.
7 | 01 00 08 04 77 67 78 87 77 67 78 87 00 00 00 00 7 | ....wgx.wgx.....
8 | 77 67 78 87 77 67 78 87 00 00 00 00 77 67 78 87 8 | wgx.wgx.....wgx.
9 | 77 68 78 87 00 00 00 00 77 68 78 87 77 68 79 87 9 | whx.....whx.why.
A | 00 00 00 00 77 68 79 87 66 57 79 76 04 30 CB 06 A | ....why.fWyv.0..
B | 66 57 79 76 66 57 79 86 20 00 08 04 66 57 79 86 B | fWyvfWy. ...fWy.
C | 66 67 79 85 00 00 00 00 -- -- -- -- -- -- -- -- C | fgy.....--------
[2024-06-08 16:51:01]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6D 1B 03 10 C0 00 66 67 79 85 67 57 79 86 0 | idm.....fgy.gWy.
1 | 00 00 00 00 67 57 79 86 67 57 79 86 00 00 00 00 1 | ....gWy.gWy.....
2 | 67 57 79 86 67 57 79 86 00 00 00 00 67 57 79 86 2 | gWy.gWy.....gWy.
3 | 67 67 79 95 00 00 00 00 67 67 79 95 67 68 79 95 3 | ggy.....ggy.ghy.
4 | 00 00 00 00 67 68 79 95 67 58 79 96 00 00 00 00 4 | ....ghy.gXy.....
5 | 67 58 79 96 67 68 79 95 10 0C E3 06 67 68 79 95 5 | gXy.ghy.....ghy.
6 | 68 79 7A A5 40 99 26 06 68 79 7A A5 89 6A 69 A4 6 | hyz.@.&.hyz..ji.
7 | 00 07 F8 21 89 6A 69 A4 89 6A 69 A4 95 00 00 00 7 | ...!.ji..ji.....
8 | 89 6A 69 A4 89 6A 69 A4 95 00 00 00 89 6A 69 A4 8 | .ji..ji......ji.
9 | 89 6A 69 A4 D7 00 00 00 89 6A 69 A4 89 6A 69 B4 9 | .ji......ji..ji.
A | F7 00 00 00 89 6A 69 B4 89 69 69 B4 F7 00 00 00 A | .....ji..ii.....
B | 89 69 69 B4 79 69 69 B4 00 00 00 00 79 69 69 B4 B | .ii.yii.....yii.
C | 79 59 69 B4 00 00 00 00 -- -- -- -- -- -- -- -- C | yYi.....--------
[2024-06-08 16:51:01]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6D 1B 04 00 04 00 34 16 25 70 0A 00 00 00 0 | idm.....4.%p....
1 | 30 EE 00 00 20 73 64 15 00 00 00 00 00 00 00 00 1 | 0... sd.........
2 | FB FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 2 | ................
3 | 00 00 34 00 63 61 6D 65 72 61 42 00 00 00 00 00 3 | ..4.cameraB.....
4 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 | ................
5 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5 | ................
6 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6 | ................
7 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7 | ................
8 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8 | ................
9 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 9 | ................
A | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A | ................
B | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B | ................
C | 00 00 00 00 00 00 00 00 -- -- -- -- -- -- -- -- C | ........--------
[2024-06-08 16:51:01]
| 0 1 2 3 4 5 6 7 8 9 A B C D E F | 0123456789ABCDEF
----------------------------------------------------- ----------------------
0 | 69 64 6E 1B 05 00 00 00 27 00 57 00 00 00 00 00 0 | idn.....'.W.....
1 | 00 00 00 00 00 00 00 00 00 00 00 00 32 32 64 00 1 | ............22d.
2 | 02 19 00 00 00 00 00 20 00 00 00 00 00 00 00 00 2 | ....... ........
3 | 07 00 20 00 00 00 01 00 00 00 00 00 00 00 00 00 3 | .. .............
4 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 | ................
5 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5 | ................
6 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6 | ................
7 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7 | ................
8 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8 | ................
9 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 9 | ................
A | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A | ................
B | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B | ................
C | 00 00 00 00 00 00 00 00 -- -- -- -- -- -- -- -- C | ........--------
]]
local debug = function(player, ...)
if xi.settings.main.DEBUG_CHOCOBO_RACING then
local t = { ... }
print(unpack(t))
player:printToPlayer(table.concat(t, ' '), xi.msg.channel.SYSTEM_3, '')
end
end
local setTimer = function(player, npcId)
end
setTimer = function(player, npcId)
player:timer(400, function(playerArg)
playerArg:sendEmptyEntityUpdateToPlayer(GetNPCByID(npcId))
setTimer(playerArg, npcId)
end)
end
local startRaceImpl = function(player)
debug(player, 'Starting race')
for _, packet in ipairs(xi.packet.parseMultiplePackets(combinedPacketData)) do
if packet[0x04 + 1] == 0x02 then
-- Races:
-- 0x0: Galka
-- 0x1: Hume M
-- 0x2: Hume F
-- 0x3: Elvaan M
-- 0x4: Elvaan F
-- 0x5: Tarutaru M
-- 0x6: Tarutaru F
-- 0x7: Mithra
local races = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }
-- Colors:
-- 0x0: Yellow
-- 0x1: Yellow
-- 0x2: Black
-- 0x3: Black
-- 0x4: Blue
-- 0x5: Blue
-- 0x6: Red
-- 0x7: Red
-- 0x8: Green
local colors = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 }
for _, offset in ipairs({ 0x0C, 0x18, 0x24, 0x30, 0x3C, 0x48, 0x54, 0x60 }) do
local race = utils.randomEntry(races)
local color = utils.randomEntry(colors)
-- Remember Lua is 1-offset!
packet[offset + 0 + 1] = 0x0E
packet[offset + 1 + 1] = 0x0C
packet[offset + 2 + 1] = 0x60
packet[offset + 3 + 1] = 0x80
packet[offset + 4 + 1] = 0x00
packet[offset + 5 + 1] = 0x00
packet[offset + 6 + 1] = 0x08
packet[offset + 7 + 1] = bit.lshift(race, 4) + color
packet[offset + 8 + 1] = 0x41
packet[offset + 9 + 1] = 0x00
packet[offset + 10 + 1] = 0x00
packet[offset + 11 + 1] = 0x00
end
end
player:sendDebugPacket(packet)
end
player:startEvent(210, 3885177, 3885177, -132554, -14500, 1344, -1, 610862737, 1)
-- 'Rungaga' seems to an anchor NPC that everything is orchestrated through?
setTimer(player, zones[xi.zone.CHOCOBO_CIRCUIT].npc.RUNGAGA)
end
xi.chocoboRacing.startRace = function()
local players = GetZone(xi.zone.CHOCOBO_CIRCUIT):getPlayers()
for _, player in ipairs(players) do
startRaceImpl(player)
end
end
-- 0x05C
xi.chocoboRacing.onEventUpdate = function(player, csid, option, npc)
debug(player, 'update', csid, option)
local chocobos =
{
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
}
local winningsPerQuill = 1076
local unknown0 = 389304928
local unknown1 = -1
local unknown2 = 610862737
local unknown3 = -5
-- NOTE: The race win order seems to be encoded in here
if csid == 210 then
if option == 5 then
debug(player, 'Intro banner')
player:updateEvent(1, 0, -132554, -14500, unknown0, unknown1, unknown2, unknown3)
elseif option == 274 then
debug(player, 'Names 1-4')
player:updateEventString(chocobos[1], chocobos[2], chocobos[3], chocobos[4])
player:updateEvent(70, 0, 7, 4, unknown0, unknown1, unknown2, unknown3)
elseif option == 510 or option == 530 then
debug(player, 'Names 5-8 and Start')
player:updateEventString(chocobos[5], chocobos[6], chocobos[7], chocobos[8])
player:updateEvent(70, 0, 7, 4, unknown0, unknown1, unknown2, unknown3)
elseif option == 17 then
debug(player, 'End and announce winnings')
player:updateEvent(70, 0, winningsPerQuill, 1, 0, 3, 3, unknown3)
end
end
end
xi.chocoboRacing.onEventFinish = function(player, csid, option, npc)
debug(player, 'finish', csid, option)
if csid == 210 and option == 17 then
debug(player, 'Hand out winnings')
end
end