From 7dc686cecb96c24b99c5bf14f8aad411390dc69b Mon Sep 17 00:00:00 2001 From: Gregorios Leach <4183233+simtel12@users.noreply.github.com> Date: Sat, 8 Aug 2026 18:13:33 -0700 Subject: [PATCH] workorders: add ;continue to resume an in-progress order from the logbook Reads the discipline logbook to recover the item and remaining quantity of an order already accepted (e.g. after a disconnect), instead of canceling and re-requesting one. Skips straight to turn-in when the logbook reports the order already complete. --- spec/workorders_spec.rb | 89 +++++++++++++++++++++++++++++++- workorders.lic | 111 ++++++++++++++++++++++++++++++++++------ 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/spec/workorders_spec.rb b/spec/workorders_spec.rb index 88a068a7d3..da0b078ccd 100644 --- a/spec/workorders_spec.rb +++ b/spec/workorders_spec.rb @@ -122,6 +122,26 @@ def before_dying(&block) expect(match[:remaining]).to eq('3') end + it 'defines LOGBOOK_ITEM_PATTERN with named capture' do + pattern = described_class::LOGBOOK_ITEM_PATTERN + match = 'This logbook is tracking a work order requiring you to craft a curved metal shovel from any material.'.match(pattern) + expect(match).not_to be_nil + expect(match[:item]).to eq('a curved metal shovel') + end + + it 'captures fabric items when remaining count is on the same line' do + line = 'This logbook is tracking a work order requiring you to craft a knitted napkin from any fabric. You must bundle and deliver 1 more within the next 2 roisaen.' + match = line.match(described_class::LOGBOOK_ITEM_PATTERN) + expect(match).not_to be_nil + expect(match[:item]).to eq('a knitted napkin') + expect(line.match(described_class::LOGBOOK_REMAINING_PATTERN)[:remaining]).to eq('1') + end + + it 'defines RESUME_LOGBOOK_PATTERNS as frozen array including LOGBOOK_ITEM_PATTERN' do + expect(described_class::RESUME_LOGBOOK_PATTERNS).to be_frozen + expect(described_class::RESUME_LOGBOOK_PATTERNS).to include(described_class::LOGBOOK_ITEM_PATTERN) + end + it 'defines COUNT_PATTERN with named capture' do pattern = described_class::COUNT_PATTERN match = '42'.match(pattern) @@ -169,7 +189,7 @@ def before_dying(&block) it 'defines VERSION as frozen string' do expect(described_class::VERSION).to be_frozen - expect(described_class::VERSION).to eq('1.0.0') + expect(described_class::VERSION).to eq('1.0.2') end end @@ -349,6 +369,73 @@ def before_dying(&block) end end + # =========================================================================== + # #resume_work_order - parse active order from logbook for continue + # =========================================================================== + describe '#resume_work_order' do + let(:recipes) do + [ + { 'name' => 'a curved metal shovel', 'noun' => 'shovel', 'type' => 'blacksmithing' }, + { 'name' => 'a metal razor', 'noun' => 'razor', 'type' => 'blacksmithing' } + ] + end + + before do + allow(workorders).to receive(:stow_tool) + end + + it 'returns recipe name and remaining count for an in-progress order' do + expect(DRCI).to receive(:get_item?).with('forging logbook').and_return(true) + # bput returns only the matched substring for the first matching pattern + expect(DRC).to receive(:bput).with('read my forging logbook', *described_class::RESUME_LOGBOOK_PATTERNS) + .and_return('This logbook is tracking a work order requiring you to craft a curved metal shovel from any') + expect(workorders).to receive(:logbook_remaining).with('forging').and_return(2) + expect(Lich::Messaging).to receive(:msg).with('plain', 'WorkOrders: Continuing work order for 2 a curved metal shovel') + + expect(workorders.send(:resume_work_order, 'forging', recipes)).to eq(['a curved metal shovel', 2]) + end + + it 'parses knitted fabric orders from the truncated bput match' do + recipes = [{ 'name' => 'a knitted napkin', 'noun' => 'napkin', 'type' => 'tailoring', 'chapter' => 5 }] + expect(DRCI).to receive(:get_item?).with('outfitting logbook').and_return(true) + expect(DRC).to receive(:bput).with('read my outfitting logbook', *described_class::RESUME_LOGBOOK_PATTERNS) + .and_return('This logbook is tracking a work order requiring you to craft a knitted napkin from any') + expect(workorders).to receive(:logbook_remaining).with('outfitting').and_return(1) + expect(Lich::Messaging).to receive(:msg).with('plain', 'WorkOrders: Continuing work order for 1 a knitted napkin') + + expect(workorders.send(:resume_work_order, 'outfitting', recipes)).to eq(['a knitted napkin', 1]) + end + + it 'returns quantity 0 when logbook order appears complete' do + expect(DRCI).to receive(:get_item?).with('forging logbook').and_return(true) + expect(DRC).to receive(:bput).with('read my forging logbook', *described_class::RESUME_LOGBOOK_PATTERNS) + .and_return('This work order appears to be complete.') + expect(Lich::Messaging).to receive(:msg).with('plain', 'WorkOrders: Logbook order complete — turning in') + + expect(workorders.send(:resume_work_order, 'forging', recipes)).to eq([nil, 0]) + end + + it 'exits when logbook has no in-progress work order' do + expect(DRCI).to receive(:get_item?).with('forging logbook').and_return(true) + expect(DRC).to receive(:bput).with('read my forging logbook', *described_class::RESUME_LOGBOOK_PATTERNS) + .and_return('This logbook is not currently tracking a work order.') + expect(Lich::Messaging).to receive(:msg).with('bold', 'WorkOrders: No in-progress work order in forging logbook') + expect(workorders).to receive(:exit).and_raise(SystemExit) + + expect { workorders.send(:resume_work_order, 'forging', recipes) }.to raise_error(SystemExit) + end + + it 'exits when logbook item does not match a recipe' do + expect(DRCI).to receive(:get_item?).with('forging logbook').and_return(true) + expect(DRC).to receive(:bput).with('read my forging logbook', *described_class::RESUME_LOGBOOK_PATTERNS) + .and_return('This logbook is tracking a work order requiring you to craft a metal unknown widget from any') + expect(Lich::Messaging).to receive(:msg).with('bold', "WorkOrders: Could not match logbook item 'a metal unknown widget' to a recipe") + expect(workorders).to receive(:exit).and_raise(SystemExit) + + expect { workorders.send(:resume_work_order, 'forging', recipes) }.to raise_error(SystemExit) + end + end + # =========================================================================== # #bundle_item - pattern matching for success/failure # =========================================================================== diff --git a/workorders.lic b/workorders.lic index f342f4b5cd..ed950e0da3 100644 --- a/workorders.lic +++ b/workorders.lic @@ -5,7 +5,7 @@ =end class WorkOrders - VERSION = '1.0.0'.freeze + VERSION = '1.0.2'.freeze # Pattern constants for game responses GIVE_LOGBOOK_SUCCESS_PATTERNS = [ @@ -78,8 +78,19 @@ class WorkOrders 'You must bundle and deliver \d+ more' ].freeze + # Full item capture must be used in bput — a shorter prefix match returns only the + # matched substring and drops the item name (item + remaining are often one line). + LOGBOOK_ITEM_PATTERN = /This logbook is tracking a work order requiring you to craft (?.+?) from any/i.freeze LOGBOOK_REMAINING_PATTERN = /You must bundle and deliver (?\d+) more/.freeze + RESUME_LOGBOOK_PATTERNS = [ + LOGBOOK_ITEM_PATTERN, + 'This work order appears to be complete.', + 'is not currently tracking', + 'has expired', + LOGBOOK_REMAINING_PATTERN + ].freeze + COUNT_PATTERN = /(?\d+)/.freeze POLISH_COUNT_PATTERN = /The surface polish has (?\d+) uses remaining/.freeze @@ -95,15 +106,16 @@ class WorkOrders [ { name: 'discipline', options: %w[blacksmithing weaponsmithing tailoring shaping carving remedies artificing], description: 'What type of workorder to do?' }, { name: 'repair', regex: /repair/i, optional: true, description: 'repair tools instead of crafting' }, - { name: 'turnin', regex: /turnin/i, optional: true, description: 'get work order and turn it in immediately with items already on hand' } + { name: 'turnin', regex: /turnin/i, optional: true, description: 'get work order and turn it in immediately with items already on hand' }, + { name: 'continue', regex: /continue/i, optional: true, description: 'resume an in-progress workorder from the logbook' } ] ] args = parse_args(arg_definitions) - work_order(args.discipline, args.repair, args.turnin) + work_order(args.discipline, args.repair, args.turnin, args.continue) end - def work_order(discipline, repair, turnin) + def work_order(discipline, repair, turnin, continue_order = nil) @settings = get_settings @worn_trashcan = @settings.worn_trashcan @worn_trashcan_verb = @settings.worn_trashcan_verb @@ -148,7 +160,12 @@ class WorkOrders exit end - unless repair + item = nil + quantity = nil + if continue_order + item_name, quantity = resume_work_order(info['logbook'], recipes) + item = recipes.find { |r| r['name'] == item_name } if item_name + elsif !repair if @settings.workorder_diff.is_a?(Hash) item_name, quantity = request_work_order(recipes, info['npc-rooms'], info['npc'], info['npc_last_name'], discipline, info['logbook'], @settings.workorder_diff[discipline]) else @@ -176,16 +193,18 @@ class WorkOrders @outfitting_room = @settings.outfitting_room tools = @settings.outfitting_tools @belt = @settings.outfitting_belt - case item['chapter'] - when 4, 2, 3 - craft_method = :sew_items - when 5 - materials_info = crafting_data['stock'][@workorders_materials['knit_type']] - craft_method = :knit_items - else - if item['chapter'] - Lich::Messaging.msg('bold', "WorkOrders: Unknown chapter for tailoring item: #{item}") - exit + if item + case item['chapter'] + when 4, 2, 3 + craft_method = :sew_items + when 5 + materials_info = crafting_data['stock'][@workorders_materials['knit_type']] + craft_method = :knit_items + else + if item['chapter'] + Lich::Messaging.msg('bold', "WorkOrders: Unknown chapter for tailoring item: #{item}") + exit + end end end when 'shaping' @@ -225,12 +244,18 @@ class WorkOrders return repair_items(info, tools) if repair + if continue_order && quantity.zero? + complete_work_order(info) + repair_items(info, tools) if @workorders_repair + return + end + if DRSkill.getxp(skill) > @craft_max_mindstate Lich::Messaging.msg('bold', "WorkOrders: Exiting because your current mindstate for #{skill} (#{DRSkill.getxp(skill)}) is over the set maximum craft_max_mindstate: #{@craft_max_mindstate}") exit end - if turnin + if turnin && !continue_order quantity.times do DRCI.get_item(item['noun'], @settings.default_container) bundle_item(item['noun'], info['logbook']) @@ -282,6 +307,60 @@ class WorkOrders nil end + # Resume an in-progress work order from the discipline logbook. + # @return [Array(String, Integer)] recipe name and remaining quantity (0 if complete) + def resume_work_order(logbook, recipes) + unless DRCI.get_item?("#{logbook} logbook") + Lich::Messaging.msg('bold', "WorkOrders: Failed to get #{logbook} logbook") + exit + end + + result = DRC.bput("read my #{logbook} logbook", *RESUME_LOGBOOK_PATTERNS) + + if result.include?('appears to be complete') + stow_tool('logbook') + Lich::Messaging.msg('plain', 'WorkOrders: Logbook order complete — turning in') + return [nil, 0] + end + + if result.include?('not currently tracking') || result.include?('has expired') + stow_tool('logbook') + Lich::Messaging.msg('bold', "WorkOrders: No in-progress work order in #{logbook} logbook") + exit + end + + match = result.match(LOGBOOK_ITEM_PATTERN) + unless match + stow_tool('logbook') + Lich::Messaging.msg('bold', "WorkOrders: Could not parse item from #{logbook} logbook") + exit + end + + item_desc = match[:item] + recipe = recipes.find { |r| r['name'] == item_desc } + unless recipe + stow_tool('logbook') + Lich::Messaging.msg('bold', "WorkOrders: Could not match logbook item '#{item_desc}' to a recipe") + exit + end + + remaining = logbook_remaining(logbook) + stow_tool('logbook') + + if remaining.nil? + Lich::Messaging.msg('bold', "WorkOrders: Could not read remaining count from #{logbook} logbook") + exit + end + + if remaining.zero? + Lich::Messaging.msg('plain', 'WorkOrders: Logbook order complete — turning in') + return [recipe['name'], 0] + end + + Lich::Messaging.msg('plain', "WorkOrders: Continuing work order for #{remaining} #{recipe['name']}") + [recipe['name'], remaining] + end + def get_tool(name) DRCC.get_crafting_item(name, @bag, @bag_items, @belt, true) end