From d9e47a0e23ac23f5089d69787bbd7c37d82d7232 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 20:18:03 +0100 Subject: [PATCH 01/10] Raising: First pass of Chocobo Whistle questing, registration, and usage --- .../hobbies/chocobo_raising/choco_data.lua | 1 + .../hobbies/chocobo_raising/event_playout.lua | 2 +- .../hobbies/chocobo_raising/event_vm.lua | 92 +++++++++++++++++-- scripts/items/chocobo_whistle.lua | 21 +++-- .../quests/hiddenQuests/Chocobo_Whistle.lua | 77 ++++++++++++++++ 5 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 scripts/quests/hiddenQuests/Chocobo_Whistle.lua diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 1e0e14547b1..063bb18046b 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -170,6 +170,7 @@ xi.chocoboRaising.initChocoState = function(player) -- : check ensures you have to finish talking to the trainer, pass some time, and then -- : come back -- TODO: Need to zone too? + -- TODO: Does this play out as part of the report, or before it? if age >= 8 and -- At least one day has to have passed, so this is the earliest possible age player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) and diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index bbc456295f9..6e7e2f2f4a2 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -231,7 +231,7 @@ xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, [xi.chocoboRaising.cutscenes.THAT_SHOULD_BE_ENOUGH] = function() debug('Removing KI White Handkerchief') player:delKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) - -- TODO: What is the general marker that this quest is completed? + player:setCharVar('HQuest[ChocoboWhistle]Prog', 1) end, [xi.chocoboRaising.cutscenes.HAVENT_SEEN_YOU] = function() diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 4434e87113f..35370c3f053 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -18,6 +18,7 @@ local vmOpCodes = UNKNOWN_216 = 216, BUY_CHOCOBO_WHISTLE = 221, RECEIVE_CHOCOBO_WHISTLE = 222, + REGISTER_CHOCOBO_WHISTLE = 223, DEBUG_ABILITIES_PRINT = 229, DEBUG_USER_WORK_PRINT = 232, GIVE_UP_CHOCOBO = 240, @@ -31,6 +32,7 @@ local vmOpCodes = UNKNOWN_252 = 252, SET_BASIC_CARE_PLAN_1 = 254, BRIEF_REPORT = 256, + WHISTLE_GAME_RESULT = 344, DEBUG_GO_FORWARD_1_UNIT = 482, SKIP_REPORT = 504, SET_BASIC_CARE_PLAN_2 = 510, @@ -58,6 +60,7 @@ local vmOpCodeNames = [vmOpCodes.UNKNOWN_216] = 'Unknown 216 (forced renaming?)', [vmOpCodes.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive chocobo whistle', + [vmOpCodes.REGISTER_CHOCOBO_WHISTLE] = 'Register chocobo whistle', [vmOpCodes.DEBUG_ABILITIES_PRINT] = 'Debug abilities print', [vmOpCodes.DEBUG_USER_WORK_PRINT] = 'Debug user work print', [vmOpCodes.GIVE_UP_CHOCOBO] = 'Give up your chocobo', @@ -71,6 +74,7 @@ local vmOpCodeNames = [vmOpCodes.UNKNOWN_252] = 'Unknown 252', [vmOpCodes.SET_BASIC_CARE_PLAN_1] = 'Set basic care plan 1', [vmOpCodes.BRIEF_REPORT] = 'Brief report', + [vmOpCodes.WHISTLE_GAME_RESULT] = 'Chocobo Whistle game result', [vmOpCodes.DEBUG_GO_FORWARD_1_UNIT] = 'Debug go forward 1 unit', [vmOpCodes.SKIP_REPORT] = 'Skip the report', [vmOpCodes.SET_BASIC_CARE_PLAN_2] = 'Set basic care plan 2', @@ -309,6 +313,8 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.INTRO_MENU_PT_3] = function() + local menuFlags = 0xFFFFFFFF + -- Define menu options -- bit.lshift(0x01, 0): Ask about your chocobo's condition local askAboutChocoboCondition = -bit.lshift(0x01, 0) @@ -329,7 +335,12 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end -- bit.lshift(0x01, 4): Request Documentation - -- bit.lshift(0x01, 5): Register to call your chocobo + + if player:getCharVar('HQuest[ChocoboWhistle]Prog') >= 4 then + local registerToCallYourChocobo = -bit.lshift(0x01, 5) + menuFlags = menuFlags + registerToCallYourChocobo + end + -- bit.lshift(0x01, 6): Receive your chocobo whistle -- bit.lshift(0x01, 7): Purchase a chocobo whistle @@ -358,7 +369,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local exit = -bit.lshift(0x01, 31) -- Enable menu options (remove bits from 0xFFFFFFFF) - local menuFlags = 0xFFFFFFFF + + menuFlags = menuFlags + askAboutChocoboCondition + careForYourChocobo + setUpCareSchedule + @@ -593,8 +604,15 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local csWeather = xi.chocoboRaising.getWeatherInZone(walkZoneId) local output = { 0, 0, 0, 0, 0, 0, 0, 0 } - -- Will there be an event? - if math.random(1, 100) <= xi.chocoboRaising.walkEventChance then + -- NOTE: We have to enforce the stage, otherwise this CS will freeze the client. + -- : The result is client-side, we can't force success. + local doWhistleWalkEvent = player:getCharVar('HQuest[ChocoboWhistle]Prog') == 2 and + chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 + + if doWhistleWalkEvent then -- Force whistle event + debug('Forcing Chocobo Whistle Walk event') + output = { 0, 14929, 1, 0, 4, 0, 2, 0 } + elseif math.random(1, 100) <= xi.chocoboRaising.walkEventChance then -- Will there be a random event? local possibleEvents = {} -- If not holding an item, it's possible to find an item @@ -602,10 +620,11 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) table.insert(possibleEvents, 1) end + -- TODO: This is wrong? -- If you haven't completed the White Handkerchief quest yet - if not player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) then - table.insert(possibleEvents, 2) - end + -- if not player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) then + -- table.insert(possibleEvents, 2) + -- end -- TODO: Meet other chocobos & raisers @@ -615,8 +634,9 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end end + -- Patch with the base CS and other bits output[1] = baseCS - output[2] = energyFlag + -- output[2] = energyFlag output[5] = chocoState.stage output[8] = csWeather @@ -950,6 +970,25 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO end, + [vmOpCodes.WHISTLE_GAME_RESULT] = function() + local keyItem = xi.keyItem.HANDKERCHIEF + + -- TODO: Handle this: + -- : A successful search does not guarantee you'll find the item. That means the item is not in that area, and you should look in the other two areas. + + -- TODO: If you have a high bond with your chocobo, this gets upgraded to be the + -- : DIRTY_HANDKERCHIEF, apparently. + + -- TODO: What are the chances here? + if math.random(1, 100) < 25 then -- success + player:updateEvent(keyItem, 0, 0, 0, 0, 1, 0, 0) + player:addKeyItem(keyItem) + player:setCharVar('HQuest[ChocoboWhistle]Prog', 3) + else -- failure + player:updateEvent(0, 0, 0, 0, 0, 2, 0, 0) + end + end, + [vmOpCodes.SKIP_REPORT] = function() -- TODO: Set up movement between chocoState.report.events and chocoState.csList to -- : include the length of each playout in days, so it can be used in handleCSUpdate() @@ -986,6 +1025,43 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO end, + [vmOpCodes.REGISTER_CHOCOBO_WHISTLE] = function() + debug('Registering field chocobo details') + player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) + + -- TODO: Shamelessly taken from !chocobo and other handlers + + -- Crest type + local enlargedCrest = 0 + + if chocoState.discernment >= 128 then + enlargedCrest = 1 + end + + -- Feet type + local enlargedFeet = 0 + + if chocoState.strength >= 128 then + enlargedFeet = 1 + end + + -- Tail feathers type + local moreTailFeathers = 0 + + if chocoState.endurance >= 128 then + moreTailFeathers = 1 + end + + local traits = + { + largeBeak = enlargedCrest, + fullTail = moreTailFeathers, + largeTalons = enlargedFeet, + } + + player:registerChocobo(chocoState.color, traits) + end, + [vmOpCodes.DEBUG_GO_FORWARD_1_UNIT] = function() -- TODO: Split stored age and time of creation so age can be manipulated player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) diff --git a/scripts/items/chocobo_whistle.lua b/scripts/items/chocobo_whistle.lua index 49568bf9b5c..168b048f0b4 100644 --- a/scripts/items/chocobo_whistle.lua +++ b/scripts/items/chocobo_whistle.lua @@ -1,10 +1,6 @@ ----------------------------------- -- ID: 15533 -- Item: Chocobo Whistle --- --- Notes: Can't use item below lv 20, no need to adjust duration for that. --- Can't normally use enchantments when level sync'd below items level so no need to check. --- Per wiki, can actually obtain without license, but cannot use, so we DO check that. ----------------------------------- ---@type TItem local itemObject = {} @@ -12,16 +8,29 @@ local itemObject = {} itemObject.onItemCheck = function(target, item, param, caster) if not target:canUseMisc(xi.zoneMisc.MOUNT) then return xi.msg.basic.CANT_BE_USED_IN_AREA - elseif not target:hasKeyItem(xi.ki.CHOCOBO_LICENSE) or target:hasEnmity() then - return xi.msg.basic.ITEM_UNABLE_TO_USE -- Todo: Verify/correct message, order of message priority. + elseif + target:getMainLvl() < 20 or -- TODO: setting? + not target:hasKeyItem(xi.ki.CHOCOBO_LICENSE) or -- TODO: Is this true? + target:hasEnmity() + then + return xi.msg.basic.ITEM_UNABLE_TO_USE -- TODO: Verify/correct message, order of message priority. + end + + -- TODO: Make a new binding for this that only gives back the actual m_FieldChocobo registered information + local info = target:getChocoboRaisingInfo() + if info == nil then + return xi.msg.basic.ITEM_UNABLE_TO_USE -- TODO: Verify/correct message end return 0 end itemObject.onItemUse = function(target, user) + -- TODO: -- Base duration 30 min, in seconds. local duration = 1800 + (target:getMod(xi.mod.CHOCOBO_RIDING_TIME) * 60) + + -- NOTE: Chocobo look is handled in core by virtue of if PChar->m_FieldChocobo being populated target:addStatusEffect(xi.effect.MOUNTED, { power = xi.mount.CHOCOBO, duration = duration, origin = user, subPower = 64, silent = true }) end diff --git a/scripts/quests/hiddenQuests/Chocobo_Whistle.lua b/scripts/quests/hiddenQuests/Chocobo_Whistle.lua new file mode 100644 index 00000000000..c6a0a5481f2 --- /dev/null +++ b/scripts/quests/hiddenQuests/Chocobo_Whistle.lua @@ -0,0 +1,77 @@ +----------------------------------- +-- Chocobo Whistle +----------------------------------- +-- Hantileon : !pos -2.675 -1.1 -105.287 230 +----------------------------------- + +local quest = HiddenQuest:new('ChocoboWhistle') + +quest.sections = +{ + { + check = function(player, questVars, vars) + return xi.settings.main.ENABLE_CHOCOBO_RAISING and + questVars.Prog == 1 + -- TODO: Also check chocobo is large enough to ride + end, + + [xi.zone.SOUTHERN_SAN_DORIA] = + { + ['Hantileon'] = + { + onTrigger = function(player, npc, trade) + -- TODO: use onEventUpdate to inject chocobo name? + return quest:progressEvent(829, 0, 0, 1, 0, 4, 1, 0, 0) + end, + }, + + onEventFinish = + { + [829] = function(player, csid, option, npc) + -- TODO: Handle option? + -- We'll check this inside the chocobo walk event logic + quest:setVar(player, 'Prog', 2) + end, + }, + }, + }, + + -- TODO: Chocobo Walk sets Prog from 2 to 3 + + { + check = function(player, questVars, vars) + return xi.settings.main.ENABLE_CHOCOBO_RAISING and + questVars.Prog == 3 + end, + + [xi.zone.SOUTHERN_SAN_DORIA] = + { + ['Hantileon'] = + { + onTrigger = function(player, npc, trade) + -- TODO: use onEventUpdate to inject chocobo name? + -- TODO: Guessed params: + -- Dirty Handkerchief + return quest:progressEvent(830, 0, 1) + + -- Regular handkerchief: + -- (given to chocobo by the stable workers) + -- return quest:progressEvent(830, 0, 2) + end, + }, + + onEventFinish = + { + [830] = function(player, csid, option, npc) + if npcUtil.giveItem(player, xi.item.CHOCOBO_WHISTLE) then + -- Rather than complete and wipe the var, we need to keep it around for + -- future reference and CSs (there's no key item or anything to track) + quest:setVar(player, 'Prog', 4) + end + end, + }, + }, + }, +} + +return quest From f80252b24312c20aba6f91568b6113a9de210e0f Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 20:44:57 +0100 Subject: [PATCH 02/10] Raising: Clean up INTRO_MENU_PT_3 --- .../hobbies/chocobo_raising/event_vm.lua | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 35370c3f053..e83315c6687 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -313,39 +313,53 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.INTRO_MENU_PT_3] = function() + -- NOTE: + -- To add options to these menus, we remove bits from the full mask. + -- Therefore, we've defined the options as subtractive values, so we + -- can add and remove at the same time... local menuFlags = 0xFFFFFFFF - -- Define menu options - -- bit.lshift(0x01, 0): Ask about your chocobo's condition - local askAboutChocoboCondition = -bit.lshift(0x01, 0) + -- + -- Regular menu options + -- - -- bit.lshift(0x01, 1): Care for your chocobo - local careForYourChocobo = -bit.lshift(0x01, 1) + local askAboutChocoboCondition = -bit.lshift(0x01, 0) + local careForYourChocobo = -bit.lshift(0x01, 1) + local setUpCareSchedule = -bit.lshift(0x01, 2) - -- Set up a care schedule - local setUpCareSchedule = -bit.lshift(0x01, 2) - local nameChocobo = 0 + menuFlags = menuFlags + + askAboutChocoboCondition + + careForYourChocobo + + setUpCareSchedule if chocoState.stage > xi.chocoboRaising.stage.EGG and chocoState.first_name == 'Chocobo' and chocoState.last_name == 'Chocobo' then - nameChocobo = -bit.lshift(0x01, 3) -- Name your chocobo + local nameYourChocobo = -bit.lshift(0x01, 3) + menuFlags = menuFlags + nameYourChocobo end - -- bit.lshift(0x01, 4): Request Documentation - if player:getCharVar('HQuest[ChocoboWhistle]Prog') >= 4 then + local requestDocumentation = -bit.lshift(0x01, 4) local registerToCallYourChocobo = -bit.lshift(0x01, 5) - menuFlags = menuFlags + registerToCallYourChocobo + local receiveYourChocoboWhistle = -bit.lshift(0x01, 6) + local purchaseAChocoboWhistle = -bit.lshift(0x01, 7) + + menuFlags = menuFlags + + requestDocumentation + + registerToCallYourChocobo + + receiveYourChocoboWhistle + + purchaseAChocoboWhistle end - -- bit.lshift(0x01, 6): Receive your chocobo whistle - -- bit.lshift(0x01, 7): Purchase a chocobo whistle - -- 8 - 25 are all '-----' (blank) + -- + -- Debug options + -- + -- Go forward 1 unit (debug) (Unused, see command: !chocoboraising) local goForward1UnitDebug = -bit.lshift(0x01, 26) utils.unused(goForward1UnitDebug) @@ -358,23 +372,19 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local userWorkPrintDebug = -bit.lshift(0x01, 28) utils.unused(userWorkPrintDebug) - local retireOrGiveUp = 0 - if chocoState.stage < xi.chocoboRaising.stage.ADULT_1 then - retireOrGiveUp = -bit.lshift(0x01, 30) -- Give up chocobo raising + if chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then + local retireYourChocobo = -bit.lshift(0x01, 29) -- Retire your chocobo + menuFlags = menuFlags + retireYourChocobo else - retireOrGiveUp = -bit.lshift(0x01, 29) -- Retire your chocobo + local giveUpChocoboRaising = -bit.lshift(0x01, 30) -- Give up chocobo raising + menuFlags = menuFlags + giveUpChocoboRaising end - -- bit.lshift(0x01, 31): Nothing. (exit) + -- Exit is always available local exit = -bit.lshift(0x01, 31) + menuFlags = menuFlags + exit - -- Enable menu options (remove bits from 0xFFFFFFFF) - menuFlags = menuFlags + - askAboutChocoboCondition + - careForYourChocobo + - setUpCareSchedule + - nameChocobo + - retireOrGiveUp + -- TODO: Do we need these anymore? if chocoState.stage >= xi.chocoboRaising.stage.CHICK then utils.unused() @@ -391,9 +401,6 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- menuFlags = menuFlags end - -- Exit is always available - menuFlags = menuFlags + exit - player:updateEvent(menuFlags, 0, 0, 0, 0, 0, 0, 0) end, From c3a0118ac072d769604dcc51ab0dbac00bbdab64 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 20:53:08 +0100 Subject: [PATCH 03/10] Raising: Clean up REGISTER_CHOCOBO_WHISTLE --- .../hobbies/chocobo_raising/event_vm.lua | 76 +++++-------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index e83315c6687..fa47437fa3d 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -465,29 +465,12 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO: These appearance changes are locked in on day 29 if -- they are 'Average' (128) or above. This will need to be -- written to the db and this part rewritten. - - -- Crest type - local enlargedCrest = 0 - - if chocoState.discernment >= 128 then - enlargedCrest = 1 - end - - -- Feet type - local enlargedFeet = 0 - - if chocoState.strength >= 128 then - enlargedFeet = 1 - end - - -- Tail feathers type - local moreTailFeathers = 0 - - if chocoState.endurance >= 128 then - moreTailFeathers = 1 - end + local enlargedCrest = chocoState.discernment >= 128 and 1 or 0 + local enlargedFeet = chocoState.strength >= 128 and 1 or 0 + local moreTailFeathers = chocoState.endurance >= 128 and 1 or 0 -- Event update parameters. + -- TODO: What's that 1 for? player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, 1, 0, 0) end, @@ -518,6 +501,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) bit.lshift(chocoState.ability2, 12) + bit.lshift(chocoState.stage, 16) + -- TODO: Refactor to use the -bit pattern -- Condition flags (can be combined) -- No flags: Stable -- local legWounded = bit.lshift(0x01, 0) @@ -542,6 +526,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = function() debug(string.format(' Energy: %i', chocoState.energy)) + -- TODO: Refactor to use the -bit pattern local watchOverChocobo = 0x01 local tellAStory = 0x02 local scoldTheChocobo = 0x04 @@ -824,6 +809,8 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local storyMask = 0xFFFFFF9C + -- TODO: This looks very similar to SCOLD_CHOCOBO and COMPETE_WITH_OTHERS, should we move those updates + -- : inside onRaisingEventPlayout? chocoState = xi.chocoboRaising.onRaisingEventPlayout(player, xi.chocoboRaising.cutscenes.INTERESTED_IN_YOUR_STORY, chocoState) player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, 0, 0, 0, 0, 0, 0, 0) @@ -978,15 +965,16 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.WHISTLE_GAME_RESULT] = function() - local keyItem = xi.keyItem.HANDKERCHIEF - -- TODO: Handle this: -- : A successful search does not guarantee you'll find the item. That means the item is not in that area, and you should look in the other two areas. + -- : We'll need to pre-assign which area the search will succeed in, and also remember + -- : if the chocobo finished the white handkerchief quest with the player. - -- TODO: If you have a high bond with your chocobo, this gets upgraded to be the - -- : DIRTY_HANDKERCHIEF, apparently. + -- TODO: If you finished the white handerchief quest, you'll get the + -- : DIRTY_HANDKERCHIEF instead of HANDKERCHIEF. + local keyItem = xi.keyItem.HANDKERCHIEF - -- TODO: What are the chances here? + -- TODO: What are the chances here, seems fair but not guaranteed from caps. if math.random(1, 100) < 25 then -- success player:updateEvent(keyItem, 0, 0, 0, 0, 1, 0, 0) player:addKeyItem(keyItem) @@ -997,10 +985,6 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.SKIP_REPORT] = function() - -- TODO: Set up movement between chocoState.report.events and chocoState.csList to - -- : include the length of each playout in days, so it can be used in handleCSUpdate() - -- : to multiply values etc. - -- Prepare chocoState.csList for _, currentEvent in pairs (chocoState.report.events) do local eventStartStart = currentEvent[1] local eventStartEnd = currentEvent[2] @@ -1036,34 +1020,14 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) debug('Registering field chocobo details') player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) - -- TODO: Shamelessly taken from !chocobo and other handlers - - -- Crest type - local enlargedCrest = 0 - - if chocoState.discernment >= 128 then - enlargedCrest = 1 - end - - -- Feet type - local enlargedFeet = 0 - - if chocoState.strength >= 128 then - enlargedFeet = 1 - end - - -- Tail feathers type - local moreTailFeathers = 0 - - if chocoState.endurance >= 128 then - moreTailFeathers = 1 - end - + -- TODO: These appearance changes are locked in on day 29 if + -- they are 'Average' (128) or above. This will need to be + -- written to the db and this part rewritten. local traits = { - largeBeak = enlargedCrest, - fullTail = moreTailFeathers, - largeTalons = enlargedFeet, + largeBeak = chocoState.discernment >= 128 and 1 or 0, + fullTail = chocoState.endurance >= 128 and 1 or 0, + largeTalons = chocoState.strength >= 128 and 1 or 0, } player:registerChocobo(chocoState.color, traits) From e129416c26ae89e4cd85fcb34177891fe08edb9d Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 21:20:34 +0100 Subject: [PATCH 04/10] Raising: Don't leak appearance data before it's time --- .../globals/hobbies/chocobo_raising/event_vm.lua | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index fa47437fa3d..44f2594aa87 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -458,6 +458,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO: Check caps if chocoState.stage == xi.chocoboRaising.stage.EGG then -- From caps: + -- TODO: What's all this then? player:updateEvent(0, 1023, 0, 0, 1, 1, 0, 0) return end @@ -469,9 +470,17 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local enlargedFeet = chocoState.strength >= 128 and 1 or 0 local moreTailFeathers = chocoState.endurance >= 128 and 1 or 0 - -- Event update parameters. - -- TODO: What's that 1 for? - player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, 1, 0, 0) + -- We don't want to leak color or physical trait information to the client before it's + -- meant to be seen, so we're going to put in default dummy data in the early lifecycle + -- stages. + -- TODO: What is this: '... 1, 0, 0)' for? Gender? + if chocoState.stage < xi.chocoboRaising.stage.ADOLESCENT then -- No information + player:updateEvent(xi.chocobo.color.YELLOW, 0, 0, 0, chocoState.stage, 1, 0, 0) + elseif chocoState.stage < xi.chocoboRaising.stage.ADULT_1 then -- Partial information + player:updateEvent(chocoState.color, 0, 0, 0, chocoState.stage, 1, 0, 0) + else -- Full information + player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, 1, 0, 0) + end end, -- TODO: This is hit directly after the CS for an egg hatching when we return to the main From 5d91a64974de55b53e7f85ff2205c66f76578274 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 22:01:25 +0100 Subject: [PATCH 05/10] Raising: Clean up stat packing and condition reporting --- .../hobbies/chocobo_raising/choco_data.lua | 2 - .../chocobo_raising/condense_events.lua | 2 +- .../hobbies/chocobo_raising/constants.lua | 24 ++- .../hobbies/chocobo_raising/event_playout.lua | 12 +- .../hobbies/chocobo_raising/event_vm.lua | 157 +++++++++++++----- 5 files changed, 143 insertions(+), 54 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 063bb18046b..2ff9548c779 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -28,8 +28,6 @@ xi.chocoboRaising.initChocoState = function(player) debug('chocoState.age = ' .. chocoState.age) debug('chocoState.last_update_age = ' .. chocoState.last_update_age) - chocoState.affectionRank = xi.chocoboRaising.affectionRank.LIKES - -- Add helpers and empty tables to navigate CSs chocoState.csList = {} chocoState.foodGiven = {} diff --git a/scripts/globals/hobbies/chocobo_raising/condense_events.lua b/scripts/globals/hobbies/chocobo_raising/condense_events.lua index e11d54e2248..a0c34271d4e 100644 --- a/scripts/globals/hobbies/chocobo_raising/condense_events.lua +++ b/scripts/globals/hobbies/chocobo_raising/condense_events.lua @@ -28,7 +28,7 @@ local compareTables = function(t1, t2) return false end - for idx, val1 in pairs(t1) do + for idx, val1 in ipairs(t1) do local val2 = t2[idx] if val1 ~= val2 then diff --git a/scripts/globals/hobbies/chocobo_raising/constants.lua b/scripts/globals/hobbies/chocobo_raising/constants.lua index 56c087092f7..6915a222447 100644 --- a/scripts/globals/hobbies/chocobo_raising/constants.lua +++ b/scripts/globals/hobbies/chocobo_raising/constants.lua @@ -173,9 +173,6 @@ xi.chocoboRaising.skillRankBoundaries = xi.chocoboRaising.numberToRank = function(skill) local rank = xi.chocoboRaising.skillRanks.F_POOR - -- Since pairs isn't guaranteed to iterate in order, we have - -- do check against ranks and see if things are greater than - -- our best-found rank for idx, boundary in ipairs(xi.chocoboRaising.skillRankBoundaries) do if skill >= boundary and xi.chocoboRaising.skillRanks[idx] > rank then rank = xi.chocoboRaising.skillRanks[idx] @@ -185,6 +182,18 @@ xi.chocoboRaising.numberToRank = function(skill) return rank end +xi.chocoboRaising.affectionToAffectionRank = function(affection) + local rank = xi.chocoboRaising.affectionRank.DOESNT_CARE + + for idx, boundary in ipairs(xi.chocoboRaising.skillRankBoundaries) do + if affection >= boundary and xi.chocoboRaising.skillRanks[idx] > rank then + rank = xi.chocoboRaising.affectionRank[idx] + end + end + + return rank +end + xi.chocoboRaising.getPlayerRidingSpeedAndTime = function(player) local baseSpeed = xi.chocoboRaising.ridingSpeedBase local baseTime = xi.chocoboRaising.ridingTimeBase @@ -524,13 +533,14 @@ xi.chocoboRaising.walkItems = } xi.chocoboRaising.packStats1 = function(chocoState) - return bit.lshift(chocoState.strength, 0) + - bit.lshift(chocoState.endurance, 8) + - bit.lshift(chocoState.discernment, 16) + - bit.lshift(chocoState.receptivity, 24) + return bit.lshift(xi.chocoboRaising.numberToRank(chocoState.strength), 0) + + bit.lshift(xi.chocoboRaising.numberToRank(chocoState.endurance), 8) + + bit.lshift(xi.chocoboRaising.numberToRank(chocoState.discernment), 16) + + bit.lshift(xi.chocoboRaising.numberToRank(chocoState.receptivity), 24) end xi.chocoboRaising.packStats2 = function(chocoState) + -- TODO: Do these need to be packed into ranks too? return bit.lshift(chocoState.affection, 0) + bit.lshift(chocoState.energy, 8) + bit.lshift(chocoState.satisfaction, 16) diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index 6e7e2f2f4a2..e00f34c32f1 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -240,17 +240,17 @@ xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, end, [xi.chocoboRaising.cutscenes.HANGS_HEAD_IN_SHAME] = function() - -- TODO: Take in a multiplier to account for merged time ranges - chocoState.affection = xi.chocoboRaising.handleStatChange(chocoState.affection, -10, 255) - chocoState.energy = xi.chocoboRaising.handleStatChange(chocoState.energy, -5, 100) + -- TODO: How much energy and affection? + chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION, chocoState.affection, -10, 255) + chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY, chocoState.energy, -5, 100) xi.chocoboRaising.setCondition(chocoState, xi.chocoboRaising.conditions.SPOILED, false) end, [xi.chocoboRaising.cutscenes.COMPETE_WITH_OTHERS] = function() - -- TODO: Take in a multiplier to account for merged time ranges + -- TODO: How much energy and affection? -- 'Increases affection slightly - confirmed.' - chocoState.affection = xi.chocoboRaising.handleStatChange(chocoState.affection, 1, 255) - chocoState.energy = xi.chocoboRaising.handleStatChange(chocoState.energy, -5, 100) + chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION, chocoState.affection, 1, 255) + chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY, chocoState.energy, -5, 100) xi.chocoboRaising.setCondition(chocoState, xi.chocoboRaising.conditions.BORED, false) end, } diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 44f2594aa87..d17a534b042 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -280,7 +280,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) chocoState.age = eventStartStart chocoState.stage = xi.chocoboRaising.ageToStage(chocoState.age) - for _, cs in pairs(eventCSList) do + for _, cs in ipairs(eventCSList) do table.insert(chocoState.csList, { cs, eventStartEnd - eventStartStart + 1 }) end @@ -412,14 +412,14 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local itemData = xi.chocoboRaising.validFoods[itemId] local hungerAmount = itemData[1] local energyAmount = itemData[3] - local glowColor = itemData[10] + local glowColor = itemData[10] player:messageSpecial(ID.text.CHOCOBO_FEEDING_ITEM, itemId, idx) -- TODO: Handle item effects if xi.chocoboRaising.hasCondition(chocoState) then - for _, condition in pairs(chocoState.conditions) do + for _, condition in ipairs(chocoState.conditions) do if xi.chocoboRaising.getCondition(chocoState, condition) then local foodCureTable = xi.chocoboRaising.conditionsHealedByItems[condition] @@ -499,35 +499,106 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM] = function() + -- TODO: When is this used? -- Block all other information - --local blockFlag = bit.lshift(0x01, 31) -- Sorry, but you will have to do this later. I have something new to report. + -- local blockFlag = bit.lshift(0x01, 31) -- Sorry, but you will have to do this later. I have something new to report. + local arg0 = vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM + local arg1 = xi.chocoboRaising.packStats1(chocoState) - local arg2 = bit.lshift(xi.chocoboRaising.affectionRank.PARENT, 0) + bit.lshift(chocoState.hunger, 16) + + local affection = xi.chocoboRaising.affectionToAffectionRank(chocoState.affection) + local arg2 = bit.lshift(affection, 0) + + bit.lshift(chocoState.hunger, 16) + + -- TODO: Does this leak the ability information early? Should we block this out? local arg3 = bit.lshift(chocoState.personality, 0) + bit.lshift(chocoState.weather_preference, 4) + bit.lshift(chocoState.ability1, 8) + bit.lshift(chocoState.ability2, 12) + bit.lshift(chocoState.stage, 16) - -- TODO: Refactor to use the -bit pattern + debug(string.format('strength: %i', chocoState.strength)) + debug(string.format('endurance: %i', chocoState.endurance)) + debug(string.format('discernment: %i', chocoState.discernment)) + debug(string.format('receptivity: %i', chocoState.receptivity)) + debug(string.format('affection: %i', chocoState.affection)) + + -- + -- NOTE: This does NOT use the negative masks of the menus! + -- + -- Condition flags (can be combined) + local legWounded = bit.lshift(0x01, 0) + local slightlyIll = bit.lshift(0x01, 1) + local stomachAche = bit.lshift(0x01, 2) + local depressed = bit.lshift(0x01, 3) + local excellentCondition = bit.lshift(0x01, 4) + local sleepingSoundly = bit.lshift(0x01, 5) + local veryIll = bit.lshift(0x01, 6) + local boredRestless = bit.lshift(0x01, 7) + local hopelesslySpoiled = bit.lshift(0x01, 8) + local ranAway = bit.lshift(0x01, 9) + local inLove = bit.lshift(0x01, 10) + local makingAFuss = bit.lshift(0x01, 11) + local fullOfEnergy = bit.lshift(0x01, 12) + local brightAndFocussed = bit.lshift(0x01, 13) + -- No flags: Stable - -- local legWounded = bit.lshift(0x01, 0) - -- local slightlyIll = bit.lshift(0x01, 1) - -- local stomachAche = bit.lshift(0x01, 2) - -- local depressed = bit.lshift(0x01, 3) - -- local excellentCondition = bit.lshift(0x01, 4) - -- local sleepingSoundly = bit.lshift(0x01, 5) - -- local veryIll = bit.lshift(0x01, 6) - -- local boredRestless = bit.lshift(0x01, 7) - -- local hopelesslySpoiled = bit.lshift(0x01, 8) - -- local ranAway = bit.lshift(0x01, 9) - -- local inLove = bit.lshift(0x01, 10) - -- local makingAFuss = bit.lshift(0x01, 11) - -- local fullOfEnergy = bit.lshift(0x01, 12) - -- local brightAndFocussed = bit.lshift(0x01, 13) - local arg4 = 0 -- fullOfEnergy + brightAndFocussed + local arg4 = 0x00000000 + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.INJURED) then + arg4 = arg4 + legWounded + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.SICK) then + arg4 = arg4 + slightlyIll + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.ILL) then + arg4 = arg4 + stomachAche + end + + -- TODO: depressed + utils.unused(depressed) + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.HIGH_SPIRITS) then + arg4 = arg4 + excellentCondition + end + + -- TODO: sleepingSoundly + utils.unused(sleepingSoundly) + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.VERY_ILL) then + arg4 = arg4 + veryIll + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.BORED) then + arg4 = arg4 + boredRestless + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.SPOILED) then + arg4 = arg4 + hopelesslySpoiled + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.RUN_AWAY) then + arg4 = arg4 + ranAway + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.LOVESICK) then + arg4 = arg4 + inLove + end + + -- TODO: makingAFuss + utils.unused(makingAFuss) + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_1) or xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_2) then + arg4 = arg4 + fullOfEnergy + end + + if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.BRIGHT_AND_FOCUSED) then + arg4 = arg4 + brightAndFocussed + end player:updateEvent(arg0, arg1, arg2, arg3, arg4, 0, 0, 0) end, @@ -535,31 +606,41 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = function() debug(string.format(' Energy: %i', chocoState.energy)) - -- TODO: Refactor to use the -bit pattern - local watchOverChocobo = 0x01 - local tellAStory = 0x02 - local scoldTheChocobo = 0x04 - local competeWithOthers = 0x08 - local goOnAWalkShort = 0x10 - local goOnAWalkRegular = 0x20 - local goOnAWalkLong = 0x40 - local mask = 0x7FFFFFFF - watchOverChocobo + -- Condition flags (can be combined) + local watchOverChocobo = -bit.lshift(0x01, 0) + local tellAStory = -bit.lshift(0x01, 1) + local scoldTheChocobo = -bit.lshift(0x01, 2) + local competeWithOthers = -bit.lshift(0x01, 3) + local goOnAWalkShort = -bit.lshift(0x01, 4) + local goOnAWalkRegular = -bit.lshift(0x01, 5) + local goOnAWalkLong = -bit.lshift(0x01, 6) + + local mask = 0x7FFFFFFF + watchOverChocobo if chocoState.stage >= xi.chocoboRaising.stage.CHICK then - mask = mask - scoldTheChocobo - goOnAWalkShort + mask = mask + + scoldTheChocobo + + goOnAWalkShort end if chocoState.stage >= xi.chocoboRaising.stage.ADOLESCENT then - mask = mask - tellAStory - goOnAWalkRegular -- TODO: Is this unlocked per-chocobo, or per-player? + local knowsAStory = true + if knowsAStory then + mask = mask + tellAStory + end + + mask = mask + goOnAWalkRegular + -- TODO: competeWithOthers: Available at adolescent stage; You must go on a regular walk to unlock this. - if true then - mask = mask - competeWithOthers + local hasGoneOnRegularWalk = true + if hasGoneOnRegularWalk then + mask = mask + competeWithOthers end end if chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then - mask = mask - goOnAWalkLong + mask = mask + goOnAWalkLong end player:updateEvent(mask, chocoState.energy, 0, 0, 0, 0, 0, 0) @@ -816,7 +897,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO: Chance to learn skill end - local storyMask = 0xFFFFFF9C + local storyMask = 0xFFFFFFFE -- 0xFFFFFF9C -- TODO: This looks very similar to SCOLD_CHOCOBO and COMPETE_WITH_OTHERS, should we move those updates -- : inside onRaisingEventPlayout? @@ -994,7 +1075,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.SKIP_REPORT] = function() - for _, currentEvent in pairs (chocoState.report.events) do + for _, currentEvent in ipairs(chocoState.report.events) do local eventStartStart = currentEvent[1] local eventStartEnd = currentEvent[2] local eventCSList = currentEvent[3] @@ -1002,7 +1083,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) chocoState.age = eventStartStart chocoState.stage = xi.chocoboRaising.ageToStage(chocoState.age) - for _, cs in pairs(eventCSList) do + for _, cs in ipairs(eventCSList) do table.insert(chocoState.csList, { cs, eventStartEnd - eventStartStart + 1 }) end end From 7eac0756e70ce10cfb734edb59e86a58a284dbb8 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 22:11:46 +0100 Subject: [PATCH 06/10] Raising: Hide more information from PRESENT_CHOCOBO_APPEARANCE --- .../hobbies/chocobo_raising/event_vm.lua | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index d17a534b042..ffc3ba283a7 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -452,34 +452,27 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.PRESENT_CHOCOBO_APPEARANCE] = function() - -- TODO: There is more information going on in here - - -- TODO: While the chocobo is an egg, things seem to be laid out differently - -- TODO: Check caps - if chocoState.stage == xi.chocoboRaising.stage.EGG then - -- From caps: - -- TODO: What's all this then? - player:updateEvent(0, 1023, 0, 0, 1, 1, 0, 0) - return - end - - -- TODO: These appearance changes are locked in on day 29 if - -- they are 'Average' (128) or above. This will need to be - -- written to the db and this part rewritten. - local enlargedCrest = chocoState.discernment >= 128 and 1 or 0 - local enlargedFeet = chocoState.strength >= 128 and 1 or 0 - local moreTailFeathers = chocoState.endurance >= 128 and 1 or 0 - -- We don't want to leak color or physical trait information to the client before it's -- meant to be seen, so we're going to put in default dummy data in the early lifecycle -- stages. - -- TODO: What is this: '... 1, 0, 0)' for? Gender? - if chocoState.stage < xi.chocoboRaising.stage.ADOLESCENT then -- No information - player:updateEvent(xi.chocobo.color.YELLOW, 0, 0, 0, chocoState.stage, 1, 0, 0) - elseif chocoState.stage < xi.chocoboRaising.stage.ADULT_1 then -- Partial information - player:updateEvent(chocoState.color, 0, 0, 0, chocoState.stage, 1, 0, 0) + if chocoState.stage == xi.chocoboRaising.stage.EGG then + -- No information + player:updateEvent(xi.chocobo.color.YELLOW, 0, 0, 0, chocoState.stage, 0, 0, 0) + elseif chocoState.stage < xi.chocoboRaising.stage.ADOLESCENT then + -- A little information + player:updateEvent(xi.chocobo.color.YELLOW, 0, 0, 0, chocoState.stage, chocoState.sex, 0, 0) + elseif chocoState.stage < xi.chocoboRaising.stage.ADULT_1 then + -- Partial information + player:updateEvent(chocoState.color, 0, 0, 0, chocoState.stage, chocoState.sex, 0, 0) else -- Full information - player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, 1, 0, 0) + -- TODO: These appearance changes are locked in on day 29 if + -- they are 'Average' (128) or above. This will need to be + -- written to the db and this part rewritten. + local enlargedCrest = chocoState.discernment >= 128 and 1 or 0 + local enlargedFeet = chocoState.strength >= 128 and 1 or 0 + local moreTailFeathers = chocoState.endurance >= 128 and 1 or 0 + + player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, chocoState.sex, 0, 0) end end, @@ -528,7 +521,6 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- NOTE: This does NOT use the negative masks of the menus! -- - -- Condition flags (can be combined) local legWounded = bit.lshift(0x01, 0) local slightlyIll = bit.lshift(0x01, 1) local stomachAche = bit.lshift(0x01, 2) @@ -606,7 +598,6 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = function() debug(string.format(' Energy: %i', chocoState.energy)) - -- Condition flags (can be combined) local watchOverChocobo = -bit.lshift(0x01, 0) local tellAStory = -bit.lshift(0x01, 1) local scoldTheChocobo = -bit.lshift(0x01, 2) From bc1017fd0a3f6214aad376ac6afe37978a010d48 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 22:24:41 +0100 Subject: [PATCH 07/10] Raising: Fix GM stat printing --- .../hobbies/chocobo_raising/event_vm.lua | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index ffc3ba283a7..6a24151488c 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -360,17 +360,26 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- Debug options -- - -- Go forward 1 unit (debug) (Unused, see command: !chocoboraising) - local goForward1UnitDebug = -bit.lshift(0x01, 26) - utils.unused(goForward1UnitDebug) + local gmModeToggled = player:getVisibleGMLevel() >= 3 + if gmModeToggled then + -- Go forward 1 unit (debug) (Unused, see command: !chocoboraising) + local goForward1UnitDebug = -bit.lshift(0x01, 26) - -- Abilities print (debug) (Unused, see command: !chocoboraising) - local abilitiesPrintDebug = -bit.lshift(0x01, 27) - utils.unused(abilitiesPrintDebug) + -- Abilities print (debug) (Unused, see command: !chocoboraising) + local abilitiesPrintDebug = -bit.lshift(0x01, 27) - -- User work print (debug) (Unused, see command: !chocoboraising) - local userWorkPrintDebug = -bit.lshift(0x01, 28) - utils.unused(userWorkPrintDebug) + -- User work print (debug) (Unused, see command: !chocoboraising) + local userWorkPrintDebug = -bit.lshift(0x01, 28) + + menuFlags = menuFlags + + goForward1UnitDebug + + abilitiesPrintDebug + + userWorkPrintDebug + end + + -- + -- Danger zone + -- if chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then local retireYourChocobo = -bit.lshift(0x01, 29) -- Retire your chocobo @@ -384,23 +393,6 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local exit = -bit.lshift(0x01, 31) menuFlags = menuFlags + exit - -- TODO: Do we need these anymore? - - if chocoState.stage >= xi.chocoboRaising.stage.CHICK then - utils.unused() - --menuFlags = menuFlags - end - - if chocoState.stage >= xi.chocoboRaising.stage.ADOLESCENT then - utils.unused() - -- menuFlags = menuFlags - end - - if chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then - utils.unused() - -- menuFlags = menuFlags - end - player:updateEvent(menuFlags, 0, 0, 0, 0, 0, 0, 0) end, @@ -471,7 +463,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local enlargedCrest = chocoState.discernment >= 128 and 1 or 0 local enlargedFeet = chocoState.strength >= 128 and 1 or 0 local moreTailFeathers = chocoState.endurance >= 128 and 1 or 0 - + player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, chocoState.sex, 0, 0) end end, @@ -1115,16 +1107,23 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.DEBUG_GO_FORWARD_1_UNIT] = function() - -- TODO: Split stored age and time of creation so age can be manipulated + -- TODO: Split stored age and time of creation so age can be manipulated from here player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, [vmOpCodes.DEBUG_ABILITIES_PRINT] = function() - player:updateEvent(1, xi.chocoboRaising.packStats1(chocoState), xi.chocoboRaising.packStats2(chocoState), 0, 0, 0, 0, 0) + local packedRawStats = + bit.lshift(chocoState.strength, 0) + + bit.lshift(chocoState.endurance, 8) + + bit.lshift(chocoState.discernment, 16) + + bit.lshift(chocoState.receptivity, 24) + + player:updateEvent(1, packedRawStats, xi.chocoboRaising.packStats2(chocoState), 0, 0, 0, 0, 0) end, [vmOpCodes.DEBUG_USER_WORK_PRINT] = function() -- TODO: Should we be tracking all user interactions with the chocobo? + player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, [vmOpCodes.GIVE_UP_CHOCOBO] = function() From 8c40c8ba3581e168fa4190803dd9649ea56ea387 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 22:51:15 +0100 Subject: [PATCH 08/10] Raising: Clean up White Handkerchief quest latching --- .../hobbies/chocobo_raising/choco_data.lua | 26 ++- .../hobbies/chocobo_raising/event_playout.lua | 5 +- .../hobbies/chocobo_raising/event_vm.lua | 166 +++++++++--------- 3 files changed, 106 insertions(+), 91 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 2ff9548c779..4e29603db0c 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -84,6 +84,12 @@ xi.chocoboRaising.initChocoState = function(player) table.insert(possibleCarePlanFuture, plan4Type) end + -- NOTE: To aid with tracking and playback, we have to keep a variable to track this quest, + -- : rather than the actual KI checks. + local whiteHandkerchiefStarted = false + local whiteHandkerchiefCancelled = false + local whiteHandkerchiefFinished = false + for idx = 1, reportLength do local possibleCarePlanEvent = possibleCarePlanFuture[idx] @@ -135,14 +141,12 @@ xi.chocoboRaising.initChocoState = function(player) -- TODO: Come up with a more modular way to track these -- Start White Handkerchief quest - -- NOTE: To aid with tracking and playback, we have to keep a variable to track this quest, - -- : rather than the actual KI checks. -- TODO: Extract out the ages and timings for this into settings? - local whiteHandkerchiefStarted = false - local whiteHandkerchiefCancelled = false if + not whiteHandkerchiefStarted and not player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) and age == 7 + -- TODO: And you've not already completed this quest once before then debug('Starting White Handkerchief quest') table.insert(events, { age, { xi.chocoboRaising.cutscenes.CRYING_AT_NIGHT } }) @@ -154,13 +158,15 @@ xi.chocoboRaising.initChocoState = function(player) -- : this quest until the next chocobo. if whiteHandkerchiefStarted and + not whiteHandkerchiefCancelled and age == 15 and reportLength >= 7 then debug('Cancelling White Handkerchief quest') - whiteHandkerchiefStarted = false - whiteHandkerchiefCancelled = true table.insert(events, { age, { xi.chocoboRaising.cutscenes.HAVENT_SEEN_YOU } }) + whiteHandkerchiefCancelled = true + -- TODO: Mark this on the chocobo permanently, we'll need to know this event happened + -- : when we do the chocobo whistle quest end -- End White Handkerchief quest @@ -170,13 +176,15 @@ xi.chocoboRaising.initChocoState = function(player) -- TODO: Need to zone too? -- TODO: Does this play out as part of the report, or before it? if + not whiteHandkerchiefStarted and + not whiteHandkerchiefCancelled and + not whiteHandkerchiefFinished and age >= 8 and -- At least one day has to have passed, so this is the earliest possible age - player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) and - not whiteHandkerchiefCancelled + player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) then debug('Ending White Handkerchief quest') - whiteHandkerchiefStarted = false table.insert(events, { age, { xi.chocoboRaising.cutscenes.THAT_SHOULD_BE_ENOUGH } }) + whiteHandkerchiefFinished = true end end diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index e00f34c32f1..e7c68b170f6 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -212,13 +212,16 @@ xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, -- Growth CSs [xi.chocoboRaising.cutscenes.ADULT_2_TO_ADULT_3] = function() -- You waited too long to name your chocobo, trainer is going to do it for you! + -- TODO: Is there a CS associated with this? if chocoState.first_name == 'Chocobo' and chocoState.last_name == 'Chocobo' then -- Pick a name at random: First name only chocoState.first_name = xi.chocoboNames.getRandomName() - chocoState.last_name = '' + chocoState.last_name = '' + + debug(string.format('Forcing rename of chocobo to: %s', chocoState.first_name)) end end, diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 6a24151488c..d540bd66171 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -10,86 +10,86 @@ local debug = utils.getDebugPlayerPrinter(xi.settings.main.DEBUG_CHOCOBO_RAISING local vmOpCodes = { - RETIRE_YOUR_CHOCOBO = 40, - ASK_ABOUT_CONDITION_MENU = 46, - CHECK_REPORT_STATUS = 208, - INTRO_MENU_PT_2 = 214, - INTRO_MENU_PT_3 = 215, - UNKNOWN_216 = 216, - BUY_CHOCOBO_WHISTLE = 221, - RECEIVE_CHOCOBO_WHISTLE = 222, - REGISTER_CHOCOBO_WHISTLE = 223, - DEBUG_ABILITIES_PRINT = 229, - DEBUG_USER_WORK_PRINT = 232, - GIVE_UP_CHOCOBO = 240, - FEED_CHOCOBO = 241, - CARE_FOR_CHOCOBO_MENU = 243, - PRESENT_CHOCOBO_APPEARANCE = 244, - EVENT_PLAYOUT = 246, - INTRO_MENU_PT_1 = 248, - SET_CARE_SCHEDULE_MENU = 250, - ASK_ABOUT_CONDITION_CONFIRM = 251, - UNKNOWN_252 = 252, - SET_BASIC_CARE_PLAN_1 = 254, - BRIEF_REPORT = 256, - WHISTLE_GAME_RESULT = 344, - DEBUG_GO_FORWARD_1_UNIT = 482, - SKIP_REPORT = 504, - SET_BASIC_CARE_PLAN_2 = 510, - UNKNOWN_600 = 600, - SET_BASIC_CARE_PLAN_3 = 766, - SET_BASIC_CARE_PLAN_4 = 1022, - UNKNOWN_1056 = 1056, - UNKNOWN_1241 = 1241, - GO_ON_A_WALK_SHORT = 10994, - GO_ON_A_WALK_REGULAR = 11250, - GO_ON_A_WALK_LONG = 11506, - WATCH_OVER_CHOCOBO_CONFIRM = 12530, - TELL_A_STORY = 13042, - SCOLD_CHOCOBO = 13298, - COMPETE_WITH_OTHERS = 13554, + RETIRE_YOUR_CHOCOBO = 40, + PREPARE_CHOCOBO_MENU = 46, + CHECK_REPORT_STATUS = 208, + INTRO_MENU_PT_2 = 214, + INTRO_MENU_PT_3 = 215, + UNKNOWN_216 = 216, + BUY_CHOCOBO_WHISTLE = 221, + RECEIVE_CHOCOBO_WHISTLE = 222, + REGISTER_CHOCOBO_WHISTLE = 223, + DEBUG_ABILITIES_PRINT = 229, + DEBUG_USER_WORK_PRINT = 232, + GIVE_UP_CHOCOBO = 240, + FEED_CHOCOBO = 241, + CARE_FOR_CHOCOBO_MENU = 243, + PRESENT_CHOCOBO_APPEARANCE = 244, + EVENT_PLAYOUT = 246, + INTRO_MENU_PT_1 = 248, + SET_CARE_SCHEDULE_MENU = 250, + ASK_ABOUT_CONDITION_MENU = 251, + UNKNOWN_252 = 252, + SET_BASIC_CARE_PLAN_1 = 254, + BRIEF_REPORT = 256, + WHISTLE_GAME_RESULT = 344, + DEBUG_GO_FORWARD_1_UNIT = 482, + SKIP_REPORT = 504, + SET_BASIC_CARE_PLAN_2 = 510, + UNKNOWN_600 = 600, + SET_BASIC_CARE_PLAN_3 = 766, + SET_BASIC_CARE_PLAN_4 = 1022, + UNKNOWN_1056 = 1056, + UNKNOWN_1241 = 1241, + GO_ON_A_WALK_SHORT = 10994, + GO_ON_A_WALK_REGULAR = 11250, + GO_ON_A_WALK_LONG = 11506, + WATCH_OVER_CHOCOBO_CONFIRM = 12530, + TELL_A_STORY = 13042, + SCOLD_CHOCOBO = 13298, + COMPETE_WITH_OTHERS = 13554, } local vmOpCodeNames = { - [vmOpCodes.RETIRE_YOUR_CHOCOBO] = 'Retire your chocobo', - [vmOpCodes.ASK_ABOUT_CONDITION_MENU] = 'Ask about chocobos condition (menu)', - [vmOpCodes.CHECK_REPORT_STATUS] = 'Check report status', - [vmOpCodes.INTRO_MENU_PT_2] = 'Intro menu pt 2', - [vmOpCodes.INTRO_MENU_PT_3] = 'Intro menu pt 3', - [vmOpCodes.UNKNOWN_216] = 'Unknown 216 (forced renaming?)', - [vmOpCodes.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', - [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive chocobo whistle', - [vmOpCodes.REGISTER_CHOCOBO_WHISTLE] = 'Register chocobo whistle', - [vmOpCodes.DEBUG_ABILITIES_PRINT] = 'Debug abilities print', - [vmOpCodes.DEBUG_USER_WORK_PRINT] = 'Debug user work print', - [vmOpCodes.GIVE_UP_CHOCOBO] = 'Give up your chocobo', - [vmOpCodes.FEED_CHOCOBO] = 'Feed chocobo', - [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = 'Care for your chocobo (menu)', - [vmOpCodes.PRESENT_CHOCOBO_APPEARANCE] = 'Present chocobo appearance', - [vmOpCodes.EVENT_PLAYOUT] = 'Event playout', - [vmOpCodes.INTRO_MENU_PT_1] = 'Intro menu pt 1', - [vmOpCodes.SET_CARE_SCHEDULE_MENU] = 'Set care schedule (menu)', - [vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM] = 'Ask about chocobos condition (confirm)', - [vmOpCodes.UNKNOWN_252] = 'Unknown 252', - [vmOpCodes.SET_BASIC_CARE_PLAN_1] = 'Set basic care plan 1', - [vmOpCodes.BRIEF_REPORT] = 'Brief report', - [vmOpCodes.WHISTLE_GAME_RESULT] = 'Chocobo Whistle game result', - [vmOpCodes.DEBUG_GO_FORWARD_1_UNIT] = 'Debug go forward 1 unit', - [vmOpCodes.SKIP_REPORT] = 'Skip the report', - [vmOpCodes.SET_BASIC_CARE_PLAN_2] = 'Set basic care plan 2', - [vmOpCodes.UNKNOWN_600] = 'Unknown 600', - [vmOpCodes.SET_BASIC_CARE_PLAN_3] = 'Set basic care plan 3', - [vmOpCodes.SET_BASIC_CARE_PLAN_4] = 'Set basic care plan 4', - [vmOpCodes.UNKNOWN_1056] = 'Unknown 1056', - [vmOpCodes.UNKNOWN_1241] = 'Unknown 1241', - [vmOpCodes.GO_ON_A_WALK_SHORT] = 'Go on a walk (Short) - Leisurely / Brisk', - [vmOpCodes.GO_ON_A_WALK_REGULAR] = 'Go on a walk (Regular) - Leisurely / Brisk', - [vmOpCodes.GO_ON_A_WALK_LONG] = 'Go on a walk (Long) - Leisurely / Brisk', - [vmOpCodes.WATCH_OVER_CHOCOBO_CONFIRM] = 'Watch over your your chocobo (confirm)', - [vmOpCodes.TELL_A_STORY] = 'Tell a story', - [vmOpCodes.SCOLD_CHOCOBO] = 'Scold the chocobo', - [vmOpCodes.COMPETE_WITH_OTHERS] = 'Compete with others', + [vmOpCodes.RETIRE_YOUR_CHOCOBO] = 'Retire your chocobo', + [vmOpCodes.PREPARE_CHOCOBO_MENU] = 'Prepare chocobo menu', + [vmOpCodes.CHECK_REPORT_STATUS] = 'Check report status', + [vmOpCodes.INTRO_MENU_PT_2] = 'Intro menu pt 2', + [vmOpCodes.INTRO_MENU_PT_3] = 'Intro menu pt 3', + [vmOpCodes.UNKNOWN_216] = 'Unknown 216 (forced renaming?)', + [vmOpCodes.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', + [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive chocobo whistle', + [vmOpCodes.REGISTER_CHOCOBO_WHISTLE] = 'Register chocobo whistle', + [vmOpCodes.DEBUG_ABILITIES_PRINT] = 'Debug abilities print', + [vmOpCodes.DEBUG_USER_WORK_PRINT] = 'Debug user work print', + [vmOpCodes.GIVE_UP_CHOCOBO] = 'Give up your chocobo', + [vmOpCodes.FEED_CHOCOBO] = 'Feed chocobo', + [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = 'Care for your chocobo (menu)', + [vmOpCodes.PRESENT_CHOCOBO_APPEARANCE] = 'Present chocobo appearance', + [vmOpCodes.EVENT_PLAYOUT] = 'Event playout', + [vmOpCodes.INTRO_MENU_PT_1] = 'Intro menu pt 1', + [vmOpCodes.SET_CARE_SCHEDULE_MENU] = 'Set care schedule (menu)', + [vmOpCodes.ASK_ABOUT_CONDITION_MENU] = 'Ask about chocobos condition (menu)', + [vmOpCodes.UNKNOWN_252] = 'Unknown 252', + [vmOpCodes.SET_BASIC_CARE_PLAN_1] = 'Set basic care plan 1', + [vmOpCodes.BRIEF_REPORT] = 'Brief report', + [vmOpCodes.WHISTLE_GAME_RESULT] = 'Chocobo Whistle game result', + [vmOpCodes.DEBUG_GO_FORWARD_1_UNIT] = 'Debug go forward 1 unit', + [vmOpCodes.SKIP_REPORT] = 'Skip the report', + [vmOpCodes.SET_BASIC_CARE_PLAN_2] = 'Set basic care plan 2', + [vmOpCodes.UNKNOWN_600] = 'Unknown 600', + [vmOpCodes.SET_BASIC_CARE_PLAN_3] = 'Set basic care plan 3', + [vmOpCodes.SET_BASIC_CARE_PLAN_4] = 'Set basic care plan 4', + [vmOpCodes.UNKNOWN_1056] = 'Unknown 1056', + [vmOpCodes.UNKNOWN_1241] = 'Unknown 1241', + [vmOpCodes.GO_ON_A_WALK_SHORT] = 'Go on a walk (Short) - Leisurely / Brisk', + [vmOpCodes.GO_ON_A_WALK_REGULAR] = 'Go on a walk (Regular) - Leisurely / Brisk', + [vmOpCodes.GO_ON_A_WALK_LONG] = 'Go on a walk (Long) - Leisurely / Brisk', + [vmOpCodes.WATCH_OVER_CHOCOBO_CONFIRM] = 'Watch over your your chocobo (confirm)', + [vmOpCodes.TELL_A_STORY] = 'Tell a story', + [vmOpCodes.SCOLD_CHOCOBO] = 'Scold the chocobo', + [vmOpCodes.COMPETE_WITH_OTHERS] = 'Compete with others', } xi.chocoboRaising.eventVM = function(player, csid, option, npc) @@ -469,11 +469,12 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, -- TODO: This is hit directly after the CS for an egg hatching when we return to the main - -- : menu, so what does this mean? - [vmOpCodes.ASK_ABOUT_CONDITION_MENU] = function() + -- : menu, so what does this mean? What does it do? + [vmOpCodes.PREPARE_CHOCOBO_MENU] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, + -- TODO: Is this even getting hit? [vmOpCodes.UNKNOWN_600] = function() -- Get KI during another CS (determined randomly) local ki = xi.ki.DIRTY_HANDKERCHIEF @@ -483,12 +484,12 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:addKeyItem(ki) end, - [vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM] = function() + [vmOpCodes.ASK_ABOUT_CONDITION_MENU] = function() -- TODO: When is this used? -- Block all other information -- local blockFlag = bit.lshift(0x01, 31) -- Sorry, but you will have to do this later. I have something new to report. - local arg0 = vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM + local arg0 = vmOpCodes.ASK_ABOUT_CONDITION_MENU local arg1 = xi.chocoboRaising.packStats1(chocoState) @@ -576,7 +577,10 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- TODO: makingAFuss utils.unused(makingAFuss) - if xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_1) or xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_2) then + if + xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_1) or + xi.chocoboRaising.getCondition(chocoState, xi.chocoboRaising.conditions.FULL_OF_ENERGY_2) + then arg4 = arg4 + fullOfEnergy end From 21de60320383b9fcec99b986f76ee80c32b48cf1 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 22:56:29 +0100 Subject: [PATCH 09/10] Raising: Even more White Handkerchief quest latching --- scripts/globals/hobbies/chocobo_raising/choco_data.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 4e29603db0c..02102003f47 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -90,6 +90,8 @@ xi.chocoboRaising.initChocoState = function(player) local whiteHandkerchiefCancelled = false local whiteHandkerchiefFinished = false + local chocoboWhistleQuestBegan = player:getCharVar('HQuest[ChocoboWhistle]Prog') > 0 + for idx = 1, reportLength do local possibleCarePlanEvent = possibleCarePlanFuture[idx] @@ -145,8 +147,8 @@ xi.chocoboRaising.initChocoState = function(player) if not whiteHandkerchiefStarted and not player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) and - age == 7 - -- TODO: And you've not already completed this quest once before + age == 7 and + not chocoboWhistleQuestBegan then debug('Starting White Handkerchief quest') table.insert(events, { age, { xi.chocoboRaising.cutscenes.CRYING_AT_NIGHT } }) From 6f6d2d08500fe9e120be09e3051c8c09a5d580e3 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 23:16:15 +0100 Subject: [PATCH 10/10] Raising: Confirm force-naming at ADULT_3 --- scripts/commands/chocoboraising.lua | 65 +++++++++---------- .../hobbies/chocobo_raising/event_playout.lua | 1 - .../hobbies/chocobo_raising/event_vm.lua | 12 +++- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/scripts/commands/chocoboraising.lua b/scripts/commands/chocoboraising.lua index 2b4e847a1f3..1f6519bc8d7 100644 --- a/scripts/commands/chocoboraising.lua +++ b/scripts/commands/chocoboraising.lua @@ -71,7 +71,7 @@ commandObj.onTrigger = function(player) }) table.insert(menu.options, { - 'Debug #1: d0-d4', + 'D#1: d0-d4', function(playerArg) playerArg:deleteRaisedChocobo() @@ -86,12 +86,13 @@ commandObj.onTrigger = function(player) playerArg:printToPlayer('Setting up debug scenario 1 (4d update)', xi.msg.channel.SYSTEM_3, '') end, }) + table.insert(menu.options, { - 'Debug #2: d0-d10', + 'D#2: d0-d10', function(playerArg) playerArg:deleteRaisedChocobo() - local egg = {} + local egg = {} local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) player:setChocoboRaisingInfo(newChoco) @@ -108,67 +109,63 @@ commandObj.onTrigger = function(player) }) table.insert(menu.options, { - 'Delete chocoState', + 'D#3: d0-d65', function(playerArg) playerArg:deleteRaisedChocobo() - playerArg:printToPlayer('Deleted chocoState', xi.msg.channel.SYSTEM_3, '') - end, - }) - else - menu.title = 'Chocobo Raising (No chocoState)' - table.insert(menu.options, { - 'Create default chocoState', - function(playerArg) - local egg = {} + local egg = {} local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) player:setChocoboRaisingInfo(newChoco) - playerArg:printToPlayer('Created default chocoState', xi.msg.channel.SYSTEM_3, '') - end, - }) - table.insert(menu.options, { - 'Give Egg', - function(playerArg) - npcUtil.giveItem(playerArg, xi.item.CHOCOBO_EGG_SLIGHTLY_WARM) + local info = playerArg:getChocoboRaisingInfo() + info['created'] = info['created'] - (epochDay * 65) + playerArg:setChocoboRaisingInfo(info) + + playerArg:printToPlayer('Setting up debug scenario 3 (65d update)', xi.msg.channel.SYSTEM_3, '') end, }) table.insert(menu.options, { - 'Debug #1: d0-d4', + 'D#4: d0-d130', function(playerArg) playerArg:deleteRaisedChocobo() - local egg = {} + local egg = {} local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) player:setChocoboRaisingInfo(newChoco) local info = playerArg:getChocoboRaisingInfo() - info['created'] = info['created'] - (epochDay * 4) + info['created'] = info['created'] - (epochDay * 130) playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Setting up debug scenario 1 (4d update)', xi.msg.channel.SYSTEM_3, '') + playerArg:printToPlayer('Setting up debug scenario 4 (130d update)', xi.msg.channel.SYSTEM_3, '') end, }) table.insert(menu.options, { - 'Debug #2: d0-d10', + 'Delete chocoState', function(playerArg) playerArg:deleteRaisedChocobo() + playerArg:printToPlayer('Deleted chocoState', xi.msg.channel.SYSTEM_3, '') + end, + }) + else + menu.title = 'Chocobo Raising (No chocoState)' + table.insert(menu.options, { + 'Create default chocoState', + function(playerArg) local egg = {} local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) player:setChocoboRaisingInfo(newChoco) + playerArg:printToPlayer('Created default chocoState', xi.msg.channel.SYSTEM_3, '') + end, + }) - local info = playerArg:getChocoboRaisingInfo() - info['created'] = info['created'] - (epochDay * 10) - playerArg:setChocoboRaisingInfo(info) - - if playerArg:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) then - playerArg:delKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) - end - - playerArg:printToPlayer('Setting up debug scenario 2 (10d update w/ handkerchief)', xi.msg.channel.SYSTEM_3, '') + table.insert(menu.options, { + 'Give Egg', + function(playerArg) + npcUtil.giveItem(playerArg, xi.item.CHOCOBO_EGG_SLIGHTLY_WARM) end, }) end diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index e7c68b170f6..c81ac344ae6 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -212,7 +212,6 @@ xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, -- Growth CSs [xi.chocoboRaising.cutscenes.ADULT_2_TO_ADULT_3] = function() -- You waited too long to name your chocobo, trainer is going to do it for you! - -- TODO: Is there a CS associated with this? if chocoState.first_name == 'Chocobo' and chocoState.last_name == 'Chocobo' diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index d540bd66171..5b96ba111a8 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -15,7 +15,7 @@ local vmOpCodes = CHECK_REPORT_STATUS = 208, INTRO_MENU_PT_2 = 214, INTRO_MENU_PT_3 = 215, - UNKNOWN_216 = 216, + FORCED_NAMING = 216, BUY_CHOCOBO_WHISTLE = 221, RECEIVE_CHOCOBO_WHISTLE = 222, REGISTER_CHOCOBO_WHISTLE = 223, @@ -57,7 +57,7 @@ local vmOpCodeNames = [vmOpCodes.CHECK_REPORT_STATUS] = 'Check report status', [vmOpCodes.INTRO_MENU_PT_2] = 'Intro menu pt 2', [vmOpCodes.INTRO_MENU_PT_3] = 'Intro menu pt 3', - [vmOpCodes.UNKNOWN_216] = 'Unknown 216 (forced renaming?)', + [vmOpCodes.FORCED_NAMING] = 'Forced naming', [vmOpCodes.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive chocobo whistle', [vmOpCodes.REGISTER_CHOCOBO_WHISTLE] = 'Register chocobo whistle', @@ -396,6 +396,14 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(menuFlags, 0, 0, 0, 0, 0, 0, 0) end, + [vmOpCodes.FORCED_NAMING] = function() + -- NOTE: The renaming is done in event playout, otherwise the CS won't see the + -- : new name in time to present it. + -- TODO: If you skip the report where the chocobo is force-named, the new name won't + -- : be visible until you've closed and reopened the menu. + player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) + end, + [vmOpCodes.FEED_CHOCOBO] = function() -- Complete the trade here to prevent any cheesing player:confirmTrade()