From 7a295064db5d7dfd24ee1b33d350bd04457e4461 Mon Sep 17 00:00:00 2001 From: sizzlins Date: Tue, 21 Apr 2026 19:03:04 +0700 Subject: [PATCH 1/6] Add auto-barter to trade UI and global toggle to trade agreements --- caravan.lua | 1 + internal/caravan/trade.lua | 193 ++++++++++++++++++++++++++++ internal/caravan/tradeagreement.lua | 35 ++++- 3 files changed, 228 insertions(+), 1 deletion(-) diff --git a/caravan.lua b/caravan.lua index 0dd2cf6d8..a6e55d48b 100644 --- a/caravan.lua +++ b/caravan.lua @@ -24,6 +24,7 @@ OVERLAY_WIDGETS = { movegoods_hider=movegoods.MoveGoodsHiderOverlay, assigntrade=movegoods.AssignTradeOverlay, displayitemselector=pedestal.PedestalOverlay, + autobarter=trade.AutoBarterOverlay, } INTERESTING_FLAGS = { diff --git a/internal/caravan/trade.lua b/internal/caravan/trade.lua index f27e949ea..54bc19c49 100644 --- a/internal/caravan/trade.lua +++ b/internal/caravan/trade.lua @@ -19,6 +19,11 @@ handle_shift_click_on_render = handle_shift_click_on_render or false local trade = df.global.game.main_interface.trade +-- Auto Barter: configurable profit margin for the merchant. +-- 1.25 = offer 25% more value than the merchant's goods you want. +-- Increase if merchants keep rejecting. Decrease as your broker skill improves. +local TARGET_MARGIN = 1.25 + -- ------------------- -- Trade -- @@ -684,6 +689,163 @@ local function getSavedGoodflag(saved_state, k) return saved_state[k+1] end +-- ------------------- +-- Auto Barter +-- + +local function get_selected_merchant_value() + local total = 0 + local goodflags = trade.goodflag[0] + local in_selected_container = false + for item_idx, item in ipairs(trade.good[0]) do + local goodflag = goodflags[item_idx] + if not goodflag.contained then + in_selected_container = goodflag.selected + if goodflag.selected then + total = total + common.get_perceived_value(item, trade.mer) + end + else + if not in_selected_container and goodflag.selected then + total = total + common.get_perceived_value(item, trade.mer) + end + end + end + return total +end + +local function build_fort_selectable_units() + local banned_items = common.get_banned_items() + local risky_items = common.get_risky_items(banned_items) + local units = {} + local current_container = nil + + for item_idx, item in ipairs(trade.good[1]) do + local goodflag = trade.goodflag[1][item_idx] + + if goodflag.contained then + -- this item is inside a container; attach it to the current container unit + if current_container then + table.insert(current_container.contained_indices, item_idx) + -- check mandate on contained item + local is_banned, _ = common.scan_banned(item, risky_items) + if is_banned then + current_container.has_banned = true + end + end + else + -- flush previous container + current_container = nil + + local is_banned, _ = common.scan_banned(item, risky_items) + local is_container = df.item_binst:is_instance(item) + + local unit = { + item_idx = item_idx, + value = common.get_perceived_value(item, trade.mer), + is_container = is_container, + contained_indices = {}, + has_banned = is_banned, + } + + if is_container then + current_container = unit + end + + table.insert(units, unit) + end + end + + -- filter out banned units + local filtered = {} + for _, unit in ipairs(units) do + if not unit.has_banned then + table.insert(filtered, unit) + end + end + return filtered +end + +local function auto_barter(expensive_first) + local merchant_value = get_selected_merchant_value() + if merchant_value <= 0 then + dfhack.printerr('Auto Barter: Select merchant goods first!') + return + end + + local target_value = math.ceil(merchant_value * TARGET_MARGIN) + + -- deselect all fortress goods + for item_idx in ipairs(trade.good[1]) do + trade.goodflag[1][item_idx].selected = false + end + + -- build and sort selectable units + local units = build_fort_selectable_units() + if expensive_first then + table.sort(units, function(a, b) return a.value > b.value end) + else + table.sort(units, function(a, b) return a.value < b.value end) + end + + -- knapsack greedy selection with overshoot protection + local running_total = 0 + local selected_units = {} + + for _, unit in ipairs(units) do + if running_total >= target_value then break end + + local remaining = target_value - running_total + -- overshoot protection: if this unit's value is more than 2x what we + -- still need AND there are cheaper options ahead, skip it + if unit.value > remaining * 2 and remaining > 0 then + -- but if we have nothing selected yet, we must take something + if #selected_units > 0 then + goto continue + end + end + + running_total = running_total + unit.value + table.insert(selected_units, unit) + + ::continue:: + end + + -- if we overshot badly in the first pass, do a second pass to find + -- tighter fits from remaining items + if running_total < target_value then + for _, unit in ipairs(units) do + if running_total >= target_value then break end + -- check if already selected + local dominated = false + for _, sel in ipairs(selected_units) do + if sel.item_idx == unit.item_idx then + dominated = true + break + end + end + if not dominated then + running_total = running_total + unit.value + table.insert(selected_units, unit) + end + end + end + + -- apply selections to trade.goodflag + for _, unit in ipairs(selected_units) do + trade.goodflag[1][unit.item_idx].selected = true + -- if it's a container, also select all contained items + for _, cidx in ipairs(unit.contained_indices) do + trade.goodflag[1][cidx].selected = true + end + end + + local value_str = dfhack.formatInt(running_total) + local target_str = dfhack.formatInt(target_value) + local merchant_str = dfhack.formatInt(merchant_value) + print(('Auto Barter: Merchant goods=%s, Target=%s (x%.2f), Offering=%s (%d items)'):format( + merchant_str, target_str, TARGET_MARGIN, value_str, #selected_units)) +end + TradeOverlay = defclass(TradeOverlay, overlay.OverlayWidget) TradeOverlay.ATTRS{ desc='Adds convenience functions for working with bins to the trade screen.', @@ -801,6 +963,37 @@ function TradeBannerOverlay:onInput(keys) end end +-- ------------------- +-- AutoBarterOverlay +-- + +AutoBarterOverlay = defclass(AutoBarterOverlay, overlay.OverlayWidget) +AutoBarterOverlay.ATTRS{ + desc='Adds auto barter buttons to the trade screen.', + default_pos={x=-31,y=-5}, + default_enabled=true, + viewscreens='dwarfmode/Trade/Default', + frame={w=25, h=2}, + frame_background=gui.CLEAR_PEN, +} + +function AutoBarterOverlay:init() + self:addviews{ + widgets.TextButton{ + frame={t=0, l=0}, + label='Barter (expensive)', + key='CUSTOM_CTRL_E', + on_activate=function() auto_barter(true) end, + }, + widgets.TextButton{ + frame={t=1, l=0}, + label='Barter (cheap)', + key='CUSTOM_CTRL_W', + on_activate=function() auto_barter(false) end, + }, + } +end + -- ------------------- -- Ethics -- diff --git a/internal/caravan/tradeagreement.lua b/internal/caravan/tradeagreement.lua index f71467025..effb6a5bb 100644 --- a/internal/caravan/tradeagreement.lua +++ b/internal/caravan/tradeagreement.lua @@ -12,7 +12,7 @@ TradeAgreementOverlay.ATTRS{ default_pos={x=45, y=-6}, default_enabled=true, viewscreens='dwarfmode/Diplomacy/Requests', - frame={w=25, h=4}, + frame={w=35, h=5}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, } @@ -71,6 +71,31 @@ local function diplomacy_toggle_cat() end end +local function diplomacy_toggle_all_cats() + local target_val = 4 + local all_selected = true + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + for i in ipairs(priority) do + if priority[i] ~= 4 then + all_selected = false + break + end + end + if not all_selected then break end + end + if all_selected then + target_val = 0 + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + for i in ipairs(priority) do + priority[i] = target_val + end + end +end + local function select_by_value(prices, val) local priority = get_cur_priority_list() for i in ipairs(priority) do @@ -92,6 +117,14 @@ function TradeAgreementOverlay:init() self:addviews{ widgets.HotkeyLabel{ frame={t=1, l=0}, + label='Select globally', + key='CUSTOM_SHIFT_A', + on_activate=diplomacy_toggle_all_cats, + }, + } + self:addviews{ + widgets.HotkeyLabel{ + frame={t=2, l=0}, label='Select by value', key='CUSTOM_CTRL_M', on_activate=self:callback('select_by_value'), From 21707cda011277946cbf2e5a69f91963f38e776a Mon Sep 17 00:00:00 2001 From: sizzlins Date: Tue, 21 Apr 2026 19:24:09 +0700 Subject: [PATCH 2/6] Update docs to describe autobarter and global trade agreement select --- docs/caravan.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/caravan.rst b/docs/caravan.rst index 17cb2ed07..8835081f2 100644 --- a/docs/caravan.rst +++ b/docs/caravan.rst @@ -133,6 +133,13 @@ are trading with. Clicking on the badge will show a list of problematic items, and you can click the button on the dialog to deselect all the problematic items in your trade list. +**caravan.autobarter** + +This overlay provides "Barter (expensive)" and "Barter (cheap)" buttons on the trade +screen. These buttons automatically select fortress goods to trade that total slightly +more than the value of the merchant goods you have selected. You can choose whether to +fill the quota with your most expensive items or your cheapest items. + Trade agreements ```````````````` @@ -141,6 +148,7 @@ Trade agreements This adds a small panel with some useful shortcuts: * ``Ctrl-a`` for selecting all/none in the currently shown category. +* ``Shift-Ctrl-a`` for selecting all/none across all item categories globally. * ``Ctrl-m`` for selecting items with specific base material price (only enabled for item categories where this matters, like gems and leather). From 24fc159191f4290933289539e707c1c8c0cce2c8 Mon Sep 17 00:00:00 2001 From: sizzlins Date: Thu, 23 Apr 2026 12:06:32 +0700 Subject: [PATCH 3/6] feat: Add extensive Save/Load and 'Select by Weight/Value' features to trade agreements - Implements persistent, per-civ trade agreement saving/loading - Adds 'Select by Weight' and 'Select by Value' sorting for Boulders, Metal, Cheese, Crafts, Mugs, Glass, Silk, and almost all other raw materials. - Includes numerous defensive nil-checks for safe memory parsing. --- internal/caravan/tradeagreement.lua | 352 ++++++++++++++++++++++++++-- 1 file changed, 336 insertions(+), 16 deletions(-) diff --git a/internal/caravan/tradeagreement.lua b/internal/caravan/tradeagreement.lua index effb6a5bb..149614f78 100644 --- a/internal/caravan/tradeagreement.lua +++ b/internal/caravan/tradeagreement.lua @@ -3,6 +3,7 @@ local dlg = require('gui.dialogs') local gui = require('gui') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') +local json = require('json') local diplomacy = df.global.game.main_interface.diplomacy @@ -12,7 +13,7 @@ TradeAgreementOverlay.ATTRS{ default_pos={x=45, y=-6}, default_enabled=true, viewscreens='dwarfmode/Diplomacy/Requests', - frame={w=35, h=5}, + frame={w=58, h=7}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, } @@ -26,7 +27,13 @@ local function transform_mat_list(matList) end local function decode_mat_list(mat) - return dfhack.matinfo.decode(mat.type, mat.index).material.material_value + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.material_value or 0 +end + +local function decode_mat_weight(mat) + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.solid_density or 0 end local select_by_value_tab = { @@ -46,9 +53,197 @@ local select_by_value_tab = { get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, decode=decode_mat_list, }, + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.material_value end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_list, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_list, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_list, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_list, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_list, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_list, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_list, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_list, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_list, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_list, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_list, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, } select_by_value_tab.LargeCutGems = select_by_value_tab.SmallCutGems +local select_by_weight_tab = { + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.solid_density end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_weight, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_weight, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_weight, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_weight, + }, + Meat={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, + decode=decode_mat_weight, + }, + Leather={ + get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, + decode=decode_mat_weight, + }, + Parchment={ + get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, + decode=decode_mat_weight, + }, + SmallCutGems={ + get_mats=function(resources) return resources.gems end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_weight, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_weight, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_weight, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_weight, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_weight, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_weight, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_weight, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, +} +select_by_weight_tab.LargeCutGems = select_by_weight_tab.SmallCutGems + local function get_cur_tab_category() return diplomacy.taking_requests_tablist[diplomacy.taking_requests_selected_tab] end @@ -58,13 +253,18 @@ local function get_select_by_value_tab(category) return select_by_value_tab[df.entity_sell_category[category]] end +local function get_select_by_weight_tab(category) + category = category or get_cur_tab_category() + return select_by_weight_tab[df.entity_sell_category[category]] +end + local function get_cur_priority_list() return diplomacy.environment.dipev.sell_requests.priority[get_cur_tab_category()] end local function diplomacy_toggle_cat() local priority = get_cur_priority_list() - if #priority == 0 then return end + if not priority or #priority == 0 then return end local target_val = priority[0] == 0 and 4 or 0 for i in ipairs(priority) do priority[i] = target_val @@ -76,10 +276,12 @@ local function diplomacy_toggle_all_cats() local all_selected = true for _, cat in ipairs(diplomacy.taking_requests_tablist) do local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - for i in ipairs(priority) do - if priority[i] ~= 4 then - all_selected = false - break + if priority then + for i in ipairs(priority) do + if priority[i] ~= 4 then + all_selected = false + break + end end end if not all_selected then break end @@ -90,14 +292,17 @@ local function diplomacy_toggle_all_cats() for _, cat in ipairs(diplomacy.taking_requests_tablist) do local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - for i in ipairs(priority) do - priority[i] = target_val + if priority then + for i in ipairs(priority) do + priority[i] = target_val + end end end end local function select_by_value(prices, val) local priority = get_cur_priority_list() + if not priority then return end for i in ipairs(priority) do if prices[i] == val then priority[i] = 4 @@ -105,36 +310,132 @@ local function select_by_value(prices, val) end end +local function get_civ_key() + local civ = df.historical_entity.find(diplomacy.actor.civ_id) + if not civ then return 'UNKNOWN' end + local name = dfhack.translation.translateName(civ.name) + local race = df.creature_raw.find(civ.race) + local race_name = race and race.name[0] or 'Unknown' + -- Save by race+name to distinguish different dwarven/elven civs, or just race + -- "MOUNTAIN" is the raw id, but race_name is "dwarf". We'll use civ ID to be safe, + -- or race_name to make it portable. Let's use race_name + civ_id to prevent clashes, + -- but actually the user asked for different civilizations. Let's key by the civ's translated name. + -- Better yet, key by the English translated name to make it readable in the JSON. + return dfhack.translation.translateName(civ.name, true) +end + +local CONFIG_FILE = 'dfhack-config/trade-agreements.json' + +local function save_requests() + local key = get_civ_key() + local data = {} + if dfhack.filesystem.isfile(CONFIG_FILE) then + data = json.decode_file(CONFIG_FILE) or {} + end + + local civ_data = data[key] or {} + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = {} + local has_requests = false + for i in ipairs(priority) do + if priority[i] ~= 0 then + saved_priority[tostring(i)] = priority[i] + has_requests = true + end + end + if has_requests then + civ_data[cat_name] = saved_priority + else + civ_data[cat_name] = nil + end + end + end + + data[key] = civ_data + json.encode_file(data, CONFIG_FILE, {pretty=true}) + dfhack.gui.showAnnouncement('Trade requests saved for ' .. key, COLOR_GREEN) +end + +local function load_requests() + if not dfhack.filesystem.isfile(CONFIG_FILE) then + dfhack.gui.showAnnouncement('No saved trade agreements found.', COLOR_RED) + return + end + + local data = json.decode_file(CONFIG_FILE) + if not data then return end + + local key = get_civ_key() + local civ_data = data[key] + + if not civ_data then + dfhack.gui.showAnnouncement('No saved requests found for ' .. key, COLOR_YELLOW) + return + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = civ_data[cat_name] or {} + for i in ipairs(priority) do + priority[i] = saved_priority[tostring(i)] or 0 + end + end + end + + dfhack.gui.showAnnouncement('Trade requests loaded for ' .. key, COLOR_GREEN) +end + function TradeAgreementOverlay:init() self:addviews{ widgets.HotkeyLabel{ - frame={t=0, l=0}, + frame={t=0, l=0, w=23}, label='Select all/none', key='CUSTOM_CTRL_A', on_activate=diplomacy_toggle_cat, }, - } - self:addviews{ widgets.HotkeyLabel{ - frame={t=1, l=0}, + frame={t=1, l=0, w=23}, label='Select globally', key='CUSTOM_SHIFT_A', on_activate=diplomacy_toggle_all_cats, }, - } - self:addviews{ widgets.HotkeyLabel{ - frame={t=2, l=0}, + frame={t=2, l=0, w=23}, label='Select by value', key='CUSTOM_CTRL_M', on_activate=self:callback('select_by_value'), enabled=get_select_by_value_tab, }, + widgets.HotkeyLabel{ + frame={t=0, l=24, w=23}, + label='Save requests', + key='CUSTOM_CTRL_S', + on_activate=save_requests, + }, + widgets.HotkeyLabel{ + frame={t=1, l=24, w=23}, + label='Load requests', + key='CUSTOM_CTRL_L', + on_activate=load_requests, + }, + widgets.HotkeyLabel{ + frame={t=2, l=24, w=23}, + label='Select by weight', + key='CUSTOM_CTRL_W', + on_activate=self:callback('select_by_weight'), + enabled=get_select_by_weight_tab, + }, } end local function get_prices(tab) local resource = tab.get_mats(df.historical_entity.find(diplomacy.actor.civ_id).resources) + if not resource then return {}, {} end local prices = {} local matValuesUnique = {} local filter = {} @@ -172,3 +473,22 @@ function TradeAgreementOverlay:select_by_value() function(id) select_by_value(prices, matValuesUnique[id].value) end ) end + +function TradeAgreementOverlay:select_by_weight() + local cat = get_cur_tab_category() + local cur_tab = get_select_by_weight_tab(cat) + + local resource_name = df.entity_sell_category[cat] + local prices, matValuesUnique = get_prices(cur_tab) + local list = {} + for index, value in ipairs(matValuesUnique) do + list[index] = ('%4d (%d type%s of %s)'):format( + value.value, value.count, value.count == 1 and '' or 's', resource_name:lower()) + end + dlg.showListPrompt( + "Select materials with solid density", "", + COLOR_WHITE, + list, + function(id) select_by_value(prices, matValuesUnique[id].value) end + ) +end From 21ae04fc39aa217fd567e4aa3cafccb96eb5c518 Mon Sep 17 00:00:00 2001 From: sizzlins Date: Thu, 23 Apr 2026 12:11:19 +0700 Subject: [PATCH 4/6] style: Fix trailing whitespace --- internal/caravan/tradeagreement.lua | 988 ++++++++++++++-------------- 1 file changed, 494 insertions(+), 494 deletions(-) diff --git a/internal/caravan/tradeagreement.lua b/internal/caravan/tradeagreement.lua index 149614f78..adafc0f15 100644 --- a/internal/caravan/tradeagreement.lua +++ b/internal/caravan/tradeagreement.lua @@ -1,494 +1,494 @@ ---@ module = true -local dlg = require('gui.dialogs') -local gui = require('gui') -local overlay = require('plugins.overlay') -local widgets = require('gui.widgets') -local json = require('json') - -local diplomacy = df.global.game.main_interface.diplomacy - -TradeAgreementOverlay = defclass(TradeAgreementOverlay, overlay.OverlayWidget) -TradeAgreementOverlay.ATTRS{ - desc='Adds quick toggles for groups of trade agreement items.', - default_pos={x=45, y=-6}, - default_enabled=true, - viewscreens='dwarfmode/Diplomacy/Requests', - frame={w=58, h=7}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, -} - -local function transform_mat_list(matList) - local list = {} - for key, value in pairs(matList.mat_index) do - list[key] = {type=matList.mat_type[key], index=value} - end - return list -end - -local function decode_mat_list(mat) - local minfo = dfhack.matinfo.decode(mat.type, mat.index) - return minfo and minfo.material.material_value or 0 -end - -local function decode_mat_weight(mat) - local minfo = dfhack.matinfo.decode(mat.type, mat.index) - return minfo and minfo.material.solid_density or 0 -end - -local select_by_value_tab = { - Leather={ - get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, - decode=decode_mat_list, - }, - SmallCutGems={ - get_mats=function(resources) return resources.gems end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Meat={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, - decode=decode_mat_list, - }, - Parchment={ - get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, - decode=decode_mat_list, - }, - Stone={ - get_mats=function(resources) return resources.stones end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Wood={ - get_mats=function(resources) return resources.wood_products end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.material_value end, - }, - MetalBars={ - get_mats=function(resources) return resources.metals end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Cheese={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, - decode=decode_mat_list, - }, - Powders={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, - decode=decode_mat_list, - }, - Extracts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, - decode=decode_mat_list, - }, - Drinks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, - decode=decode_mat_list, - }, - CupsMugsGoblets={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_list, - }, - Crafts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_list, - }, - FlasksWaterskins={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, - decode=decode_mat_list, - }, - Quivers={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, - decode=decode_mat_list, - }, - Backpacks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, - decode=decode_mat_list, - }, - Barrels={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, - decode=decode_mat_list, - }, - Sand={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, - decode=decode_mat_list, - }, - Glass={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, - decode=decode_mat_list, - }, - Clay={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, - decode=decode_mat_list, - }, - ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - Plants={ - get_mats=function(resources) return resources.plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, - }, - GardenVegetables={ - get_mats=function(resources) return resources.shrub_fruit_plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, - }, -} -select_by_value_tab.LargeCutGems = select_by_value_tab.SmallCutGems - -local select_by_weight_tab = { - Stone={ - get_mats=function(resources) return resources.stones end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - Wood={ - get_mats=function(resources) return resources.wood_products end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.solid_density end, - }, - MetalBars={ - get_mats=function(resources) return resources.metals end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - Cheese={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, - decode=decode_mat_weight, - }, - Powders={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, - decode=decode_mat_weight, - }, - Extracts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, - decode=decode_mat_weight, - }, - Drinks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, - decode=decode_mat_weight, - }, - Meat={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, - decode=decode_mat_weight, - }, - Leather={ - get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, - decode=decode_mat_weight, - }, - Parchment={ - get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, - decode=decode_mat_weight, - }, - SmallCutGems={ - get_mats=function(resources) return resources.gems end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - CupsMugsGoblets={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_weight, - }, - Crafts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_weight, - }, - FlasksWaterskins={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, - decode=decode_mat_weight, - }, - Quivers={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, - decode=decode_mat_weight, - }, - Backpacks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, - decode=decode_mat_weight, - }, - Barrels={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, - decode=decode_mat_weight, - }, - Sand={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, - decode=decode_mat_weight, - }, - Glass={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, - decode=decode_mat_weight, - }, - Clay={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, - decode=decode_mat_weight, - }, - ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - Plants={ - get_mats=function(resources) return resources.plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, - }, - GardenVegetables={ - get_mats=function(resources) return resources.shrub_fruit_plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, - }, -} -select_by_weight_tab.LargeCutGems = select_by_weight_tab.SmallCutGems - -local function get_cur_tab_category() - return diplomacy.taking_requests_tablist[diplomacy.taking_requests_selected_tab] -end - -local function get_select_by_value_tab(category) - category = category or get_cur_tab_category() - return select_by_value_tab[df.entity_sell_category[category]] -end - -local function get_select_by_weight_tab(category) - category = category or get_cur_tab_category() - return select_by_weight_tab[df.entity_sell_category[category]] -end - -local function get_cur_priority_list() - return diplomacy.environment.dipev.sell_requests.priority[get_cur_tab_category()] -end - -local function diplomacy_toggle_cat() - local priority = get_cur_priority_list() - if not priority or #priority == 0 then return end - local target_val = priority[0] == 0 and 4 or 0 - for i in ipairs(priority) do - priority[i] = target_val - end -end - -local function diplomacy_toggle_all_cats() - local target_val = 4 - local all_selected = true - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - for i in ipairs(priority) do - if priority[i] ~= 4 then - all_selected = false - break - end - end - end - if not all_selected then break end - end - if all_selected then - target_val = 0 - end - - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - for i in ipairs(priority) do - priority[i] = target_val - end - end - end -end - -local function select_by_value(prices, val) - local priority = get_cur_priority_list() - if not priority then return end - for i in ipairs(priority) do - if prices[i] == val then - priority[i] = 4 - end - end -end - -local function get_civ_key() - local civ = df.historical_entity.find(diplomacy.actor.civ_id) - if not civ then return 'UNKNOWN' end - local name = dfhack.translation.translateName(civ.name) - local race = df.creature_raw.find(civ.race) - local race_name = race and race.name[0] or 'Unknown' - -- Save by race+name to distinguish different dwarven/elven civs, or just race - -- "MOUNTAIN" is the raw id, but race_name is "dwarf". We'll use civ ID to be safe, - -- or race_name to make it portable. Let's use race_name + civ_id to prevent clashes, - -- but actually the user asked for different civilizations. Let's key by the civ's translated name. - -- Better yet, key by the English translated name to make it readable in the JSON. - return dfhack.translation.translateName(civ.name, true) -end - -local CONFIG_FILE = 'dfhack-config/trade-agreements.json' - -local function save_requests() - local key = get_civ_key() - local data = {} - if dfhack.filesystem.isfile(CONFIG_FILE) then - data = json.decode_file(CONFIG_FILE) or {} - end - - local civ_data = data[key] or {} - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local cat_name = df.entity_sell_category[cat] - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - local saved_priority = {} - local has_requests = false - for i in ipairs(priority) do - if priority[i] ~= 0 then - saved_priority[tostring(i)] = priority[i] - has_requests = true - end - end - if has_requests then - civ_data[cat_name] = saved_priority - else - civ_data[cat_name] = nil - end - end - end - - data[key] = civ_data - json.encode_file(data, CONFIG_FILE, {pretty=true}) - dfhack.gui.showAnnouncement('Trade requests saved for ' .. key, COLOR_GREEN) -end - -local function load_requests() - if not dfhack.filesystem.isfile(CONFIG_FILE) then - dfhack.gui.showAnnouncement('No saved trade agreements found.', COLOR_RED) - return - end - - local data = json.decode_file(CONFIG_FILE) - if not data then return end - - local key = get_civ_key() - local civ_data = data[key] - - if not civ_data then - dfhack.gui.showAnnouncement('No saved requests found for ' .. key, COLOR_YELLOW) - return - end - - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local cat_name = df.entity_sell_category[cat] - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - local saved_priority = civ_data[cat_name] or {} - for i in ipairs(priority) do - priority[i] = saved_priority[tostring(i)] or 0 - end - end - end - - dfhack.gui.showAnnouncement('Trade requests loaded for ' .. key, COLOR_GREEN) -end - -function TradeAgreementOverlay:init() - self:addviews{ - widgets.HotkeyLabel{ - frame={t=0, l=0, w=23}, - label='Select all/none', - key='CUSTOM_CTRL_A', - on_activate=diplomacy_toggle_cat, - }, - widgets.HotkeyLabel{ - frame={t=1, l=0, w=23}, - label='Select globally', - key='CUSTOM_SHIFT_A', - on_activate=diplomacy_toggle_all_cats, - }, - widgets.HotkeyLabel{ - frame={t=2, l=0, w=23}, - label='Select by value', - key='CUSTOM_CTRL_M', - on_activate=self:callback('select_by_value'), - enabled=get_select_by_value_tab, - }, - widgets.HotkeyLabel{ - frame={t=0, l=24, w=23}, - label='Save requests', - key='CUSTOM_CTRL_S', - on_activate=save_requests, - }, - widgets.HotkeyLabel{ - frame={t=1, l=24, w=23}, - label='Load requests', - key='CUSTOM_CTRL_L', - on_activate=load_requests, - }, - widgets.HotkeyLabel{ - frame={t=2, l=24, w=23}, - label='Select by weight', - key='CUSTOM_CTRL_W', - on_activate=self:callback('select_by_weight'), - enabled=get_select_by_weight_tab, - }, - } -end - -local function get_prices(tab) - local resource = tab.get_mats(df.historical_entity.find(diplomacy.actor.civ_id).resources) - if not resource then return {}, {} end - local prices = {} - local matValuesUnique = {} - local filter = {} - for civid, matid in pairs(resource) do - local price = tab.decode(matid) - prices[civid] = price - if not filter[price] then - local val = {value=price, count=1} - filter[price] = val - table.insert(matValuesUnique, val) - else - filter[price].count = filter[price].count + 1 - end - end - table.sort(matValuesUnique, function(a, b) return a.value < b.value end) - return prices, matValuesUnique -end - -function TradeAgreementOverlay:select_by_value() - local cat = get_cur_tab_category() - local cur_tab = get_select_by_value_tab(cat) - - local resource_name = df.entity_sell_category[cat] - if resource_name:endswith('Gems') then resource_name = 'Gem' end - local prices, matValuesUnique = get_prices(cur_tab) - local list = {} - for index, value in ipairs(matValuesUnique) do - list[index] = ('%4d%s (%d type%s of %s)'):format( - value.value, string.char(15), value.count, value.count == 1 and '' or 's', resource_name:lower()) - end - dlg.showListPrompt( - "Select materials with base value", "", - COLOR_WHITE, - list, - function(id) select_by_value(prices, matValuesUnique[id].value) end - ) -end - -function TradeAgreementOverlay:select_by_weight() - local cat = get_cur_tab_category() - local cur_tab = get_select_by_weight_tab(cat) - - local resource_name = df.entity_sell_category[cat] - local prices, matValuesUnique = get_prices(cur_tab) - local list = {} - for index, value in ipairs(matValuesUnique) do - list[index] = ('%4d (%d type%s of %s)'):format( - value.value, value.count, value.count == 1 and '' or 's', resource_name:lower()) - end - dlg.showListPrompt( - "Select materials with solid density", "", - COLOR_WHITE, - list, - function(id) select_by_value(prices, matValuesUnique[id].value) end - ) -end +--@ module = true +local dlg = require('gui.dialogs') +local gui = require('gui') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') +local json = require('json') + +local diplomacy = df.global.game.main_interface.diplomacy + +TradeAgreementOverlay = defclass(TradeAgreementOverlay, overlay.OverlayWidget) +TradeAgreementOverlay.ATTRS{ + desc='Adds quick toggles for groups of trade agreement items.', + default_pos={x=45, y=-6}, + default_enabled=true, + viewscreens='dwarfmode/Diplomacy/Requests', + frame={w=58, h=7}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, +} + +local function transform_mat_list(matList) + local list = {} + for key, value in pairs(matList.mat_index) do + list[key] = {type=matList.mat_type[key], index=value} + end + return list +end + +local function decode_mat_list(mat) + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.material_value or 0 +end + +local function decode_mat_weight(mat) + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.solid_density or 0 +end + +local select_by_value_tab = { + Leather={ + get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, + decode=decode_mat_list, + }, + SmallCutGems={ + get_mats=function(resources) return resources.gems end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Meat={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, + decode=decode_mat_list, + }, + Parchment={ + get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, + decode=decode_mat_list, + }, + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.material_value end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_list, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_list, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_list, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_list, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_list, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_list, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_list, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_list, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_list, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_list, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_list, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, +} +select_by_value_tab.LargeCutGems = select_by_value_tab.SmallCutGems + +local select_by_weight_tab = { + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.solid_density end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_weight, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_weight, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_weight, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_weight, + }, + Meat={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, + decode=decode_mat_weight, + }, + Leather={ + get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, + decode=decode_mat_weight, + }, + Parchment={ + get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, + decode=decode_mat_weight, + }, + SmallCutGems={ + get_mats=function(resources) return resources.gems end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_weight, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_weight, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_weight, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_weight, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_weight, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_weight, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_weight, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, +} +select_by_weight_tab.LargeCutGems = select_by_weight_tab.SmallCutGems + +local function get_cur_tab_category() + return diplomacy.taking_requests_tablist[diplomacy.taking_requests_selected_tab] +end + +local function get_select_by_value_tab(category) + category = category or get_cur_tab_category() + return select_by_value_tab[df.entity_sell_category[category]] +end + +local function get_select_by_weight_tab(category) + category = category or get_cur_tab_category() + return select_by_weight_tab[df.entity_sell_category[category]] +end + +local function get_cur_priority_list() + return diplomacy.environment.dipev.sell_requests.priority[get_cur_tab_category()] +end + +local function diplomacy_toggle_cat() + local priority = get_cur_priority_list() + if not priority or #priority == 0 then return end + local target_val = priority[0] == 0 and 4 or 0 + for i in ipairs(priority) do + priority[i] = target_val + end +end + +local function diplomacy_toggle_all_cats() + local target_val = 4 + local all_selected = true + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + for i in ipairs(priority) do + if priority[i] ~= 4 then + all_selected = false + break + end + end + end + if not all_selected then break end + end + if all_selected then + target_val = 0 + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + for i in ipairs(priority) do + priority[i] = target_val + end + end + end +end + +local function select_by_value(prices, val) + local priority = get_cur_priority_list() + if not priority then return end + for i in ipairs(priority) do + if prices[i] == val then + priority[i] = 4 + end + end +end + +local function get_civ_key() + local civ = df.historical_entity.find(diplomacy.actor.civ_id) + if not civ then return 'UNKNOWN' end + local name = dfhack.translation.translateName(civ.name) + local race = df.creature_raw.find(civ.race) + local race_name = race and race.name[0] or 'Unknown' + -- Save by race+name to distinguish different dwarven/elven civs, or just race + -- "MOUNTAIN" is the raw id, but race_name is "dwarf". We'll use civ ID to be safe, + -- or race_name to make it portable. Let's use race_name + civ_id to prevent clashes, + -- but actually the user asked for different civilizations. Let's key by the civ's translated name. + -- Better yet, key by the English translated name to make it readable in the JSON. + return dfhack.translation.translateName(civ.name, true) +end + +local CONFIG_FILE = 'dfhack-config/trade-agreements.json' + +local function save_requests() + local key = get_civ_key() + local data = {} + if dfhack.filesystem.isfile(CONFIG_FILE) then + data = json.decode_file(CONFIG_FILE) or {} + end + + local civ_data = data[key] or {} + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = {} + local has_requests = false + for i in ipairs(priority) do + if priority[i] ~= 0 then + saved_priority[tostring(i)] = priority[i] + has_requests = true + end + end + if has_requests then + civ_data[cat_name] = saved_priority + else + civ_data[cat_name] = nil + end + end + end + + data[key] = civ_data + json.encode_file(data, CONFIG_FILE, {pretty=true}) + dfhack.gui.showAnnouncement('Trade requests saved for ' .. key, COLOR_GREEN) +end + +local function load_requests() + if not dfhack.filesystem.isfile(CONFIG_FILE) then + dfhack.gui.showAnnouncement('No saved trade agreements found.', COLOR_RED) + return + end + + local data = json.decode_file(CONFIG_FILE) + if not data then return end + + local key = get_civ_key() + local civ_data = data[key] + + if not civ_data then + dfhack.gui.showAnnouncement('No saved requests found for ' .. key, COLOR_YELLOW) + return + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = civ_data[cat_name] or {} + for i in ipairs(priority) do + priority[i] = saved_priority[tostring(i)] or 0 + end + end + end + + dfhack.gui.showAnnouncement('Trade requests loaded for ' .. key, COLOR_GREEN) +end + +function TradeAgreementOverlay:init() + self:addviews{ + widgets.HotkeyLabel{ + frame={t=0, l=0, w=23}, + label='Select all/none', + key='CUSTOM_CTRL_A', + on_activate=diplomacy_toggle_cat, + }, + widgets.HotkeyLabel{ + frame={t=1, l=0, w=23}, + label='Select globally', + key='CUSTOM_SHIFT_A', + on_activate=diplomacy_toggle_all_cats, + }, + widgets.HotkeyLabel{ + frame={t=2, l=0, w=23}, + label='Select by value', + key='CUSTOM_CTRL_M', + on_activate=self:callback('select_by_value'), + enabled=get_select_by_value_tab, + }, + widgets.HotkeyLabel{ + frame={t=0, l=24, w=23}, + label='Save requests', + key='CUSTOM_CTRL_S', + on_activate=save_requests, + }, + widgets.HotkeyLabel{ + frame={t=1, l=24, w=23}, + label='Load requests', + key='CUSTOM_CTRL_L', + on_activate=load_requests, + }, + widgets.HotkeyLabel{ + frame={t=2, l=24, w=23}, + label='Select by weight', + key='CUSTOM_CTRL_W', + on_activate=self:callback('select_by_weight'), + enabled=get_select_by_weight_tab, + }, + } +end + +local function get_prices(tab) + local resource = tab.get_mats(df.historical_entity.find(diplomacy.actor.civ_id).resources) + if not resource then return {}, {} end + local prices = {} + local matValuesUnique = {} + local filter = {} + for civid, matid in pairs(resource) do + local price = tab.decode(matid) + prices[civid] = price + if not filter[price] then + local val = {value=price, count=1} + filter[price] = val + table.insert(matValuesUnique, val) + else + filter[price].count = filter[price].count + 1 + end + end + table.sort(matValuesUnique, function(a, b) return a.value < b.value end) + return prices, matValuesUnique +end + +function TradeAgreementOverlay:select_by_value() + local cat = get_cur_tab_category() + local cur_tab = get_select_by_value_tab(cat) + + local resource_name = df.entity_sell_category[cat] + if resource_name:endswith('Gems') then resource_name = 'Gem' end + local prices, matValuesUnique = get_prices(cur_tab) + local list = {} + for index, value in ipairs(matValuesUnique) do + list[index] = ('%4d%s (%d type%s of %s)'):format( + value.value, string.char(15), value.count, value.count == 1 and '' or 's', resource_name:lower()) + end + dlg.showListPrompt( + "Select materials with base value", "", + COLOR_WHITE, + list, + function(id) select_by_value(prices, matValuesUnique[id].value) end + ) +end + +function TradeAgreementOverlay:select_by_weight() + local cat = get_cur_tab_category() + local cur_tab = get_select_by_weight_tab(cat) + + local resource_name = df.entity_sell_category[cat] + local prices, matValuesUnique = get_prices(cur_tab) + local list = {} + for index, value in ipairs(matValuesUnique) do + list[index] = ('%4d (%d type%s of %s)'):format( + value.value, value.count, value.count == 1 and '' or 's', resource_name:lower()) + end + dlg.showListPrompt( + "Select materials with solid density", "", + COLOR_WHITE, + list, + function(id) select_by_value(prices, matValuesUnique[id].value) end + ) +end From a0b183beab5688663be33e95dff4fccf71b172ca Mon Sep 17 00:00:00 2001 From: sizzlins Date: Thu, 23 Apr 2026 12:19:15 +0700 Subject: [PATCH 5/6] style: Fix mixed line endings --- internal/caravan/tradeagreement.lua | 988 ++++++++++++++-------------- 1 file changed, 494 insertions(+), 494 deletions(-) diff --git a/internal/caravan/tradeagreement.lua b/internal/caravan/tradeagreement.lua index adafc0f15..7f06580ca 100644 --- a/internal/caravan/tradeagreement.lua +++ b/internal/caravan/tradeagreement.lua @@ -1,494 +1,494 @@ ---@ module = true -local dlg = require('gui.dialogs') -local gui = require('gui') -local overlay = require('plugins.overlay') -local widgets = require('gui.widgets') -local json = require('json') - -local diplomacy = df.global.game.main_interface.diplomacy - -TradeAgreementOverlay = defclass(TradeAgreementOverlay, overlay.OverlayWidget) -TradeAgreementOverlay.ATTRS{ - desc='Adds quick toggles for groups of trade agreement items.', - default_pos={x=45, y=-6}, - default_enabled=true, - viewscreens='dwarfmode/Diplomacy/Requests', - frame={w=58, h=7}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, -} - -local function transform_mat_list(matList) - local list = {} - for key, value in pairs(matList.mat_index) do - list[key] = {type=matList.mat_type[key], index=value} - end - return list -end - -local function decode_mat_list(mat) - local minfo = dfhack.matinfo.decode(mat.type, mat.index) - return minfo and minfo.material.material_value or 0 -end - -local function decode_mat_weight(mat) - local minfo = dfhack.matinfo.decode(mat.type, mat.index) - return minfo and minfo.material.solid_density or 0 -end - -local select_by_value_tab = { - Leather={ - get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, - decode=decode_mat_list, - }, - SmallCutGems={ - get_mats=function(resources) return resources.gems end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Meat={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, - decode=decode_mat_list, - }, - Parchment={ - get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, - decode=decode_mat_list, - }, - Stone={ - get_mats=function(resources) return resources.stones end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Wood={ - get_mats=function(resources) return resources.wood_products end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.material_value end, - }, - MetalBars={ - get_mats=function(resources) return resources.metals end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, - }, - Cheese={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, - decode=decode_mat_list, - }, - Powders={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, - decode=decode_mat_list, - }, - Extracts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, - decode=decode_mat_list, - }, - Drinks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, - decode=decode_mat_list, - }, - CupsMugsGoblets={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_list, - }, - Crafts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_list, - }, - FlasksWaterskins={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, - decode=decode_mat_list, - }, - Quivers={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, - decode=decode_mat_list, - }, - Backpacks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, - decode=decode_mat_list, - }, - Barrels={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, - decode=decode_mat_list, - }, - Sand={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, - decode=decode_mat_list, - }, - Glass={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, - decode=decode_mat_list, - }, - Clay={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, - decode=decode_mat_list, - }, - ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, - ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, - ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, - Plants={ - get_mats=function(resources) return resources.plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, - }, - GardenVegetables={ - get_mats=function(resources) return resources.shrub_fruit_plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, - }, -} -select_by_value_tab.LargeCutGems = select_by_value_tab.SmallCutGems - -local select_by_weight_tab = { - Stone={ - get_mats=function(resources) return resources.stones end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - Wood={ - get_mats=function(resources) return resources.wood_products end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.solid_density end, - }, - MetalBars={ - get_mats=function(resources) return resources.metals end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - Cheese={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, - decode=decode_mat_weight, - }, - Powders={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, - decode=decode_mat_weight, - }, - Extracts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, - decode=decode_mat_weight, - }, - Drinks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, - decode=decode_mat_weight, - }, - Meat={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, - decode=decode_mat_weight, - }, - Leather={ - get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, - decode=decode_mat_weight, - }, - Parchment={ - get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, - decode=decode_mat_weight, - }, - SmallCutGems={ - get_mats=function(resources) return resources.gems end, - decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, - }, - CupsMugsGoblets={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_weight, - }, - Crafts={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, - decode=decode_mat_weight, - }, - FlasksWaterskins={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, - decode=decode_mat_weight, - }, - Quivers={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, - decode=decode_mat_weight, - }, - Backpacks={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, - decode=decode_mat_weight, - }, - Barrels={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, - decode=decode_mat_weight, - }, - Sand={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, - decode=decode_mat_weight, - }, - Glass={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, - decode=decode_mat_weight, - }, - Clay={ - get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, - decode=decode_mat_weight, - }, - ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, - ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, - ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, - Plants={ - get_mats=function(resources) return resources.plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, - }, - GardenVegetables={ - get_mats=function(resources) return resources.shrub_fruit_plants end, - decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, - }, -} -select_by_weight_tab.LargeCutGems = select_by_weight_tab.SmallCutGems - -local function get_cur_tab_category() - return diplomacy.taking_requests_tablist[diplomacy.taking_requests_selected_tab] -end - -local function get_select_by_value_tab(category) - category = category or get_cur_tab_category() - return select_by_value_tab[df.entity_sell_category[category]] -end - -local function get_select_by_weight_tab(category) - category = category or get_cur_tab_category() - return select_by_weight_tab[df.entity_sell_category[category]] -end - -local function get_cur_priority_list() - return diplomacy.environment.dipev.sell_requests.priority[get_cur_tab_category()] -end - -local function diplomacy_toggle_cat() - local priority = get_cur_priority_list() - if not priority or #priority == 0 then return end - local target_val = priority[0] == 0 and 4 or 0 - for i in ipairs(priority) do - priority[i] = target_val - end -end - -local function diplomacy_toggle_all_cats() - local target_val = 4 - local all_selected = true - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - for i in ipairs(priority) do - if priority[i] ~= 4 then - all_selected = false - break - end - end - end - if not all_selected then break end - end - if all_selected then - target_val = 0 - end - - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - for i in ipairs(priority) do - priority[i] = target_val - end - end - end -end - -local function select_by_value(prices, val) - local priority = get_cur_priority_list() - if not priority then return end - for i in ipairs(priority) do - if prices[i] == val then - priority[i] = 4 - end - end -end - -local function get_civ_key() - local civ = df.historical_entity.find(diplomacy.actor.civ_id) - if not civ then return 'UNKNOWN' end - local name = dfhack.translation.translateName(civ.name) - local race = df.creature_raw.find(civ.race) - local race_name = race and race.name[0] or 'Unknown' - -- Save by race+name to distinguish different dwarven/elven civs, or just race - -- "MOUNTAIN" is the raw id, but race_name is "dwarf". We'll use civ ID to be safe, - -- or race_name to make it portable. Let's use race_name + civ_id to prevent clashes, - -- but actually the user asked for different civilizations. Let's key by the civ's translated name. - -- Better yet, key by the English translated name to make it readable in the JSON. - return dfhack.translation.translateName(civ.name, true) -end - -local CONFIG_FILE = 'dfhack-config/trade-agreements.json' - -local function save_requests() - local key = get_civ_key() - local data = {} - if dfhack.filesystem.isfile(CONFIG_FILE) then - data = json.decode_file(CONFIG_FILE) or {} - end - - local civ_data = data[key] or {} - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local cat_name = df.entity_sell_category[cat] - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - local saved_priority = {} - local has_requests = false - for i in ipairs(priority) do - if priority[i] ~= 0 then - saved_priority[tostring(i)] = priority[i] - has_requests = true - end - end - if has_requests then - civ_data[cat_name] = saved_priority - else - civ_data[cat_name] = nil - end - end - end - - data[key] = civ_data - json.encode_file(data, CONFIG_FILE, {pretty=true}) - dfhack.gui.showAnnouncement('Trade requests saved for ' .. key, COLOR_GREEN) -end - -local function load_requests() - if not dfhack.filesystem.isfile(CONFIG_FILE) then - dfhack.gui.showAnnouncement('No saved trade agreements found.', COLOR_RED) - return - end - - local data = json.decode_file(CONFIG_FILE) - if not data then return end - - local key = get_civ_key() - local civ_data = data[key] - - if not civ_data then - dfhack.gui.showAnnouncement('No saved requests found for ' .. key, COLOR_YELLOW) - return - end - - for _, cat in ipairs(diplomacy.taking_requests_tablist) do - local cat_name = df.entity_sell_category[cat] - local priority = diplomacy.environment.dipev.sell_requests.priority[cat] - if priority then - local saved_priority = civ_data[cat_name] or {} - for i in ipairs(priority) do - priority[i] = saved_priority[tostring(i)] or 0 - end - end - end - - dfhack.gui.showAnnouncement('Trade requests loaded for ' .. key, COLOR_GREEN) -end - -function TradeAgreementOverlay:init() - self:addviews{ - widgets.HotkeyLabel{ - frame={t=0, l=0, w=23}, - label='Select all/none', - key='CUSTOM_CTRL_A', - on_activate=diplomacy_toggle_cat, - }, - widgets.HotkeyLabel{ - frame={t=1, l=0, w=23}, - label='Select globally', - key='CUSTOM_SHIFT_A', - on_activate=diplomacy_toggle_all_cats, - }, - widgets.HotkeyLabel{ - frame={t=2, l=0, w=23}, - label='Select by value', - key='CUSTOM_CTRL_M', - on_activate=self:callback('select_by_value'), - enabled=get_select_by_value_tab, - }, - widgets.HotkeyLabel{ - frame={t=0, l=24, w=23}, - label='Save requests', - key='CUSTOM_CTRL_S', - on_activate=save_requests, - }, - widgets.HotkeyLabel{ - frame={t=1, l=24, w=23}, - label='Load requests', - key='CUSTOM_CTRL_L', - on_activate=load_requests, - }, - widgets.HotkeyLabel{ - frame={t=2, l=24, w=23}, - label='Select by weight', - key='CUSTOM_CTRL_W', - on_activate=self:callback('select_by_weight'), - enabled=get_select_by_weight_tab, - }, - } -end - -local function get_prices(tab) - local resource = tab.get_mats(df.historical_entity.find(diplomacy.actor.civ_id).resources) - if not resource then return {}, {} end - local prices = {} - local matValuesUnique = {} - local filter = {} - for civid, matid in pairs(resource) do - local price = tab.decode(matid) - prices[civid] = price - if not filter[price] then - local val = {value=price, count=1} - filter[price] = val - table.insert(matValuesUnique, val) - else - filter[price].count = filter[price].count + 1 - end - end - table.sort(matValuesUnique, function(a, b) return a.value < b.value end) - return prices, matValuesUnique -end - -function TradeAgreementOverlay:select_by_value() - local cat = get_cur_tab_category() - local cur_tab = get_select_by_value_tab(cat) - - local resource_name = df.entity_sell_category[cat] - if resource_name:endswith('Gems') then resource_name = 'Gem' end - local prices, matValuesUnique = get_prices(cur_tab) - local list = {} - for index, value in ipairs(matValuesUnique) do - list[index] = ('%4d%s (%d type%s of %s)'):format( - value.value, string.char(15), value.count, value.count == 1 and '' or 's', resource_name:lower()) - end - dlg.showListPrompt( - "Select materials with base value", "", - COLOR_WHITE, - list, - function(id) select_by_value(prices, matValuesUnique[id].value) end - ) -end - -function TradeAgreementOverlay:select_by_weight() - local cat = get_cur_tab_category() - local cur_tab = get_select_by_weight_tab(cat) - - local resource_name = df.entity_sell_category[cat] - local prices, matValuesUnique = get_prices(cur_tab) - local list = {} - for index, value in ipairs(matValuesUnique) do - list[index] = ('%4d (%d type%s of %s)'):format( - value.value, value.count, value.count == 1 and '' or 's', resource_name:lower()) - end - dlg.showListPrompt( - "Select materials with solid density", "", - COLOR_WHITE, - list, - function(id) select_by_value(prices, matValuesUnique[id].value) end - ) -end +--@ module = true +local dlg = require('gui.dialogs') +local gui = require('gui') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') +local json = require('json') + +local diplomacy = df.global.game.main_interface.diplomacy + +TradeAgreementOverlay = defclass(TradeAgreementOverlay, overlay.OverlayWidget) +TradeAgreementOverlay.ATTRS{ + desc='Adds quick toggles for groups of trade agreement items.', + default_pos={x=45, y=-6}, + default_enabled=true, + viewscreens='dwarfmode/Diplomacy/Requests', + frame={w=58, h=7}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, +} + +local function transform_mat_list(matList) + local list = {} + for key, value in pairs(matList.mat_index) do + list[key] = {type=matList.mat_type[key], index=value} + end + return list +end + +local function decode_mat_list(mat) + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.material_value or 0 +end + +local function decode_mat_weight(mat) + local minfo = dfhack.matinfo.decode(mat.type, mat.index) + return minfo and minfo.material.solid_density or 0 +end + +local select_by_value_tab = { + Leather={ + get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, + decode=decode_mat_list, + }, + SmallCutGems={ + get_mats=function(resources) return resources.gems end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Meat={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, + decode=decode_mat_list, + }, + Parchment={ + get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, + decode=decode_mat_list, + }, + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.material_value end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.material_value end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_list, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_list, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_list, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_list, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_list, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_list, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_list, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_list, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_list, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_list, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_list, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_list, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_list }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_list }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_list }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.material_value end, + }, +} +select_by_value_tab.LargeCutGems = select_by_value_tab.SmallCutGems + +local select_by_weight_tab = { + Stone={ + get_mats=function(resources) return resources.stones end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Wood={ + get_mats=function(resources) return resources.wood_products end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.WOOD, id).material.solid_density end, + }, + MetalBars={ + get_mats=function(resources) return resources.metals end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + Cheese={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.cheese) end, + decode=decode_mat_weight, + }, + Powders={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.powders) end, + decode=decode_mat_weight, + }, + Extracts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.extracts) end, + decode=decode_mat_weight, + }, + Drinks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.booze) end, + decode=decode_mat_weight, + }, + Meat={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.meat) end, + decode=decode_mat_weight, + }, + Leather={ + get_mats=function(resources) return transform_mat_list(resources.organic.leather) end, + decode=decode_mat_weight, + }, + Parchment={ + get_mats=function(resources) return transform_mat_list(resources.organic.parchment) end, + decode=decode_mat_weight, + }, + SmallCutGems={ + get_mats=function(resources) return resources.gems end, + decode=function(id) return dfhack.matinfo.decode(0, id).material.solid_density end, + }, + CupsMugsGoblets={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + Crafts={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.crafts) end, + decode=decode_mat_weight, + }, + FlasksWaterskins={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.flasks) end, + decode=decode_mat_weight, + }, + Quivers={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.quivers) end, + decode=decode_mat_weight, + }, + Backpacks={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.backpacks) end, + decode=decode_mat_weight, + }, + Barrels={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.barrels) end, + decode=decode_mat_weight, + }, + Sand={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.sand) end, + decode=decode_mat_weight, + }, + Glass={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.glass) end, + decode=decode_mat_weight, + }, + Clay={ + get_mats=function(resources) return transform_mat_list(resources.misc_mat.clay) end, + decode=decode_mat_weight, + }, + ClothPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ThreadPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + RopesPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + BagsPlant={ get_mats=function(resources) return transform_mat_list(resources.organic.fiber) end, decode=decode_mat_weight }, + ClothSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ThreadSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + RopesSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + BagsSilk={ get_mats=function(resources) return transform_mat_list(resources.organic.silk) end, decode=decode_mat_weight }, + ClothYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + ThreadYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + RopesYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + BagsYarn={ get_mats=function(resources) return transform_mat_list(resources.organic.wool) end, decode=decode_mat_weight }, + Plants={ + get_mats=function(resources) return resources.plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, + GardenVegetables={ + get_mats=function(resources) return resources.shrub_fruit_plants end, + decode=function(id) return dfhack.matinfo.decode(df.builtin_mats.PLANT, id).material.solid_density end, + }, +} +select_by_weight_tab.LargeCutGems = select_by_weight_tab.SmallCutGems + +local function get_cur_tab_category() + return diplomacy.taking_requests_tablist[diplomacy.taking_requests_selected_tab] +end + +local function get_select_by_value_tab(category) + category = category or get_cur_tab_category() + return select_by_value_tab[df.entity_sell_category[category]] +end + +local function get_select_by_weight_tab(category) + category = category or get_cur_tab_category() + return select_by_weight_tab[df.entity_sell_category[category]] +end + +local function get_cur_priority_list() + return diplomacy.environment.dipev.sell_requests.priority[get_cur_tab_category()] +end + +local function diplomacy_toggle_cat() + local priority = get_cur_priority_list() + if not priority or #priority == 0 then return end + local target_val = priority[0] == 0 and 4 or 0 + for i in ipairs(priority) do + priority[i] = target_val + end +end + +local function diplomacy_toggle_all_cats() + local target_val = 4 + local all_selected = true + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + for i in ipairs(priority) do + if priority[i] ~= 4 then + all_selected = false + break + end + end + end + if not all_selected then break end + end + if all_selected then + target_val = 0 + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + for i in ipairs(priority) do + priority[i] = target_val + end + end + end +end + +local function select_by_value(prices, val) + local priority = get_cur_priority_list() + if not priority then return end + for i in ipairs(priority) do + if prices[i] == val then + priority[i] = 4 + end + end +end + +local function get_civ_key() + local civ = df.historical_entity.find(diplomacy.actor.civ_id) + if not civ then return 'UNKNOWN' end + local name = dfhack.translation.translateName(civ.name) + local race = df.creature_raw.find(civ.race) + local race_name = race and race.name[0] or 'Unknown' + -- Save by race+name to distinguish different dwarven/elven civs, or just race + -- "MOUNTAIN" is the raw id, but race_name is "dwarf". We'll use civ ID to be safe, + -- or race_name to make it portable. Let's use race_name + civ_id to prevent clashes, + -- but actually the user asked for different civilizations. Let's key by the civ's translated name. + -- Better yet, key by the English translated name to make it readable in the JSON. + return dfhack.translation.translateName(civ.name, true) +end + +local CONFIG_FILE = 'dfhack-config/trade-agreements.json' + +local function save_requests() + local key = get_civ_key() + local data = {} + if dfhack.filesystem.isfile(CONFIG_FILE) then + data = json.decode_file(CONFIG_FILE) or {} + end + + local civ_data = data[key] or {} + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = {} + local has_requests = false + for i in ipairs(priority) do + if priority[i] ~= 0 then + saved_priority[tostring(i)] = priority[i] + has_requests = true + end + end + if has_requests then + civ_data[cat_name] = saved_priority + else + civ_data[cat_name] = nil + end + end + end + + data[key] = civ_data + json.encode_file(data, CONFIG_FILE, {pretty=true}) + dfhack.gui.showAnnouncement('Trade requests saved for ' .. key, COLOR_GREEN) +end + +local function load_requests() + if not dfhack.filesystem.isfile(CONFIG_FILE) then + dfhack.gui.showAnnouncement('No saved trade agreements found.', COLOR_RED) + return + end + + local data = json.decode_file(CONFIG_FILE) + if not data then return end + + local key = get_civ_key() + local civ_data = data[key] + + if not civ_data then + dfhack.gui.showAnnouncement('No saved requests found for ' .. key, COLOR_YELLOW) + return + end + + for _, cat in ipairs(diplomacy.taking_requests_tablist) do + local cat_name = df.entity_sell_category[cat] + local priority = diplomacy.environment.dipev.sell_requests.priority[cat] + if priority then + local saved_priority = civ_data[cat_name] or {} + for i in ipairs(priority) do + priority[i] = saved_priority[tostring(i)] or 0 + end + end + end + + dfhack.gui.showAnnouncement('Trade requests loaded for ' .. key, COLOR_GREEN) +end + +function TradeAgreementOverlay:init() + self:addviews{ + widgets.HotkeyLabel{ + frame={t=0, l=0, w=23}, + label='Select all/none', + key='CUSTOM_CTRL_A', + on_activate=diplomacy_toggle_cat, + }, + widgets.HotkeyLabel{ + frame={t=1, l=0, w=23}, + label='Select globally', + key='CUSTOM_SHIFT_A', + on_activate=diplomacy_toggle_all_cats, + }, + widgets.HotkeyLabel{ + frame={t=2, l=0, w=23}, + label='Select by value', + key='CUSTOM_CTRL_M', + on_activate=self:callback('select_by_value'), + enabled=get_select_by_value_tab, + }, + widgets.HotkeyLabel{ + frame={t=0, l=24, w=23}, + label='Save requests', + key='CUSTOM_CTRL_S', + on_activate=save_requests, + }, + widgets.HotkeyLabel{ + frame={t=1, l=24, w=23}, + label='Load requests', + key='CUSTOM_CTRL_L', + on_activate=load_requests, + }, + widgets.HotkeyLabel{ + frame={t=2, l=24, w=23}, + label='Select by weight', + key='CUSTOM_CTRL_W', + on_activate=self:callback('select_by_weight'), + enabled=get_select_by_weight_tab, + }, + } +end + +local function get_prices(tab) + local resource = tab.get_mats(df.historical_entity.find(diplomacy.actor.civ_id).resources) + if not resource then return {}, {} end + local prices = {} + local matValuesUnique = {} + local filter = {} + for civid, matid in pairs(resource) do + local price = tab.decode(matid) + prices[civid] = price + if not filter[price] then + local val = {value=price, count=1} + filter[price] = val + table.insert(matValuesUnique, val) + else + filter[price].count = filter[price].count + 1 + end + end + table.sort(matValuesUnique, function(a, b) return a.value < b.value end) + return prices, matValuesUnique +end + +function TradeAgreementOverlay:select_by_value() + local cat = get_cur_tab_category() + local cur_tab = get_select_by_value_tab(cat) + + local resource_name = df.entity_sell_category[cat] + if resource_name:endswith('Gems') then resource_name = 'Gem' end + local prices, matValuesUnique = get_prices(cur_tab) + local list = {} + for index, value in ipairs(matValuesUnique) do + list[index] = ('%4d%s (%d type%s of %s)'):format( + value.value, string.char(15), value.count, value.count == 1 and '' or 's', resource_name:lower()) + end + dlg.showListPrompt( + "Select materials with base value", "", + COLOR_WHITE, + list, + function(id) select_by_value(prices, matValuesUnique[id].value) end + ) +end + +function TradeAgreementOverlay:select_by_weight() + local cat = get_cur_tab_category() + local cur_tab = get_select_by_weight_tab(cat) + + local resource_name = df.entity_sell_category[cat] + local prices, matValuesUnique = get_prices(cur_tab) + local list = {} + for index, value in ipairs(matValuesUnique) do + list[index] = ('%4d (%d type%s of %s)'):format( + value.value, value.count, value.count == 1 and '' or 's', resource_name:lower()) + end + dlg.showListPrompt( + "Select materials with solid density", "", + COLOR_WHITE, + list, + function(id) select_by_value(prices, matValuesUnique[id].value) end + ) +end From 152905839e0d85310d4836451ba87db6f7f633b4 Mon Sep 17 00:00:00 2001 From: sizzlins Date: Thu, 23 Apr 2026 14:54:05 +0700 Subject: [PATCH 6/6] fix: Auto-barter accounts for merchant negotiation markup via trade.mer.import_value --- internal/caravan/trade.lua | 170 +++++++------------------------------ 1 file changed, 30 insertions(+), 140 deletions(-) diff --git a/internal/caravan/trade.lua b/internal/caravan/trade.lua index 54bc19c49..5511ae971 100644 --- a/internal/caravan/trade.lua +++ b/internal/caravan/trade.lua @@ -1,33 +1,26 @@ --@ module = true - -- TODO: the category checkbox that indicates whether all items in the category -- are selected can be incorrect after the overlay adjusts the container -- selection. the state is in trade.current_type_a_flag, but figuring out which -- index to modify is non-trivial. - local common = reqscript('internal/caravan/common') local gui = require('gui') local overlay = require('plugins.overlay') local predicates = reqscript('internal/caravan/predicates') local utils = require('utils') local widgets = require('gui.widgets') - trader_selected_state = trader_selected_state or {} broker_selected_state = broker_selected_state or {} handle_ctrl_click_on_render = handle_ctrl_click_on_render or false handle_shift_click_on_render = handle_shift_click_on_render or false - local trade = df.global.game.main_interface.trade - -- Auto Barter: configurable profit margin for the merchant. -- 1.25 = offer 25% more value than the merchant's goods you want. -- Increase if merchants keep rejecting. Decrease as your broker skill improves. local TARGET_MARGIN = 1.25 - -- ------------------- -- Trade -- - Trade = defclass(Trade, widgets.Window) Trade.ATTRS { frame_title='Select trade goods', @@ -35,50 +28,42 @@ Trade.ATTRS { resizable=true, resize_min={w=48, h=40}, } - local function get_entry_icon(data) if trade.goodflag[data.list_idx][data.item_idx].selected then return common.ALL_PEN end end - local function sort_noop() -- this function is used as a marker and never actually gets called error('sort_noop should not be called') end - local function sort_base(a, b) return a.data.desc < b.data.desc end - local function sort_by_name_desc(a, b) if a.search_key == b.search_key then return sort_base(a, b) end return a.search_key < b.search_key end - local function sort_by_name_asc(a, b) if a.search_key == b.search_key then return sort_base(a, b) end return a.search_key > b.search_key end - local function sort_by_value_desc(a, b) if a.data.value == b.data.value then return sort_by_name_desc(a, b) end return a.data.value > b.data.value end - local function sort_by_value_asc(a, b) if a.data.value == b.data.value then return sort_by_name_desc(a, b) end return a.data.value < b.data.value end - local function sort_by_status_desc(a, b) local a_selected = get_entry_icon(a.data) local b_selected = get_entry_icon(b.data) @@ -87,7 +72,6 @@ local function sort_by_status_desc(a, b) end return a_selected end - local function sort_by_status_asc(a, b) local a_selected = get_entry_icon(a.data) local b_selected = get_entry_icon(b.data) @@ -96,21 +80,17 @@ local function sort_by_status_asc(a, b) end return b_selected end - local STATUS_COL_WIDTH = 7 local VALUE_COL_WIDTH = 6 local FILTER_HEIGHT = 18 - function Trade:init() self.cur_page = 1 self.filters = {'', ''} self.predicate_contexts = {{name='trade_caravan'}, {name='trade_fort'}} - self.animal_ethics = common.is_animal_lover_caravan(trade.mer) self.wood_ethics = common.is_tree_lover_caravan(trade.mer) self.banned_items = common.get_banned_items() self.risky_items = common.get_risky_items(self.banned_items) - self:addviews{ widgets.CycleHotkeyLabel{ view_id='sort', @@ -274,16 +254,13 @@ function Trade:init() auto_width=true, }, } - -- replace the FilteredList's built-in EditField with our own self.subviews.list.list.frame.t = 0 self.subviews.list.edit.visible = false self.subviews.list.edit = self.subviews.search self.subviews.search.on_change = self.subviews.list:callback('onFilterChange') - self:reset_cache() end - function Trade:refresh_list(sort_widget, sort_fn) sort_widget = sort_widget or 'sort' sort_fn = sort_fn or self.subviews.sort:getOptionValue() @@ -302,7 +279,6 @@ function Trade:refresh_list(sort_widget, sort_fn) list:setFilter(saved_filter) list.list:on_scrollbar(math.max(0, saved_top - list.list.page_top)) end - local function is_ethical_product(item, animal_ethics, wood_ethics) if not animal_ethics and not wood_ethics then return true end -- bin contents are already split out; no need to double-check them @@ -315,21 +291,17 @@ local function is_ethical_product(item, animal_ethics, wood_ethics) end end end - return (not animal_ethics or not item:isAnimalProduct()) and (not wood_ethics or not common.has_wood(item)) end - local function make_choice_text(value, desc) return { {width=STATUS_COL_WIDTH+VALUE_COL_WIDTH, rjustify=true, text=common.obfuscate_value(value)}, {gap=2, text=desc}, } end - function Trade:cache_choices(list_idx, trade_bins) if self.choices[list_idx][trade_bins] then return self.choices[list_idx][trade_bins] end - local goodflags = trade.goodflag[list_idx] local trade_bins_choices, notrade_bins_choices = {}, {} local parent_data @@ -389,12 +361,10 @@ function Trade:cache_choices(list_idx, trade_bins) end if is_container then parent_data = data end end - self.choices[list_idx][true] = trade_bins_choices self.choices[list_idx][false] = notrade_bins_choices return self:cache_choices(list_idx, trade_bins) end - function Trade:get_choices() local raw_choices = self:cache_choices(self.cur_page-1, self.subviews.trade_bins:getOptionValue()) local provenance = self.subviews.provenance:getOptionValue() @@ -444,7 +414,6 @@ function Trade:get_choices() table.sort(choices, self.subviews.sort:getOptionValue()) return choices end - local function toggle_item_base(choice, target_value) local goodflag = trade.goodflag[choice.data.list_idx][choice.data.item_idx] if target_value == nil then @@ -457,17 +426,14 @@ local function toggle_item_base(choice, target_value) end return target_value end - function Trade:select_item(idx, choice) if not dfhack.internal.getModifiers().shift then self.prev_list_idx = self.subviews.list.list:getSelected() end end - function Trade:toggle_item(idx, choice) toggle_item_base(choice) end - function Trade:toggle_range(idx, choice) if not self.prev_list_idx then self:toggle_item(idx, choice) @@ -481,35 +447,28 @@ function Trade:toggle_range(idx, choice) end self.prev_list_idx = list_idx end - function Trade:toggle_visible() local target_value for _, choice in ipairs(self.subviews.list:getVisibleChoices()) do target_value = toggle_item_base(choice, target_value) end end - function Trade:reset_cache() self.choices = {[0]={}, [1]={}} self:refresh_list() end - -- ------------------- -- TradeScreen -- - trade_view = trade_view or nil - TradeScreen = defclass(TradeScreen, gui.ZScreen) TradeScreen.ATTRS { focus_path='caravan/trade', } - function TradeScreen:init() self.trade_window = Trade{} self:addviews{self.trade_window} end - function TradeScreen:onInput(keys) if self.reset_pending then return false end local handled = TradeScreen.super.onInput(self, keys) @@ -519,7 +478,6 @@ function TradeScreen:onInput(keys) end return handled end - function TradeScreen:onRenderFrame() if not df.global.game.main_interface.trade.open then if trade_view then trade_view:dismiss() end @@ -531,17 +489,13 @@ function TradeScreen:onRenderFrame() self.trade_window:reset_cache() end end - function TradeScreen:onDismiss() trade_view = nil end - -- ------------------- -- TradeOverlay -- - local MARGIN_HEIGHT = 26 -- screen height *other* than the list - local function set_height(list_idx, delta) trade.i_height[list_idx] = trade.i_height[list_idx] + delta if delta >= 0 then return end @@ -552,14 +506,12 @@ local function set_height(list_idx, delta) math.min(trade.scroll_position_item[list_idx], trade.i_height[list_idx] - page_height)) end - local function flags_match(goodflag1, goodflag2) return goodflag1.selected == goodflag2.selected and goodflag1.contained == goodflag2.contained and goodflag1.container_collapsed == goodflag2.container_collapsed and goodflag1.filtered_off == goodflag2.filtered_off end - local function select_shift_clicked_container_items(new_state, old_state_fn, list_idx) -- if ctrl is also held, collapse the container too local also_collapse = dfhack.internal.getModifiers().ctrl @@ -573,29 +525,23 @@ local function select_shift_clicked_container_items(new_state, old_state_fn, lis end goto continue end - local old_goodflag = old_state_fn(k) if flags_match(goodflag, old_goodflag) then goto continue end local is_container = df.item_binst:is_instance(trade.good[list_idx][k]) if not is_container then goto continue end - -- deselect the container itself goodflag.selected = false - if also_collapse or old_goodflag.container_collapsed then goodflag.container_collapsed = true collapsing_container = not old_goodflag.container_collapsed end in_target_container = true - ::continue:: end - if collapsed_item_count > 0 then set_height(list_idx, collapsed_item_count * -3) end end - -- collapses uncollapsed containers and restores the selection state for the container -- and contained items local function toggle_ctrl_clicked_containers(new_state, old_state_fn, list_idx) @@ -608,27 +554,21 @@ local function toggle_ctrl_clicked_containers(new_state, old_state_fn, list_idx) utils.assign(goodflag, old_goodflag) goto continue end - if flags_match(goodflag, old_goodflag) or goodflag.contained then goto continue end local is_container = df.item_binst:is_instance(trade.good[list_idx][k]) if not is_container then goto continue end - goodflag.selected = old_goodflag.selected goodflag.container_collapsed = not old_goodflag.container_collapsed in_target_container = true is_collapsing = goodflag.container_collapsed - ::continue:: end - if toggled_item_count > 0 then set_height(list_idx, toggled_item_count * 3 * (is_collapsing and -1 or 1)) end end - local function collapseTypes(types_list, list_idx) local type_on_count = 0 - for k in ipairs(types_list) do local type_on = trade.current_type_a_on[list_idx][k] if type_on then @@ -636,63 +576,50 @@ local function collapseTypes(types_list, list_idx) end types_list[k] = false end - trade.i_height[list_idx] = type_on_count * 3 trade.scroll_position_item[list_idx] = 0 end - local function collapseAllTypes() collapseTypes(trade.current_type_a_expanded[0], 0) collapseTypes(trade.current_type_a_expanded[1], 1) end - local function collapseContainers(item_list, list_idx) local num_items_collapsed = 0 for k, goodflag in ipairs(item_list) do if goodflag.contained then goto continue end - local item = trade.good[list_idx][k] local is_container = df.item_binst:is_instance(item) if not is_container then goto continue end - if not goodflag.container_collapsed then goodflag.container_collapsed = true num_items_collapsed = num_items_collapsed + #dfhack.items.getContainedItems(item) end - ::continue:: end - if num_items_collapsed > 0 then set_height(list_idx, num_items_collapsed * -3) end end - local function collapseAllContainers() collapseContainers(trade.goodflag[0], 0) collapseContainers(trade.goodflag[1], 1) end - local function collapseEverything() collapseAllContainers() collapseAllTypes() end - local function copyGoodflagState() -- utils.clone will return a lua table, with indices offset by 1 -- we'll use getSavedGoodflag to map the index back to the original value trader_selected_state = utils.clone(trade.goodflag[0], true) broker_selected_state = utils.clone(trade.goodflag[1], true) end - local function getSavedGoodflag(saved_state, k) return saved_state[k+1] end - -- ------------------- -- Auto Barter -- - local function get_selected_merchant_value() local total = 0 local goodflags = trade.goodflag[0] @@ -712,16 +639,13 @@ local function get_selected_merchant_value() end return total end - local function build_fort_selectable_units() local banned_items = common.get_banned_items() local risky_items = common.get_risky_items(banned_items) local units = {} local current_container = nil - for item_idx, item in ipairs(trade.good[1]) do local goodflag = trade.goodflag[1][item_idx] - if goodflag.contained then -- this item is inside a container; attach it to the current container unit if current_container then @@ -735,10 +659,8 @@ local function build_fort_selectable_units() else -- flush previous container current_container = nil - local is_banned, _ = common.scan_banned(item, risky_items) local is_container = df.item_binst:is_instance(item) - local unit = { item_idx = item_idx, value = common.get_perceived_value(item, trade.mer), @@ -746,15 +668,12 @@ local function build_fort_selectable_units() contained_indices = {}, has_banned = is_banned, } - if is_container then current_container = unit end - table.insert(units, unit) end end - -- filter out banned units local filtered = {} for _, unit in ipairs(units) do @@ -764,21 +683,39 @@ local function build_fort_selectable_units() end return filtered end - +-- Compute the negotiation markup the game applies to merchant goods. +-- dfhack.items.getValue returns base values, but the game's trade screen +-- applies a markup based on broker/merchant skill negotiation. We derive +-- this markup by comparing import_value (the game's actual total for all +-- merchant goods) against our computed total. +local function compute_merchant_markup() + local total_base = 0 + for item_idx, item in ipairs(trade.good[0]) do + local goodflag = trade.goodflag[0][item_idx] + if not goodflag.contained then + total_base = total_base + common.get_perceived_value(item, trade.mer) + end + end + local import_value = trade.mer.import_value + if total_base > 0 and import_value > 0 then + return import_value / total_base + end + return 1.0 +end local function auto_barter(expensive_first) local merchant_value = get_selected_merchant_value() if merchant_value <= 0 then dfhack.printerr('Auto Barter: Select merchant goods first!') return end - - local target_value = math.ceil(merchant_value * TARGET_MARGIN) - + -- Scale the target by the negotiation markup so we match the game's + -- actual displayed merchant value, not just the base item values. + local markup = compute_merchant_markup() + local target_value = math.ceil(merchant_value * markup * TARGET_MARGIN) -- deselect all fortress goods for item_idx in ipairs(trade.good[1]) do trade.goodflag[1][item_idx].selected = false end - -- build and sort selectable units local units = build_fort_selectable_units() if expensive_first then @@ -786,14 +723,11 @@ local function auto_barter(expensive_first) else table.sort(units, function(a, b) return a.value < b.value end) end - -- knapsack greedy selection with overshoot protection local running_total = 0 local selected_units = {} - for _, unit in ipairs(units) do if running_total >= target_value then break end - local remaining = target_value - running_total -- overshoot protection: if this unit's value is more than 2x what we -- still need AND there are cheaper options ahead, skip it @@ -803,15 +737,12 @@ local function auto_barter(expensive_first) goto continue end end - running_total = running_total + unit.value table.insert(selected_units, unit) - ::continue:: end - - -- if we overshot badly in the first pass, do a second pass to find - -- tighter fits from remaining items + -- if we didn't reach target in the first pass, do a second pass + -- to fill the gap from remaining items if running_total < target_value then for _, unit in ipairs(units) do if running_total >= target_value then break end @@ -829,7 +760,6 @@ local function auto_barter(expensive_first) end end end - -- apply selections to trade.goodflag for _, unit in ipairs(selected_units) do trade.goodflag[1][unit.item_idx].selected = true @@ -838,14 +768,12 @@ local function auto_barter(expensive_first) trade.goodflag[1][cidx].selected = true end end - - local value_str = dfhack.formatInt(running_total) - local target_str = dfhack.formatInt(target_value) - local merchant_str = dfhack.formatInt(merchant_value) - print(('Auto Barter: Merchant goods=%s, Target=%s (x%.2f), Offering=%s (%d items)'):format( - merchant_str, target_str, TARGET_MARGIN, value_str, #selected_units)) + -- local value_str = dfhack.formatInt(running_total) + -- local target_str = dfhack.formatInt(target_value) + -- local merchant_str = dfhack.formatInt(math.ceil(merchant_value * markup)) + -- print(('Auto Barter: Merchant goods(game)=%s, Target=%s (x%.2f, markup=%.2f), Offering=%s (%d items)'):format( + -- merchant_str, target_str, TARGET_MARGIN, markup, value_str, #selected_units)) end - TradeOverlay = defclass(TradeOverlay, overlay.OverlayWidget) TradeOverlay.ATTRS{ desc='Adds convenience functions for working with bins to the trade screen.', @@ -856,7 +784,6 @@ TradeOverlay.ATTRS{ frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, } - function TradeOverlay:init() self:addviews{ widgets.Label{ @@ -898,7 +825,6 @@ function TradeOverlay:init() }, } end - -- do our alterations *after* the vanilla response to the click has registered. otherwise -- it's very difficult to figure out which item has been clicked function TradeOverlay:onRenderBody(dc) @@ -912,10 +838,8 @@ function TradeOverlay:onRenderBody(dc) toggle_ctrl_clicked_containers(trade.goodflag[1], curry(getSavedGoodflag, broker_selected_state), 1) end end - function TradeOverlay:onInput(keys) if TradeOverlay.super.onInput(self, keys) then return true end - if keys._MOUSE_L then if dfhack.internal.getModifiers().shift then handle_shift_click_on_render = true @@ -926,11 +850,9 @@ function TradeOverlay:onInput(keys) end end end - -- ------------------- -- TradeBannerOverlay -- - TradeBannerOverlay = defclass(TradeBannerOverlay, overlay.OverlayWidget) TradeBannerOverlay.ATTRS{ desc='Adds link to the trade screen to launch the DFHack trade UI.', @@ -940,7 +862,6 @@ TradeBannerOverlay.ATTRS{ frame={w=25, h=1}, frame_background=gui.CLEAR_PEN, } - function TradeBannerOverlay:init() self:addviews{ widgets.TextButton{ @@ -952,21 +873,17 @@ function TradeBannerOverlay:init() }, } end - function TradeBannerOverlay:onInput(keys) if TradeBannerOverlay.super.onInput(self, keys) then return true end - if keys._MOUSE_R or keys.LEAVESCREEN then if trade_view then trade_view:dismiss() end end end - -- ------------------- -- AutoBarterOverlay -- - AutoBarterOverlay = defclass(AutoBarterOverlay, overlay.OverlayWidget) AutoBarterOverlay.ATTRS{ desc='Adds auto barter buttons to the trade screen.', @@ -976,7 +893,6 @@ AutoBarterOverlay.ATTRS{ frame={w=25, h=2}, frame_background=gui.CLEAR_PEN, } - function AutoBarterOverlay:init() self:addviews{ widgets.TextButton{ @@ -993,23 +909,19 @@ function AutoBarterOverlay:init() }, } end - -- ------------------- -- Ethics -- - Ethics = defclass(Ethics, widgets.Window) Ethics.ATTRS { frame_title='Ethical transgressions', frame={w=45, h=30}, resizable=true, } - function Ethics:init() self.choices = {} self.animal_ethics = common.is_animal_lover_caravan(trade.mer) self.wood_ethics = common.is_tree_lover_caravan(trade.mer) - self:addviews{ widgets.Label{ frame={l=0, t=0}, @@ -1034,18 +946,14 @@ function Ethics:init() on_activate=self:callback('deselect_transgressions'), }, } - self:rescan() end - function Ethics:get_transgression_count() return #self.choices end - function Ethics:get_transgression_color() return next(self.choices) and COLOR_LIGHTRED or COLOR_LIGHTGREEN end - -- also used by confirm function for_selected_item(list_idx, fn) local goodflags = trade.goodflag[list_idx] @@ -1062,7 +970,6 @@ function for_selected_item(list_idx, fn) end end end - local function for_ethics_violation(fn, animal_ethics, wood_ethics) if not animal_ethics and not wood_ethics then return end for_selected_item(1, function(item_idx, item) @@ -1071,7 +978,6 @@ local function for_ethics_violation(fn, animal_ethics, wood_ethics) end end) end - function Ethics:rescan() local choices = {} for_ethics_violation(function(item_idx, item) @@ -1081,11 +987,9 @@ function Ethics:rescan() } table.insert(choices, choice) end, self.animal_ethics, self.wood_ethics) - self.subviews.list:setChoices(choices) self.choices = choices end - function Ethics:deselect_transgressions() local goodflags = trade.goodflag[1] for _,choice in ipairs(self.choices) do @@ -1093,23 +997,18 @@ function Ethics:deselect_transgressions() end self:rescan() end - -- ------------------- -- EthicsScreen -- - ethics_view = ethics_view or nil - EthicsScreen = defclass(EthicsScreen, gui.ZScreen) EthicsScreen.ATTRS { focus_path='caravan/trade/ethics', } - function EthicsScreen:init() self.ethics_window = Ethics{} self:addviews{self.ethics_window} end - function EthicsScreen:onInput(keys) if self.reset_pending then return false end local handled = EthicsScreen.super.onInput(self, keys) @@ -1119,7 +1018,6 @@ function EthicsScreen:onInput(keys) end return handled end - function EthicsScreen:onRenderFrame() if not df.global.game.main_interface.trade.open then if ethics_view then ethics_view:dismiss() end @@ -1131,15 +1029,12 @@ function EthicsScreen:onRenderFrame() self.ethics_window:rescan() end end - function EthicsScreen:onDismiss() ethics_view = nil end - -- -------------------------- -- TradeEthicsWarningOverlay -- - -- also called by confirm function has_ethics_violation() local violated = false @@ -1149,7 +1044,6 @@ function has_ethics_violation() end, common.is_animal_lover_caravan(trade.mer), common.is_tree_lover_caravan(trade.mer)) return violated end - TradeEthicsWarningOverlay = defclass(TradeEthicsWarningOverlay, overlay.OverlayWidget) TradeEthicsWarningOverlay.ATTRS{ desc='Adds warning to the trade screen when you are about to offend the elves.', @@ -1159,7 +1053,6 @@ TradeEthicsWarningOverlay.ATTRS{ frame={w=9, h=2}, visible=has_ethics_violation, } - function TradeEthicsWarningOverlay:init() self:addviews{ widgets.BannerPanel{ @@ -1179,14 +1072,11 @@ function TradeEthicsWarningOverlay:init() }, } end - function TradeEthicsWarningOverlay:preUpdateLayout(rect) self.frame.w = (rect.width - 95) // 2 end - function TradeEthicsWarningOverlay:onInput(keys) if TradeEthicsWarningOverlay.super.onInput(self, keys) then return true end - if keys._MOUSE_R or keys.LEAVESCREEN then if ethics_view then ethics_view:dismiss()