From 470ed4648707012d998df20dbd460010e538b1ee Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 13:32:22 +0100 Subject: [PATCH 1/6] Raising: Remove synthetic events, general cleanup --- scripts/commands/chocoboraising.lua | 41 ++- .../hobbies/chocobo_raising/choco_data.lua | 1 + .../chocobo_raising/condense_events.lua | 7 +- .../hobbies/chocobo_raising/constants.lua | 4 +- .../hobbies/chocobo_raising/event_playout.lua | 1 + .../hobbies/chocobo_raising/event_vm.lua | 303 +++++++++++++----- 6 files changed, 261 insertions(+), 96 deletions(-) diff --git a/scripts/commands/chocoboraising.lua b/scripts/commands/chocoboraising.lua index ed57716b0ef..3baab173c9d 100644 --- a/scripts/commands/chocoboraising.lua +++ b/scripts/commands/chocoboraising.lua @@ -71,7 +71,23 @@ commandObj.onTrigger = function(player) }) table.insert(menu.options, { - 'Debug #1', + 'Debug #1: d0-d4', + function(playerArg) + playerArg:deleteRaisedChocobo() + + local egg = {} + local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) + player:setChocoboRaisingInfo(newChoco) + + local info = playerArg:getChocoboRaisingInfo() + info['created'] = info['created'] - (epochDay * 4) + playerArg:setChocoboRaisingInfo(info) + + playerArg:printToPlayer('Setting up debug scenario 1 (4d update)', xi.msg.channel.SYSTEM_3, '') + end, + }) + table.insert(menu.options, { + 'Debug #2: d0-d10', function(playerArg) playerArg:deleteRaisedChocobo() @@ -83,7 +99,7 @@ commandObj.onTrigger = function(player) info['created'] = info['created'] - (epochDay * 10) playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Setting up debug scenario 1 (10d update)', xi.msg.channel.SYSTEM_3, '') + playerArg:printToPlayer('Setting up debug scenario 2 (10d update)', xi.msg.channel.SYSTEM_3, '') end, }) @@ -136,7 +152,24 @@ commandObj.onTrigger = function(player) }) table.insert(menu.options, { - 'Debug #1', + 'Debug #1: d0-d4', + function(playerArg) + playerArg:deleteRaisedChocobo() + + local egg = {} + local newChoco = xi.chocoboRaising.newChocobo(playerArg, egg) + player:setChocoboRaisingInfo(newChoco) + + local info = playerArg:getChocoboRaisingInfo() + info['created'] = info['created'] - (epochDay * 4) + playerArg:setChocoboRaisingInfo(info) + + playerArg:printToPlayer('Setting up debug scenario 1 (4d update)', xi.msg.channel.SYSTEM_3, '') + end, + }) + + table.insert(menu.options, { + 'Debug #2: d0-d10', function(playerArg) playerArg:deleteRaisedChocobo() @@ -148,7 +181,7 @@ commandObj.onTrigger = function(player) info['created'] = info['created'] - (epochDay * 10) playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Setting up debug scenario 1 (10d update)', xi.msg.channel.SYSTEM_3, '') + playerArg:printToPlayer('Setting up debug scenario 2 (10d update)', xi.msg.channel.SYSTEM_3, '') end, }) end diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 53cf8a8f5d4..b78b4cdab6e 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -8,6 +8,7 @@ xi.chocoboRaising = xi.chocoboRaising or {} local debug = utils.getDebugPlayerPrinter(xi.settings.main.DEBUG_CHOCOBO_RAISING) +-- TODO: There's a lot of logic in here about reporting, should we move this into it's own file? xi.chocoboRaising.initChocoState = function(player) local chocoState = player:getChocoboRaisingInfo() if not chocoState then diff --git a/scripts/globals/hobbies/chocobo_raising/condense_events.lua b/scripts/globals/hobbies/chocobo_raising/condense_events.lua index 3b2df6e6eda..268ba6285ff 100644 --- a/scripts/globals/hobbies/chocobo_raising/condense_events.lua +++ b/scripts/globals/hobbies/chocobo_raising/condense_events.lua @@ -95,7 +95,12 @@ xi.chocoboRaising.condenseEvents = function(events) debug('Condensed Events & Spans') for _, entry in ipairs(condensedEvents) do - debug('Days', entry[1], 'to', entry[2], ':', entry[3][1]) + local csList = entry[3] + if #csList > 1 then + debug('Days', entry[1], 'to', entry[2], ':', tostring(csList[1]) .. string.format(' (+%d events)', #csList - 1)) + else + debug('Days', entry[1], 'to', entry[2], ':', csList[1]) + end end return condensedEvents diff --git a/scripts/globals/hobbies/chocobo_raising/constants.lua b/scripts/globals/hobbies/chocobo_raising/constants.lua index 9b759cc0e78..931ae3bef68 100644 --- a/scripts/globals/hobbies/chocobo_raising/constants.lua +++ b/scripts/globals/hobbies/chocobo_raising/constants.lua @@ -569,7 +569,7 @@ xi.chocoboRaising.getWeatherInZone = function(zoneId) end -- If stage = [1] and age >= [2], play CS: [3] and set stage to [4]. -local ageBoundaries = +xi.chocoboRaising.ageBoundaries = { { xi.chocoboRaising.stage.EGG, xi.chocoboRaising.daysToChick, xi.chocoboRaising.cutscenes.EGG_HATCHING, xi.chocoboRaising.stage.CHICK }, { xi.chocoboRaising.stage.CHICK, xi.chocoboRaising.daysToAdolescent, xi.chocoboRaising.cutscenes.CHICK_TO_ADOLESCENT, xi.chocoboRaising.stage.ADOLESCENT }, @@ -580,7 +580,7 @@ local ageBoundaries = } xi.chocoboRaising.ageToStage = function(age) - for _, entry in ipairs(ageBoundaries) do + for _, entry in ipairs(xi.chocoboRaising.ageBoundaries) do if age <= entry[2] then return entry[1] end diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index 6e43c78c159..369e585b9a7 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -8,6 +8,7 @@ xi.chocoboRaising = xi.chocoboRaising or {} local debug = utils.getDebugPlayerPrinter(xi.settings.main.DEBUG_CHOCOBO_RAISING) +-- TODO: Split trade and trigger blocks apart xi.chocoboRaising.startCutscene = function(player, npc, trade) local ID = zones[player:getZoneID()] local reminderCsid = xi.chocoboRaising.csidTable[player:getZoneID()][1] diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 7cd070af59d..f8c05650280 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -8,6 +8,84 @@ xi.chocoboRaising = xi.chocoboRaising or {} 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, + BUY_CHOCOBO_WHISTLE = 221, + RECEIVE_CHOCOBO_WHISTLE = 222, + 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, + 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.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', + [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive 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.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) -- TODO: The majority of logic is controlled by the option, which is -- sent in by the client. We can't trust this isn't tampered with. @@ -32,8 +110,15 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) return end - debug(string.format('CS Update: %i', option)) + -------------------------------------------------------- + -- Special cases + -- + -- The VM option will have additional information packed into it and signal + -- this is the case by using a flag in the last byte. We'll look for it and + -- then handle these special cases, bailing out before we hit the VM proper. + -------------------------------------------------------- + -------------------------------------------------------- -- Setting the name for a chocobo: when the name is -- applied from the menu the name offsets (from the menu) -- are sent combined inside the option. The bottom byte @@ -43,7 +128,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- 0000 0000 0000 0100 0000 0000 1111 1111 -- ^----------^^----------^ ^-------^ -- last_name first_name name_change_flag (0xFF) - + -------------------------------------------------------- if bit.band(0x000000FF, option) == 0xFF then local offset1 = bit.band(0x3FF, bit.rshift(option, 8)) local offset2 = bit.band(0x3FF, bit.rshift(option, 18)) @@ -73,12 +158,22 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- Write to cache xi.chocoboRaising.chocoState[player:getID()] = chocoState - -- Set synthetic CS option for later CSs - option = 0xFF - debug(string.format('CS (Synthetic) Update: %i', option)) + -- If the name is still 'Chocobo Chocobo' then the renaming failed or was + -- rejected, play the appropriate response. + if chocoState.first_name == 'Chocobo' and chocoState.last_name == 'Chocobo' then + player:updateEvent(1, 1, 1, 1, 1, 1, 1, 1) + else + player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) + end + + -- TODO: Write to db? end + + -- There's no follow-up CSs here, bail out + return end + -------------------------------------------------------- -- Similar to above, updates to care plan are flagged by setting bits -- in the update option. In this case, the mask if 0xFE (1111 1110). -- @@ -92,12 +187,15 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- 2: Type of care plan -- 3: Slot of care plan -- 4: 'Key' for care plan updates (0xFE) - + -------------------------------------------------------- if bit.band(0x000000FF, option) == 0xFE then local carePlanSlot = bit.band(0xF, bit.rshift(option, 8)) local carePlanLength = bit.band(0x7, bit.rshift(option, 16)) local carePlanType = bit.band(0xF, bit.rshift(option, 19)) + debug(string.format('carePlanSlot: %i, carePlanLength: %i, carePlanType: %i', + carePlanSlot, carePlanLength, carePlanType)) + -- If zero, make sure to default if chocoState.care_plan == 0 then local defaultCarePlan = bit.lshift(7, 4) + 0 @@ -120,20 +218,27 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) local finalCarePlan = bit.bor(zerodCarePlan, bit.lshift(carePlan, targetSlotOffset)) chocoState.care_plan = finalCarePlan - print(string.format('%s updating chocobo care plan: slot: %i type: %i length: %i', + debug(string.format('%s updating chocobo care plan: slot: %i type: %i length: %i', player:getName(), carePlanSlot + 1, carePlanType, carePlanLength)) -- Write to cache xi.chocoboRaising.chocoState[player:getID()] = chocoState + + -- TODO: Write to db? + + -- There's no follow-up CSs here, bail out + return end -------------------------------------------------------- -- Main body update logic -------------------------------------------------------- + local opCodeName = xi.chocoboRaising.vmOpCodeNames[option] or '?' + debug(string.format('ChocoVM Op: %i: %s', option, opCodeName)) + switch (option): caseof { - -- ? - [208] = function() + [vmOpCodes.CHECK_REPORT_STATUS] = function() local hasReport = 0 if #chocoState.report.events > 0 then hasReport = 0xFFFFFFFF @@ -143,7 +248,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, -- ? - [252] = function() + [vmOpCodes.UNKNOWN_252] = function() local hasReport = 0 if #chocoState.report.events > 0 then hasReport = 0xFFFFFFFF @@ -154,7 +259,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- Main menu (248 -> 214 -> 215) -- Update (248 -> 246 -> 244) - [248] = function() + [vmOpCodes.INTRO_MENU_PT_1] = function() local report = 0x00000000 if #chocoState.report.events > 0 then @@ -196,11 +301,11 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(248, report, #chocoState.csList, playMultipleCutscenes, chocoState.stage, 0, 0, exitFlag) end, - [214] = function() + [vmOpCodes.INTRO_MENU_PT_2] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [215] = function() + [vmOpCodes.INTRO_MENU_PT_3] = function() -- Define menu options -- bit.lshift(0x01, 0): Ask about your chocobo's condition local askAboutChocoboCondition = -bit.lshift(0x01, 0) @@ -278,7 +383,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(menuFlags, 0, 0, 0, 0, 0, 0, 0) end, - [241] = function() -- Feed chocobo + [vmOpCodes.FEED_CHOCOBO] = function() -- Complete the trade here to prevent any cheesing player:confirmTrade() @@ -325,9 +430,17 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) xi.chocoboRaising.updateChocoState(player, chocoState) end, - [244] = function() -- Present chocobo appearance + [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: + 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. @@ -357,11 +470,13 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(chocoState.color, enlargedCrest, enlargedFeet, moreTailFeathers, chocoState.stage, 1, 0, 0) end, - [46] = function() -- Ask about chocobo's condition (menu) + -- 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() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [600] = function() + [vmOpCodes.UNKNOWN_600] = function() -- Get KI during another CS (determined randomly) local ki = xi.ki.DIRTY_HANDKERCHIEF local getKi = 1 @@ -370,10 +485,10 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:addKeyItem(ki) end, - [251] = function() -- Ask about chocobo's condition (confirm) + [vmOpCodes.ASK_ABOUT_CONDITION_CONFIRM] = function() -- 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 = 251 + 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 arg3 = bit.lshift(chocoState.personality, 0) + @@ -403,7 +518,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(arg0, arg1, arg2, arg3, arg4, 0, 0, 0) end, - [243] = function() -- Care for your chocobo (menu) + [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = function() local watchOverChocobo = 0x01 local tellAStory = 0x02 local scoldTheChocobo = 0x04 @@ -433,7 +548,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(mask, chocoState.energy, 0, 0, 0, 0, 0, 0) end, - [10994] = function() -- Go on a walk (Short) - Leisurely / Brisk + [vmOpCodes.GO_ON_A_WALK_SHORT] = function() table.insert(chocoState.csList, xi.chocoboRaising.cutscenes.TAKE_A_WALK) player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, @@ -510,7 +625,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(unpack(output)) end, - [11250] = function() -- Go on a walk (Regular) - Leisurely / Brisk + [vmOpCodes.GO_ON_A_WALK_REGULAR] = function() table.insert(chocoState.csList, xi.chocoboRaising.cutscenes.TAKE_A_WALK) player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, @@ -562,7 +677,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(unpack(output)) end, - [11506] = function() -- Go on a walk (Long) - Leisurely / Brisk + [vmOpCodes.GO_ON_A_WALK_LONG] = function() table.insert(chocoState.csList, xi.chocoboRaising.cutscenes.TAKE_A_WALK) player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, @@ -613,53 +728,61 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(unpack(output)) end, - [12530] = function() -- Watch over your your chocobo (confirm) - player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, - 0, 0, 0, 0, 0, 0, 0) + [vmOpCodes.WATCH_OVER_CHOCOBO_CONFIRM] = function() + -- TODO: Is this needed? Check caps + -- player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, + -- 0, 0, 0, 0, 0, 0, 0) local baseCS = xi.chocoboRaising.csidTable[player:getZoneID()][9] + -- + -- Handle egg case + -- if chocoState.stage == xi.chocoboRaising.stage.EGG then -- Your egg does not seem to be in the best condition at the moment... - local badEggFlag = 0 -- bit.lshift(0x01, 31) (1st arg) + local badEggFlag = 0 -- bit.lshift(0x01, 31) -- 1st arg player:updateEvent(baseCS, badEggFlag, 0, 0, 0, 0, 0, 0) - else - local energyFlag = 0 + return + end - if chocoState.energy < xi.chocoboRaising.watchOverEnergy then - energyFlag = -1 - else - chocoState.energy = chocoState.energy - xi.chocoboRaising.watchOverEnergy - end + -- + -- Regular case + -- + local energyFlag = 0 - -- Sandy: 304, 14396, 0, 0, 6, 0, 0, 2 - -- Windurst: 816, 18250, 1, 511, 2, 0, 0, 1 - local givingItem = 0 - local givenItem = 0 + if chocoState.energy < xi.chocoboRaising.watchOverEnergy then + energyFlag = -1 + else + chocoState.energy = chocoState.energy - xi.chocoboRaising.watchOverEnergy + end - if chocoState.held_item > 0 then - givingItem = 1 - givenItem = chocoState.held_item - end + -- Sandy: 304, 14396, 0, 0, 6, 0, 0, 2 + -- Windurst: 816, 18250, 1, 511, 2, 0, 0, 1 + local givingItem = 0 + local givenItem = 0 - if - givingItem == 1 and - player:getFreeSlotsCount() == 0 - then - givingItem = 2 - end + if chocoState.held_item > 0 then + givingItem = 1 + givenItem = chocoState.held_item + end - player:updateEvent(baseCS, energyFlag, givingItem, givenItem, 2, 0, 0, 1) + if + givingItem == 1 and + player:getFreeSlotsCount() == 0 + then + givingItem = 2 + end - if givingItem == 1 then - player:addItem({ id = givenItem, silent = true }) - chocoState.held_item = 0 - end + player:updateEvent(baseCS, energyFlag, givingItem, givenItem, 2, 0, 0, 1) + + if givingItem == 1 then + player:addItem({ id = givenItem, silent = true }) + chocoState.held_item = 0 end end, - [13042] = function() -- Tell a story + [vmOpCodes.TELL_A_STORY] = function() -- A chocobo must have a DSC of D (A bit deficient, 64-95) or -- higher to have a chance at learning a skill from a story if chocoState.discernment >= 64 then @@ -676,7 +799,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) xi.chocoboRaising.updateChocoState(player, chocoState) end, - [13298] = function() -- Scold the chocobo + [vmOpCodes.SCOLD_CHOCOBO] = function() chocoState = xi.chocoboRaising.onRaisingEventPlayout(player, xi.chocoboRaising.cutscenes.HANGS_HEAD_IN_SHAME, chocoState) player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, 0, 0, 0, 0, 0, 0, 0) @@ -684,7 +807,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) xi.chocoboRaising.updateChocoState(player, chocoState) end, - [13554] = function() -- Compete with others + [vmOpCodes.COMPETE_WITH_OTHERS] = function() -- player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.last_name, -- 4163, 67, 0, 0, 0, 0, 0) -- player:updateEvent(820, 18017, 0, 72, 3, 254, 46, 1) @@ -720,7 +843,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) player:updateEvent(xi.chocoboRaising.getCutsceneWithOffset(player, xi.chocoboRaising.cutscenes.COMPETE_WITH_OTHERS), 0, winner, 0, chocoState.stage, 0, 0, 0) end, - [250] = function() -- Set Basic Care (menu) + [vmOpCodes.SET_CARE_SCHEDULE_MENU] = function() local plan1Length = bit.rshift(bit.band(chocoState.care_plan, 0xF0000000), 28) local plan1Type = bit.rshift(bit.band(chocoState.care_plan, 0x0F000000), 24) local plan2Length = bit.rshift(bit.band(chocoState.care_plan, 0x00F00000), 20) @@ -737,46 +860,58 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) bit.lshift(plan4Length, 24) + bit.lshift(plan4Type, 27) -- TODO: Set up mask for relevant stage - local menuMask = 0 -- 0x7FFFFFFE + local menuMask = 0 -- Egg: 0x7FFFFFFE + + -- Default to Egg: + -- TODO: Make this a table + if chocoState.stage == xi.chocoboRaising.stage.EGG then + -- Just 'Basic Care' + menuMask = 0x7FFFFFFE + elseif chocoState.stage == xi.chocoboRaising.stage.CHICK then + menuMask = 0x7FFFFFFE + elseif chocoState.stage == xi.chocoboRaising.stage.ADOLESCENT then + menuMask = 0x7FFFFFFE + elseif chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then + menuMask = 0x7FFFFFFE + end player:updateEvent(250, planInfo, 0, 0, 0, 0, 0, menuMask) end, - [254] = function() -- Set Basic Care plan 1 + [vmOpCodes.SET_BASIC_CARE_PLAN_1] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [510] = function() -- Set Basic Care plan 2 + [vmOpCodes.SET_BASIC_CARE_PLAN_2] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [766] = function() -- Set Basic Care plan 3 + [vmOpCodes.SET_BASIC_CARE_PLAN_3] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [1022] = function() -- Set Basic Care plan 4 + [vmOpCodes.SET_BASIC_CARE_PLAN_4] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [1056] = function() -- Unknown - print('ChocoboRaising: Unknown update: 1056') + [vmOpCodes.UNKNOWN_1056] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [1241] = function() -- Called during 'Compete with Others' + [vmOpCodes.UNKNOWN_1241] = function() -- Called during 'Compete with Others' -- Appears to always be blank player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) end, - [246] = function() -- Update CS + [vmOpCodes.EVENT_PLAYOUT] = function() chocoState = xi.chocoboRaising.handleCSUpdate(player, chocoState, true) end, - [256] = function() -- Brief report? - -- + [vmOpCodes.BRIEF_REPORT] = function() + -- TODO end, - [504] = function() -- Skip report + [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. @@ -796,7 +931,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) chocoState.report.events = {} - -- NOTE: each cs will be popped off inside of handleCSUpdate + -- NOTE: Each cs will be popped off inside of handleCSUpdate while #chocoState.csList > 0 do chocoState = xi.chocoboRaising.handleCSUpdate(player, chocoState, false) end @@ -804,43 +939,33 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) xi.chocoboRaising.updateChocoState(player, chocoState) end, - [221] = function() -- Buy your chocobo whistle - -- + [vmOpCodes.BUY_CHOCOBO_WHISTLE] = function() + -- TODO end, - [222] = function() -- Recieve your chocobo whistle - -- + [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = function() + -- TODO end, - [482] = function() -- DEBUG: Go forward 1 unit + [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) end, - [229] = function() -- DEBUG: Abilities print + [vmOpCodes.DEBUG_ABILITIES_PRINT] = function() player:updateEvent(1, xi.chocoboRaising.packStats1(chocoState), xi.chocoboRaising.packStats2(chocoState), 0, 0, 0, 0, 0) end, - [232] = function() -- DEBUG: User work print + [vmOpCodes.DEBUG_USER_WORK_PRINT] = function() -- TODO: Should we be tracking all user interactions with the chocobo? end, - [240] = function() -- Give up your chocobo + [vmOpCodes.GIVE_UP_CHOCOBO] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) player:deleteRaisedChocobo() end, - [255] = function() -- Synthetic update: Change/set chocobo name - -- If the name is still 'Chocobo Chocobo' then the renaming failed or was - -- rejected, play the appropriate response. - if chocoState.first_name == 'Chocobo' and chocoState.last_name == 'Chocobo' then - player:updateEvent(1, 1, 1, 1, 1, 1, 1, 1) - else - player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) - end - end, - - [40] = function() -- Retire your chocobo + [vmOpCodes.RETIRE_YOUR_CHOCOBO] = function() player:updateEvent(0, 0, 0, 0, 0, 0, 0, 0) player:deleteRaisedChocobo() end, From 8c58e5d6e427fd0e3bc4fec2eab9050213ee02b8 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 14:30:28 +0100 Subject: [PATCH 2/6] Raising: Handle initial care plan shifting and reporting --- .../hobbies/chocobo_raising/care_plan.lua | 105 ++++++++++++++++++ .../hobbies/chocobo_raising/constants.lua | 58 ++++------ .../hobbies/chocobo_raising/event_playout.lua | 39 ++++--- .../hobbies/chocobo_raising/event_vm.lua | 8 +- 4 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 scripts/globals/hobbies/chocobo_raising/care_plan.lua diff --git a/scripts/globals/hobbies/chocobo_raising/care_plan.lua b/scripts/globals/hobbies/chocobo_raising/care_plan.lua new file mode 100644 index 00000000000..6e925ffb233 --- /dev/null +++ b/scripts/globals/hobbies/chocobo_raising/care_plan.lua @@ -0,0 +1,105 @@ +----------------------------------- +-- Chocobo Raising - Care Plans +----------------------------------- +require('scripts/globals/hobbies/chocobo_raising/constants') +----------------------------------- +xi = xi or {} +xi.chocoboRaising = xi.chocoboRaising or {} + +local debug = utils.getDebugPlayerPrinter(xi.settings.main.DEBUG_CHOCOBO_RAISING) + +xi.chocoboRaising.handleStatChange = function(stat, value, change, max) + if change == 0 then + return value + end + + if change > 0 then + debug(string.format('%s += %i', xi.chocoboRaising.carePlanStatNames[stat], change)) + change = change * xi.settings.main.CHOCOBO_RAISING_STAT_POS_MULTIPLIER + elseif change < 0 then + debug(string.format('%s -= %i', xi.chocoboRaising.carePlanStatNames[stat], -change)) + change = change * xi.settings.main.CHOCOBO_RAISING_STAT_NEG_MULTIPLIER + end + + -- TODO: Handle Green Racing Silks here for energy? + -- https://ffxiclopedia.fandom.com/wiki/Green_Race_Silks + + value = utils.clamp(value + change, 0, max) + + return value +end + +xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapsedDays) + elapsedDays = elapsedDays or 1 + + debug(string.format('handleCarePlan: %i days', elapsedDays)) + + -- Process Care Plan shifting + local plan1Length = bit.rshift(bit.band(chocoState.care_plan, 0xF0000000), 28) + local plan1Type = bit.rshift(bit.band(chocoState.care_plan, 0x0F000000), 24) + local plan2Length = bit.rshift(bit.band(chocoState.care_plan, 0x00F00000), 20) + local plan2Type = bit.rshift(bit.band(chocoState.care_plan, 0x000F0000), 16) + local plan3Length = bit.rshift(bit.band(chocoState.care_plan, 0x0000F000), 12) + local plan3Type = bit.rshift(bit.band(chocoState.care_plan, 0x00000F00), 8) + local plan4Length = bit.rshift(bit.band(chocoState.care_plan, 0x000000F0), 4) + local plan4Type = bit.rshift(bit.band(chocoState.care_plan, 0x0000000F), 0) + + local remainingDays = elapsedDays + local hasRemainingPlans = plan1Length > 0 or plan2Length > 0 or plan3Length > 0 or plan4Length > 0 + + while + remainingDays > 0 and + hasRemainingPlans + do + if plan1Length > 0 then + local deduct = math.min(plan1Length, remainingDays) + plan1Length = plan1Length - deduct + remainingDays = remainingDays - deduct + end + + local hasNextPlans = plan2Length > 0 or plan3Length > 0 or plan4Length > 0 + + if + plan1Length == 0 and + hasNextPlans + then + -- Shift plans left + plan1Length = plan2Length + plan1Type = plan2Type + plan2Length = plan3Length + plan2Type = plan3Type + plan3Length = plan4Length + plan3Type = plan4Type + plan4Length = 0 + plan4Type = 0 + end + + hasRemainingPlans = plan1Length > 0 or plan2Length > 0 or plan3Length > 0 or plan4Length > 0 + end + + chocoState.care_plan = + bit.lshift(plan1Length, 28) + bit.lshift(plan1Type, 24) + + bit.lshift(plan2Length, 20) + bit.lshift(plan2Type, 16) + + bit.lshift(plan3Length, 12) + bit.lshift(plan3Type, 8) + + bit.lshift(plan4Length, 4) + bit.lshift(plan4Type, 0) + + -- TODO: Do we end up with the correct energy usage, if we assume the chocobo has slept and recovered + -- : on the morning of the final day of the report? + + chocoState.strength = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.STRENGTH , chocoState.strength , xi.chocoboRaising.carePlanData[carePlan][1] * elapsedDays, 255) + chocoState.endurance = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENDURANCE , chocoState.endurance , xi.chocoboRaising.carePlanData[carePlan][2] * elapsedDays, 255) + chocoState.discernment = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.DISCERNMENT, chocoState.discernment, xi.chocoboRaising.carePlanData[carePlan][3] * elapsedDays, 255) + chocoState.receptivity = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.RECEPTIVITY, chocoState.receptivity, xi.chocoboRaising.carePlanData[carePlan][4] * elapsedDays, 255) + chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION , chocoState.affection , xi.chocoboRaising.carePlanData[carePlan][5] * elapsedDays, 255) + chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY , chocoState.energy , xi.chocoboRaising.carePlanData[carePlan][6] * elapsedDays, 100) + + local payment = xi.chocoboRaising.carePlanData[carePlan][7] + + if payment then + payment = payment * elapsedDays * xi.settings.main.CHOCOBO_RAISING_GIL_MULTIPLIER + debug(string.format('Care Plan Payment: %d', payment)) + + -- TODO: Handle payment using player object + utils.unused(player) + end +end diff --git a/scripts/globals/hobbies/chocobo_raising/constants.lua b/scripts/globals/hobbies/chocobo_raising/constants.lua index 931ae3bef68..4ab5afeb3f7 100644 --- a/scripts/globals/hobbies/chocobo_raising/constants.lua +++ b/scripts/globals/hobbies/chocobo_raising/constants.lua @@ -307,6 +307,26 @@ xi.chocoboRaising.carePlans = ACTING_IN_A_PLAY = 12, } +xi.chocoboRaising.carePlanStats = +{ + STRENGTH = 1, + ENDURANCE = 2, + DISCERNMENT = 3, + RECEPTIVITY = 4, + AFFECTION = 5, + ENERGY = 6, +} + +xi.chocoboRaising.carePlanStatNames = +{ + [xi.chocoboRaising.carePlanStats.STRENGTH ] = 'Strength', + [xi.chocoboRaising.carePlanStats.ENDURANCE ] = 'Endurance', + [xi.chocoboRaising.carePlanStats.DISCERNMENT] = 'Discernment', + [xi.chocoboRaising.carePlanStats.RECEPTIVITY] = 'Receptivity', + [xi.chocoboRaising.carePlanStats.AFFECTION ] = 'Affection', + [xi.chocoboRaising.carePlanStats.ENERGY ] = 'Energy', +} + -- http://www.playonline.com/pcd/update/ff11us/20060822VOL2B1/table03en.jpg -- minor: 1, moderate: 5, major: 10 -- strength, endurance, discernment, receptivity, affection, energy, payment @@ -327,44 +347,6 @@ xi.chocoboRaising.carePlanData = [xi.chocoboRaising.carePlans.ACTING_IN_A_PLAY ] = { -5, 0, 0, 10, -10, -10, 100 }, } -xi.chocoboRaising.handleStatChange = function(stat, change, max) - if change > 0 then - change = change * xi.settings.main.CHOCOBO_RAISING_STAT_POS_MULTIPLIER - elseif change < 0 then - change = change * xi.settings.main.CHOCOBO_RAISING_STAT_NEG_MULTIPLIER - end - - -- TODO: Enum for which stat is changing? - -- TODO: Handle Green Racing Silks here for energy? - -- https://ffxiclopedia.fandom.com/wiki/Green_Race_Silks - - stat = utils.clamp(stat + change, 0, max) - - return stat -end - -xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan) - -- TODO: Take in a multiplier to account for merged time ranges - - chocoState.strength = xi.chocoboRaising.handleStatChange(chocoState.strength , xi.chocoboRaising.carePlanData[carePlan][1], 255) - chocoState.endurance = xi.chocoboRaising.handleStatChange(chocoState.endurance , xi.chocoboRaising.carePlanData[carePlan][2], 255) - chocoState.discernment = xi.chocoboRaising.handleStatChange(chocoState.discernment, xi.chocoboRaising.carePlanData[carePlan][3], 255) - chocoState.receptivity = xi.chocoboRaising.handleStatChange(chocoState.receptivity, xi.chocoboRaising.carePlanData[carePlan][4], 255) - chocoState.affection = xi.chocoboRaising.handleStatChange(chocoState.affection , xi.chocoboRaising.carePlanData[carePlan][5], 255) - chocoState.energy = xi.chocoboRaising.handleStatChange(chocoState.energy , xi.chocoboRaising.carePlanData[carePlan][6], 100) - - local payment = xi.chocoboRaising.carePlanData[carePlan][7] - - if payment then - payment = payment * xi.settings.main.CHOCOBO_RAISING_GIL_MULTIPLIER - debug(string.format('Care Plan Payment: %d', payment)) - - -- TODO: Handle payment - end -end - --- TODO: Make sure stat changes are clamped 0-255! - xi.chocoboRaising.validFoods = { -- [itemId] = { hunger, affection, energy, strength, endurance, discernment, receptivity, randomAttribute, glow } diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index 369e585b9a7..836633b7dfb 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -1,6 +1,7 @@ ----------------------------------- -- Chocobo Raising ----------------------------------- +require('scripts/globals/hobbies/chocobo_raising/care_plan') require('scripts/globals/hobbies/chocobo_raising/constants') ----------------------------------- xi = xi or {} @@ -109,7 +110,9 @@ end xi.chocoboRaising.handleCSUpdate = function(player, chocoState, doEventUpdate) -- Generate final CS value from (location offset * 256) + cutscene offset - local csOffset = chocoState.csList[1] + local csListEntry = chocoState.csList[1] + local csOffset = type(csListEntry) == 'table' and csListEntry[1] or csListEntry + local elapsedDays = type(csListEntry) == 'table' and csListEntry[2] or 1 local locationOffset = xi.chocoboRaising.raisingLocation[player:getZoneID()] * 256 local csToPlay = locationOffset + csOffset @@ -133,9 +136,9 @@ xi.chocoboRaising.handleCSUpdate = function(player, chocoState, doEventUpdate) chocoState.stage = xi.chocoboRaising.stage.ADULT_4 end - chocoState = xi.chocoboRaising.onRaisingEventPlayout(player, csOffset, chocoState) + chocoState = xi.chocoboRaising.onRaisingEventPlayout(player, csOffset, chocoState, elapsedDays) - -- Skip the event updates during 'Skip Report' + -- This will skip the event updates during 'Skip Report' if doEventUpdate then player:updateEventString(chocoState.first_name, chocoState.last_name, chocoState.first_name, chocoState.first_name, 0, 0, 0, 0, 0, 0, 0, 0) @@ -145,63 +148,65 @@ xi.chocoboRaising.handleCSUpdate = function(player, chocoState, doEventUpdate) return chocoState end -xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState) +xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, elapsedDays) + elapsedDays = elapsedDays or 1 + switch (csOffset): caseof { -- EGG ONWARDS: [xi.chocoboRaising.cutscenes.REPORT_BASIC_CARE] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.BASIC_CARE) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.BASIC_CARE, elapsedDays) end, -- CHICK ONWARDS: [xi.chocoboRaising.cutscenes.REPORT_REST] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.RESTING) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.RESTING, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_TAKE_A_WALK] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.TAKING_A_WALK) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.TAKING_A_WALK, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_LISTEN_TO_MUSIC] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.LISTENING_TO_MUSIC) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.LISTENING_TO_MUSIC, elapsedDays) end, -- ADOLESCENT ONWARDS: [xi.chocoboRaising.cutscenes.REPORT_EXERCISE_ALONE] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXERCISING_ALONE) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXERCISING_ALONE, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_EXERCISE_IN_A_GROUP] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXCERCISING_IN_A_GROUP) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXCERCISING_IN_A_GROUP, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_INTERACT_WITH_CHILDREN] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.PLAYING_WITH_CHILDREN) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.PLAYING_WITH_CHILDREN, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_INTERACT_WITH_CHOCOBOS] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.PLAYING_WITH_CHOCOBOS) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.PLAYING_WITH_CHOCOBOS, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_CARRY_PACKAGES] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.CARRYING_PACKAGES) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.CARRYING_PACKAGES, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_EXHIBIT_TO_THE_PUBLIC] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXHIBITING_TO_THE_PUBLIC) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.EXHIBITING_TO_THE_PUBLIC, elapsedDays) end, -- ADULT ONWARDS: [xi.chocoboRaising.cutscenes.REPORT_DELIVER_MESSAGES] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.DELIVERING_MESSAGES) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.DELIVERING_MESSAGES, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_DIG_FOR_TREASURE] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.DIGGING_FOR_TREASURE) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.DIGGING_FOR_TREASURE, elapsedDays) end, [xi.chocoboRaising.cutscenes.REPORT_ACT_IN_A_PLAY] = function() - xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.ACTING_IN_A_PLAY) + xi.chocoboRaising.handleCarePlan(player, chocoState, xi.chocoboRaising.carePlans.ACTING_IN_A_PLAY, elapsedDays) end, -- Growth CSs diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index f8c05650280..61ba53dc946 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -275,7 +275,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) chocoState.stage = xi.chocoboRaising.ageToStage(chocoState.age) for _, cs in pairs(eventCSList) do - table.insert(chocoState.csList, cs) + table.insert(chocoState.csList, { cs, eventStartEnd - eventStartStart + 1 }) end report = bit.lshift(eventStartStart, 0) + bit.lshift(eventStartEnd, 20) @@ -918,14 +918,14 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -- Prepare chocoState.csList for _, currentEvent in pairs (chocoState.report.events) do local eventStartStart = currentEvent[1] - -- local eventStartEnd = currentEvent[2] - local eventCSList = currentEvent[3] + local eventStartEnd = currentEvent[2] + local eventCSList = currentEvent[3] chocoState.age = eventStartStart chocoState.stage = xi.chocoboRaising.ageToStage(chocoState.age) for _, cs in pairs(eventCSList) do - table.insert(chocoState.csList, cs) + table.insert(chocoState.csList, { cs, eventStartEnd - eventStartStart + 1 }) end end From 828c198cfd01348e109bd2f35b7a93890f07d6e0 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 15:36:14 +0100 Subject: [PATCH 3/6] Raising: Don't accumulate energy usage through full report --- .../globals/hobbies/chocobo_raising/care_plan.lua | 15 ++++++++------- .../hobbies/chocobo_raising/choco_data.lua | 9 ++------- .../globals/hobbies/chocobo_raising/event_vm.lua | 3 +++ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/care_plan.lua b/scripts/globals/hobbies/chocobo_raising/care_plan.lua index 6e925ffb233..79071d1dffc 100644 --- a/scripts/globals/hobbies/chocobo_raising/care_plan.lua +++ b/scripts/globals/hobbies/chocobo_raising/care_plan.lua @@ -14,10 +14,10 @@ xi.chocoboRaising.handleStatChange = function(stat, value, change, max) end if change > 0 then - debug(string.format('%s += %i', xi.chocoboRaising.carePlanStatNames[stat], change)) + debug(string.format(' %s += %i', xi.chocoboRaising.carePlanStatNames[stat], change)) change = change * xi.settings.main.CHOCOBO_RAISING_STAT_POS_MULTIPLIER elseif change < 0 then - debug(string.format('%s -= %i', xi.chocoboRaising.carePlanStatNames[stat], -change)) + debug(string.format(' %s -= %i', xi.chocoboRaising.carePlanStatNames[stat], -change)) change = change * xi.settings.main.CHOCOBO_RAISING_STAT_NEG_MULTIPLIER end @@ -32,7 +32,7 @@ end xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapsedDays) elapsedDays = elapsedDays or 1 - debug(string.format('handleCarePlan: %i days', elapsedDays)) + debug(string.format('Execute Care Plan: %i days', elapsedDays)) -- Process Care Plan shifting local plan1Length = bit.rshift(bit.band(chocoState.care_plan, 0xF0000000), 28) @@ -83,15 +83,16 @@ xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapse bit.lshift(plan3Length, 12) + bit.lshift(plan3Type, 8) + bit.lshift(plan4Length, 4) + bit.lshift(plan4Type, 0) - -- TODO: Do we end up with the correct energy usage, if we assume the chocobo has slept and recovered - -- : on the morning of the final day of the report? - chocoState.strength = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.STRENGTH , chocoState.strength , xi.chocoboRaising.carePlanData[carePlan][1] * elapsedDays, 255) chocoState.endurance = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENDURANCE , chocoState.endurance , xi.chocoboRaising.carePlanData[carePlan][2] * elapsedDays, 255) chocoState.discernment = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.DISCERNMENT, chocoState.discernment, xi.chocoboRaising.carePlanData[carePlan][3] * elapsedDays, 255) chocoState.receptivity = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.RECEPTIVITY, chocoState.receptivity, xi.chocoboRaising.carePlanData[carePlan][4] * elapsedDays, 255) chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION , chocoState.affection , xi.chocoboRaising.carePlanData[carePlan][5] * elapsedDays, 255) - chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY , chocoState.energy , xi.chocoboRaising.carePlanData[carePlan][6] * elapsedDays, 100) + + -- TODO: Double check this from caps. + -- After each day the chocobo's energy is refreshed, so only previous day's energy cost is applied + -- to the chocobo + chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY, chocoState.energy, xi.chocoboRaising.carePlanData[carePlan][6], 100) local payment = xi.chocoboRaising.carePlanData[carePlan][7] diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index b78b4cdab6e..a121adc445f 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -47,9 +47,9 @@ xi.chocoboRaising.initChocoState = function(player) chocoState.report.day_start = chocoState.last_update_age chocoState.report.day_end = chocoState.age - local reportLength = chocoState.report.day_end - chocoState.report.day_start - debug('reportLength', reportLength) + local reportLength = chocoState.report.day_end - chocoState.report.day_start + debug('Report length:', chocoState.reportLength) chocoState.last_update_age = chocoState.age @@ -86,9 +86,6 @@ xi.chocoboRaising.initChocoState = function(player) table.insert(possibleCarePlanFuture, plan4Type) end - -- TODO: Remove careplan energy from this - chocoState.energy = 100 - for idx = 1, reportLength do local possibleCarePlanEvent = possibleCarePlanFuture[idx] @@ -152,8 +149,6 @@ xi.chocoboRaising.initChocoState = function(player) then table.insert(events, { age, { xi.chocoboRaising.cutscenes.HAVENT_SEEN_YOU } }) end - - -- TODO: Remove used days from care plan and write back to chocoState + db end -- Step 3: Condense that table down diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 61ba53dc946..e94aaf68372 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -296,6 +296,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) playMultipleCutscenes = 0x00010000 end + -- TODO: What's this? local exitFlag = 0 player:updateEvent(248, report, #chocoState.csList, playMultipleCutscenes, chocoState.stage, 0, 0, exitFlag) @@ -519,6 +520,8 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) end, [vmOpCodes.CARE_FOR_CHOCOBO_MENU] = function() + debug(string.format(' Energy: %i', chocoState.energy)) + local watchOverChocobo = 0x01 local tellAStory = 0x02 local scoldTheChocobo = 0x04 From 9b4c921ab9d797f4cff56ea2851d37c341ceeacc Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 15:59:07 +0100 Subject: [PATCH 4/6] Raising: Map out care plan menu --- .../hobbies/chocobo_raising/event_vm.lua | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index e94aaf68372..0f0592d0df0 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -862,20 +862,54 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) bit.lshift(plan3Length, 16) + bit.lshift(plan3Type, 19) + bit.lshift(plan4Length, 24) + bit.lshift(plan4Type, 27) - -- TODO: Set up mask for relevant stage - local menuMask = 0 -- Egg: 0x7FFFFFFE + local emptyMask = 0x7FFFFFFF + local basicCare = -bit.lshift(0x01, 0) + local rest = -bit.lshift(0x01, 1) + local takeAWalkInTown = -bit.lshift(0x01, 2) + local listenToMusic = -bit.lshift(0x01, 3) + local exerciseAlone = -bit.lshift(0x01, 4) + local exerciseInAGroup = -bit.lshift(0x01, 5) + local interactWithChildren = -bit.lshift(0x01, 6) + local interactWithChocobos = -bit.lshift(0x01, 7) + local carryPackages = -bit.lshift(0x01, 8) + local exhibitToThePublic = -bit.lshift(0x01, 9) + local deliverMessages = -bit.lshift(0x01, 10) + local digForTreasure = -bit.lshift(0x01, 11) + local actInAPlay = -bit.lshift(0x01, 12) + -- The remaining options are blank and there seemingly are no + -- debug options - -- Default to Egg: - -- TODO: Make this a table - if chocoState.stage == xi.chocoboRaising.stage.EGG then - -- Just 'Basic Care' - menuMask = 0x7FFFFFFE - elseif chocoState.stage == xi.chocoboRaising.stage.CHICK then - menuMask = 0x7FFFFFFE - elseif chocoState.stage == xi.chocoboRaising.stage.ADOLESCENT then - menuMask = 0x7FFFFFFE - elseif chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then - menuMask = 0x7FFFFFFE + -- + -- Append more options depending on chocobo's age + -- + + -- TODO: Make all of this a table + + -- Options for Egg and beyond + local menuMask = emptyMask + basicCare + + if chocoState.stage >= xi.chocoboRaising.stage.CHICK then + menuMask = menuMask + + rest + + takeAWalkInTown + + listenToMusic + end + + if chocoState.stage >= xi.chocoboRaising.stage.ADOLESCENT then + menuMask = menuMask + + exerciseAlone + + exerciseInAGroup + + interactWithChildren + + interactWithChocobos + + carryPackages + + exhibitToThePublic + end + + if chocoState.stage >= xi.chocoboRaising.stage.ADULT_1 then + menuMask = menuMask + + deliverMessages + + digForTreasure + + actInAPlay end player:updateEvent(250, planInfo, 0, 0, 0, 0, 0, menuMask) From b4af979fb3e7ffe651a9b26e267a76ad171c605b Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 16:36:03 +0100 Subject: [PATCH 5/6] Raising: Trying to correct care plan overrun --- .../hobbies/chocobo_raising/care_plan.lua | 69 ++++++++++++++----- .../hobbies/chocobo_raising/choco_data.lua | 4 +- .../chocobo_raising/condense_events.lua | 6 +- .../hobbies/chocobo_raising/constants.lua | 2 - .../hobbies/chocobo_raising/event_vm.lua | 4 +- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/scripts/globals/hobbies/chocobo_raising/care_plan.lua b/scripts/globals/hobbies/chocobo_raising/care_plan.lua index 79071d1dffc..7955586615f 100644 --- a/scripts/globals/hobbies/chocobo_raising/care_plan.lua +++ b/scripts/globals/hobbies/chocobo_raising/care_plan.lua @@ -44,25 +44,16 @@ xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapse local plan4Length = bit.rshift(bit.band(chocoState.care_plan, 0x000000F0), 4) local plan4Type = bit.rshift(bit.band(chocoState.care_plan, 0x0000000F), 0) - local remainingDays = elapsedDays - local hasRemainingPlans = plan1Length > 0 or plan2Length > 0 or plan3Length > 0 or plan4Length > 0 + local remainingDays = elapsedDays - while - remainingDays > 0 and - hasRemainingPlans - do + while remainingDays > 0 do if plan1Length > 0 then - local deduct = math.min(plan1Length, remainingDays) - plan1Length = plan1Length - deduct + local deduct = math.min(plan1Length, remainingDays) + plan1Length = plan1Length - deduct remainingDays = remainingDays - deduct end - local hasNextPlans = plan2Length > 0 or plan3Length > 0 or plan4Length > 0 - - if - plan1Length == 0 and - hasNextPlans - then + if plan1Length == 0 then -- Shift plans left plan1Length = plan2Length plan1Type = plan2Type @@ -72,9 +63,49 @@ xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapse plan3Type = plan4Type plan4Length = 0 plan4Type = 0 + + -- After shifting, if a plan slot is 0, it should be set back to 7 days of basic care + if plan1Length == 0 then + plan1Length = 7 + plan1Type = xi.chocoboRaising.carePlans.BASIC_CARE + end + + if plan2Length == 0 then + plan2Length = 7 + plan2Type = xi.chocoboRaising.carePlans.BASIC_CARE + end + + if plan3Length == 0 then + plan3Length = 7 + plan3Type = xi.chocoboRaising.carePlans.BASIC_CARE + end + + if plan4Length == 0 then + plan4Length = 7 + plan4Type = xi.chocoboRaising.carePlans.BASIC_CARE + end end + end + + -- Ensure all slots are refilled if they are still empty + if plan1Length == 0 then + plan1Length = 7 + plan1Type = xi.chocoboRaising.carePlans.BASIC_CARE + end + + if plan2Length == 0 then + plan2Length = 7 + plan2Type = xi.chocoboRaising.carePlans.BASIC_CARE + end + + if plan3Length == 0 then + plan3Length = 7 + plan3Type = xi.chocoboRaising.carePlans.BASIC_CARE + end - hasRemainingPlans = plan1Length > 0 or plan2Length > 0 or plan3Length > 0 or plan4Length > 0 + if plan4Length == 0 then + plan4Length = 7 + plan4Type = xi.chocoboRaising.carePlans.BASIC_CARE end chocoState.care_plan = @@ -83,16 +114,16 @@ xi.chocoboRaising.handleCarePlan = function(player, chocoState, carePlan, elapse bit.lshift(plan3Length, 12) + bit.lshift(plan3Type, 8) + bit.lshift(plan4Length, 4) + bit.lshift(plan4Type, 0) - chocoState.strength = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.STRENGTH , chocoState.strength , xi.chocoboRaising.carePlanData[carePlan][1] * elapsedDays, 255) - chocoState.endurance = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENDURANCE , chocoState.endurance , xi.chocoboRaising.carePlanData[carePlan][2] * elapsedDays, 255) + chocoState.strength = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.STRENGTH, chocoState.strength, xi.chocoboRaising.carePlanData[carePlan][1] * elapsedDays, 255) + chocoState.endurance = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENDURANCE, chocoState.endurance, xi.chocoboRaising.carePlanData[carePlan][2] * elapsedDays, 255) chocoState.discernment = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.DISCERNMENT, chocoState.discernment, xi.chocoboRaising.carePlanData[carePlan][3] * elapsedDays, 255) chocoState.receptivity = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.RECEPTIVITY, chocoState.receptivity, xi.chocoboRaising.carePlanData[carePlan][4] * elapsedDays, 255) - chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION , chocoState.affection , xi.chocoboRaising.carePlanData[carePlan][5] * elapsedDays, 255) + chocoState.affection = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.AFFECTION, chocoState.affection, xi.chocoboRaising.carePlanData[carePlan][5] * elapsedDays, 255) -- TODO: Double check this from caps. -- After each day the chocobo's energy is refreshed, so only previous day's energy cost is applied -- to the chocobo - chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY, chocoState.energy, xi.chocoboRaising.carePlanData[carePlan][6], 100) + chocoState.energy = xi.chocoboRaising.handleStatChange(xi.chocoboRaising.carePlanStats.ENERGY, 100, xi.chocoboRaising.carePlanData[carePlan][6], 100) local payment = xi.chocoboRaising.carePlanData[carePlan][7] diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index a121adc445f..3fc5b16d784 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -49,7 +49,7 @@ xi.chocoboRaising.initChocoState = function(player) chocoState.report.day_end = chocoState.age local reportLength = chocoState.report.day_end - chocoState.report.day_start - debug('Report length:', chocoState.reportLength) + debug('Report length:', reportLength) chocoState.last_update_age = chocoState.age @@ -90,7 +90,7 @@ xi.chocoboRaising.initChocoState = function(player) local possibleCarePlanEvent = possibleCarePlanFuture[idx] if possibleCarePlanEvent == nil then -- We went past the end of the care plan - possibleCarePlanEvent = 0 -- Default to Basic Care + possibleCarePlanEvent = xi.chocoboRaising.carePlans.BASIC_CARE end local age = chocoState.report.day_start + idx - 1 diff --git a/scripts/globals/hobbies/chocobo_raising/condense_events.lua b/scripts/globals/hobbies/chocobo_raising/condense_events.lua index 268ba6285ff..e11d54e2248 100644 --- a/scripts/globals/hobbies/chocobo_raising/condense_events.lua +++ b/scripts/globals/hobbies/chocobo_raising/condense_events.lua @@ -54,7 +54,7 @@ xi.chocoboRaising.condenseEvents = function(events) local eventDay = entry[1] local eventCSList = entry[2] - debug('Day', eventDay, ':', eventCSList[1]) + debug(' Day', eventDay, ':', eventCSList[1]) if not currentEventCSTable then -- Start first span @@ -97,9 +97,9 @@ xi.chocoboRaising.condenseEvents = function(events) for _, entry in ipairs(condensedEvents) do local csList = entry[3] if #csList > 1 then - debug('Days', entry[1], 'to', entry[2], ':', tostring(csList[1]) .. string.format(' (+%d events)', #csList - 1)) + debug(' Days', entry[1], 'to', entry[2], ':', tostring(csList[1]) .. string.format(' (+%d events)', #csList - 1)) else - debug('Days', entry[1], 'to', entry[2], ':', csList[1]) + debug(' Days', entry[1], 'to', entry[2], ':', csList[1]) end end diff --git a/scripts/globals/hobbies/chocobo_raising/constants.lua b/scripts/globals/hobbies/chocobo_raising/constants.lua index 4ab5afeb3f7..903c6c184bc 100644 --- a/scripts/globals/hobbies/chocobo_raising/constants.lua +++ b/scripts/globals/hobbies/chocobo_raising/constants.lua @@ -4,8 +4,6 @@ xi = xi or {} xi.chocoboRaising = xi.chocoboRaising or {} -local debug = utils.getDebugPlayerPrinter(xi.settings.main.DEBUG_CHOCOBO_RAISING) - -- TODO: Remove the duplication for walk CSs xi.chocoboRaising.csidTable = { diff --git a/scripts/globals/hobbies/chocobo_raising/event_vm.lua b/scripts/globals/hobbies/chocobo_raising/event_vm.lua index 0f0592d0df0..4434e87113f 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_vm.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_vm.lua @@ -15,6 +15,7 @@ local vmOpCodes = 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, DEBUG_ABILITIES_PRINT = 229, @@ -54,6 +55,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.BUY_CHOCOBO_WHISTLE] = 'Buy chocobo whistle', [vmOpCodes.RECEIVE_CHOCOBO_WHISTLE] = 'Receive chocobo whistle', [vmOpCodes.DEBUG_ABILITIES_PRINT] = 'Debug abilities print', @@ -233,7 +235,7 @@ xi.chocoboRaising.eventVM = function(player, csid, option, npc) -------------------------------------------------------- -- Main body update logic -------------------------------------------------------- - local opCodeName = xi.chocoboRaising.vmOpCodeNames[option] or '?' + local opCodeName = vmOpCodeNames[option] or '?' debug(string.format('ChocoVM Op: %i: %s', option, opCodeName)) switch (option): caseof From ddbae9eb03559704c408dec26eca294badc57739 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Tue, 21 Apr 2026 18:01:38 +0100 Subject: [PATCH 6/6] Raising: Handle White Handkerchief quest --- scripts/commands/chocoboraising.lua | 31 +++++----------- .../hobbies/chocobo_raising/choco_data.lua | 36 +++++++++++++++++-- .../hobbies/chocobo_raising/constants.lua | 4 +-- .../hobbies/chocobo_raising/event_playout.lua | 14 +++++--- 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/scripts/commands/chocoboraising.lua b/scripts/commands/chocoboraising.lua index 3baab173c9d..2b4e847a1f3 100644 --- a/scripts/commands/chocoboraising.lua +++ b/scripts/commands/chocoboraising.lua @@ -99,28 +99,11 @@ commandObj.onTrigger = function(player) info['created'] = info['created'] - (epochDay * 10) playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Setting up debug scenario 2 (10d update)', xi.msg.channel.SYSTEM_3, '') - end, - }) - - table.insert(menu.options, { - 'Change sex', - function(playerArg) - local info = playerArg:getChocoboRaisingInfo() - info['sex'] = (info['sex'] + 1) % 2 - playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Changed sex to ' .. sex[info['sex']], xi.msg.channel.SYSTEM_3, '') - end, - }) - - table.insert(menu.options, { - 'Dump chocoState', - function(playerArg) - local info = playerArg:getChocoboRaisingInfo() - playerArg:printToPlayer('created ' .. os.date('%Y %m %d %H %M %S', info['created']), xi.msg.channel.SYSTEM_3, '') - for k, v in pairs(info) do - playerArg:printToPlayer(string.format('%s %s', k, v), xi.msg.channel.SYSTEM_3, '') + 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, '') end, }) @@ -181,7 +164,11 @@ commandObj.onTrigger = function(player) info['created'] = info['created'] - (epochDay * 10) playerArg:setChocoboRaisingInfo(info) - playerArg:printToPlayer('Setting up debug scenario 2 (10d update)', xi.msg.channel.SYSTEM_3, '') + 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, '') end, }) end diff --git a/scripts/globals/hobbies/chocobo_raising/choco_data.lua b/scripts/globals/hobbies/chocobo_raising/choco_data.lua index 3fc5b16d784..1e0e14547b1 100644 --- a/scripts/globals/hobbies/chocobo_raising/choco_data.lua +++ b/scripts/globals/hobbies/chocobo_raising/choco_data.lua @@ -130,25 +130,55 @@ xi.chocoboRaising.initChocoState = function(player) end end + -- + -- Quest conditions + -- + + -- TODO: Come up with a more modular way to track these + -- Start White Handkerchief quest - local whiteHandkerchiefStarted = false + -- 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 - -- TODO: Should this be a charvar to track this? - not player:hasKeyItem(xi.ki.WHITE_HANDKERCHIEF) and + not player:hasKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) and age == 7 then + debug('Starting White Handkerchief quest') table.insert(events, { age, { xi.chocoboRaising.cutscenes.CRYING_AT_NIGHT } }) whiteHandkerchiefStarted = true end -- Cancel White Handkerchief quest + -- NOTE: If we've played all the way out here, it'll then not be possible to complete + -- : this quest until the next chocobo. if whiteHandkerchiefStarted 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 } }) end + + -- End White Handkerchief quest + -- NOTE: It isn't possible to fly through and complete this in one go in retail, so the KI + -- : check ensures you have to finish talking to the trainer, pass some time, and then + -- : come back + -- TODO: Need to zone too? + 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 + not whiteHandkerchiefCancelled + then + debug('Ending White Handkerchief quest') + whiteHandkerchiefStarted = false + table.insert(events, { age, { xi.chocoboRaising.cutscenes.THAT_SHOULD_BE_ENOUGH } }) + end end -- Step 3: Condense that table down diff --git a/scripts/globals/hobbies/chocobo_raising/constants.lua b/scripts/globals/hobbies/chocobo_raising/constants.lua index 903c6c184bc..56c087092f7 100644 --- a/scripts/globals/hobbies/chocobo_raising/constants.lua +++ b/scripts/globals/hobbies/chocobo_raising/constants.lua @@ -123,8 +123,8 @@ xi.chocoboRaising.cutscenes = HANGS_HEAD_IN_SHAME = 51, -- Hangs its head in shame COMPETE_WITH_OTHERS = 52, HAVENT_SEEN_YOU = 53, -- Haven't seen you around, chocobo is sleeping (dispose of white handkerchief) - -- 54: Accept white handkerchief - CRYING_AT_NIGHT = 69, -- White handkerchief + THAT_SHOULD_BE_ENOUGH = 54, -- That should be enough! Hand over the {White Handkerchief} now. (white handkerchief end) + CRYING_AT_NIGHT = 69, -- White handkerchief start -- 70: Chocobo full of energy! -- 71: Bright and focused -- 72: Injury has healed diff --git a/scripts/globals/hobbies/chocobo_raising/event_playout.lua b/scripts/globals/hobbies/chocobo_raising/event_playout.lua index 836633b7dfb..bbc456295f9 100644 --- a/scripts/globals/hobbies/chocobo_raising/event_playout.lua +++ b/scripts/globals/hobbies/chocobo_raising/event_playout.lua @@ -224,13 +224,19 @@ xi.chocoboRaising.onRaisingEventPlayout = function(player, csOffset, chocoState, [xi.chocoboRaising.cutscenes.CRYING_AT_NIGHT] = function() -- NOTE: The messaging is handled in the CS - player:addKeyItem(xi.ki.WHITE_HANDKERCHIEF) - player:setCharVar('[choco]WH_TIME', GetSystemTime() * utils.days(1)) + debug('Giving KI White Handkerchief') + player:addKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) + end, + + [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? end, [xi.chocoboRaising.cutscenes.HAVENT_SEEN_YOU] = function() - player:delKeyItem(xi.ki.WHITE_HANDKERCHIEF) - player:setCharVar('[choco]WH_TIME', 0) + debug('Removing KI White Handkerchief') + player:delKeyItem(xi.keyItem.WHITE_HANDKERCHIEF) end, [xi.chocoboRaising.cutscenes.HANGS_HEAD_IN_SHAME] = function()