From 71e8f4a7e0c39f70375d10ae755602248a99804e Mon Sep 17 00:00:00 2001 From: Charley Crissman Date: Mon, 19 Jan 2026 06:43:38 -0800 Subject: [PATCH 1/2] Adds new actions to fluid caravans similar to the "load/empty until" actions for cargo caravans: - Fill fluid until (caravan/target) has specific fluid level - Empty fluid until (caravan/target) has specific fluid level Also resolves https://github.com/pyanodon/pybugreports/issues/1375 . --- locale/en/caravan.cfg | 4 + scripts/caravan/caravan-prototypes.lua | 16 ++- scripts/caravan/gui/actions.lua | 6 +- scripts/caravan/impl/actions.lua | 179 ++++++++++++++++++------- 4 files changed, 157 insertions(+), 48 deletions(-) diff --git a/locale/en/caravan.cfg b/locale/en/caravan.cfg index 99e49e9a2..4ecf0625e 100644 --- a/locale/en/caravan.cfg +++ b/locale/en/caravan.cfg @@ -77,8 +77,12 @@ store-food=Store food store-specific-food=Store food until fill-inventory=Fill cargo fill-tank=Fill self fluid +fill-tank-caravan=Fill self fluid until caravan has +fill-tank-target=Fill self fluid until target has empty-inventory=Empty cargo empty-tank=Empty self fluid +empty-tank-caravan=Empty self fluid until caravan has +empty-tank-target=Empty self fluid until target has item-count=Until caravan has exactly N items inverse-item-count=Until target has exactly N items detonate=Detonate diff --git a/scripts/caravan/caravan-prototypes.lua b/scripts/caravan/caravan-prototypes.lua index 712b9f828..be141ee4e 100644 --- a/scripts/caravan/caravan-prototypes.lua +++ b/scripts/caravan/caravan-prototypes.lua @@ -45,7 +45,11 @@ Caravan.all_actions = { "fill-tank", "empty-tank", "circuit-condition", - "circuit-condition-static" + "circuit-condition-static", + "fill-tank-caravan", + "fill-tank-target", + "empty-tank-caravan", + "empty-tank-target", }, ["character"] = table.invert { "time-passed", @@ -194,7 +198,11 @@ Caravan.valid_actions = { "fill-tank", "empty-tank", "circuit-condition", - "circuit-condition-static" + "circuit-condition-static", + "fill-tank-caravan", + "fill-tank-target", + "empty-tank-caravan", + "empty-tank-target", }, ["electric-pole"] = table.invert{ "time-passed", @@ -241,6 +249,10 @@ Caravan.actions_with_item_count = table.invert{ "outpost-fluid-count", "caravan-fluid-count", "target-fluid-count", + "fill-tank-caravan", + "fill-tank-target", + "empty-tank-caravan", + "empty-tank-target", } Caravan.foods = { diff --git a/scripts/caravan/gui/actions.lua b/scripts/caravan/gui/actions.lua index ec84cf5bb..e932735dd 100644 --- a/scripts/caravan/gui/actions.lua +++ b/scripts/caravan/gui/actions.lua @@ -6,7 +6,7 @@ local number_selection = require "action_widgets/number_selection" local comparator = require "action_widgets/comparator" local caravan_prototypes = require "__pyalienlife__/scripts/caravan/caravan-prototypes" -local possibly_blocking_actions = {"fill-inventory", "empty-inventory", "store-food", "store-specific-food", "load-caravan", "unload-caravan", "load-target", "unload-target", "fill-tank", "empty-tank"} +local possibly_blocking_actions = {"fill-inventory", "empty-inventory", "store-food", "store-specific-food", "load-caravan", "unload-caravan", "load-target", "unload-target", "fill-tank", "empty-tank", "fill-tank-caravan", "fill-tank-target", "empty-tank-caravan", "empty-tank-target"} local function play_stop_button_info(caravan_data, schedule_id, action_id) local schedule = caravan_data.schedule[schedule_id] @@ -39,6 +39,10 @@ function P.build_action_flow(parent, caravan_data, action, tags) elseif Utils.contains({"load-caravan", "unload-caravan", "load-target", "unload-target"}, action.type) then -- don't know why we restrict comparison here, shouldn't it be a regular dropdown? comparator.build_static_comparator_widgets(flow, action, tags, "item", nil, "=") + elseif Utils.contains({"fill-tank-caravan", "empty-tank-target"}, action.type) then + comparator.build_static_comparator_widgets(flow, action, tags, "fluid", nil, "≥") + elseif Utils.contains({"fill-tank-target", "empty-tank-caravan"}, action.type) then + comparator.build_static_comparator_widgets(flow, action, tags, "fluid", nil, "≤") -- these are interrupt-only elseif Utils.contains({"food-count", "caravan-item-count", "target-item-count"}, action.type) then local filters diff --git a/scripts/caravan/impl/actions.lua b/scripts/caravan/impl/actions.lua index 5646ac4bc..0da80cb9a 100644 --- a/scripts/caravan/impl/actions.lua +++ b/scripts/caravan/impl/actions.lua @@ -107,6 +107,76 @@ local function transfer_filtered_items_2(input_inventory, output_inventory, item end end +local function transfer_fluid_to_caravan(caravan_data, outpost, fluid, action, max_transfer) + if not outpost or not outpost.valid then return true end + + local input = outpost.get_fluid(1) + if not input or (input.amount == 0) or input.name ~= fluid then return action.async end + if caravan_data.fluid and caravan_data.fluid.name ~= input.name then return action.async end + + local output = caravan_data.fluid or {amount = 0, temperature = 15, name = ""} + + local total_output_volume = caravan_prototypes[caravan_data.entity.name].max_volume + local remaining_space = total_output_volume - output.amount + local goal = math.min(max_transfer, remaining_space) + if goal <= 0 then return true end + + local amount_to_transfer = outpost.remove_fluid({name = input.name, amount = goal}) + + output.temperature = math.floor((output.amount * output.temperature + amount_to_transfer * input.temperature) / (output.amount + amount_to_transfer)) + output.amount = output.amount + amount_to_transfer + output.name = input.name + if output.amount == 0 then + caravan_data.fluid = nil + else + caravan_data.fluid = output + end + + if caravan_data.alt_mode == nil and caravan_data.fluid then + P.render_altmode_icon(caravan_data) + end + + local completed = action.async or (amount_to_transfer >= goal) + if amount_to_transfer > 0 and completed then + ImplControl.eat(caravan_data) + end + return completed +end + +local function transfer_fluid_from_caravan(caravan_data, outpost, fluid, action, max_transfer) + if not outpost or not outpost.valid then return true end + + local input = caravan_data.fluid + + if input == nil then return true end + if input.name ~= fluid then return true end + local goal = math.min(max_transfer, input.amount) + if goal <= 0 then return true end + + local amount_transfered = outpost.insert_fluid({ + amount = goal, + name = input.name, + temperature = input.temperature + }) + + input.amount = input.amount - amount_transfered + if input.amount == 0 then + caravan_data.fluid = nil + else + caravan_data.fluid = input + end + + if caravan_data.alt_mode and caravan_data.fluid == nil then + P.destroy_altmode_icon(caravan_data) + end + + local completed = action.async or (amount_transfered >= goal) + if amount_transfered > 0 and completed then + ImplControl.eat(caravan_data) + end + return completed +end + local circuit_red, circuit_green = defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green local function evaluate_signal(entity, signal) local result = entity.get_signal(signal, circuit_red, circuit_green) @@ -562,66 +632,81 @@ function P.not_at_outpost(caravan_data, schedule, action) end function P.fill_tank(caravan_data, schedule, action) - local storage_tank = schedule.entity - if not storage_tank or not storage_tank.valid then return true end - local output = caravan_data.fluid or {amount = 0, temperature = 15, name = ""} - local input = storage_tank.get_fluid(1) + local outpost = schedule.entity + if not outpost or not outpost.valid then return true end - local total_output_volume = caravan_prototypes[caravan_data.entity.name].max_volume - local max_output_volume = total_output_volume - output.amount - local caravan_was_empty = output.amount == 0 + local outpost_fluid = outpost.get_fluid(1) + if not outpost_fluid or outpost_fluid.amount == 0 then return action.async end + local fluid = outpost_fluid.name - if max_output_volume == 0 then return true end - if input == nil then return action.async end + -- request to transfer the full tank capacity; we later reduce this to the available contents + local max_transfer = caravan_prototypes[caravan_data.entity.name].max_volume - local amount_to_transfer = storage_tank.remove_fluid({name = input.name, amount = max_output_volume}) + return transfer_fluid_to_caravan(caravan_data, outpost, fluid, action, max_transfer) +end - output.temperature = math.floor((output.amount * output.temperature + amount_to_transfer * input.temperature) / (output.amount + amount_to_transfer)) - output.amount = output.amount + amount_to_transfer - output.name = input.name - if output.amount == 0 then - caravan_data.fluid = nil - else - caravan_data.fluid = output - end +function P.fill_tank_until_caravan_contains(caravan_data, schedule, action) + local outpost = schedule.entity + local goal = action.item_count or 0 + local fluid = action.elem_value - if caravan_data.alt_mode == nil and caravan_data.fluid then - P.render_altmode_icon(caravan_data) + local max_transfer = goal + if caravan_data.fluid then + max_transfer = max_transfer - caravan_data.fluid.amount end - local completed = action.async or output.amount >= total_output_volume - if amount_to_transfer > 0 and completed then - ImplControl.eat(caravan_data) - end - return completed + return transfer_fluid_to_caravan(caravan_data, outpost, fluid, action, max_transfer) +end + +function P.fill_tank_until_outpost_contains(caravan_data, schedule, action) + local outpost = schedule.entity + if not outpost or not outpost.valid then return true end + + local fluid = action.elem_value + local outpost_goal = action.item_count or 0 + local outpost_contents = outpost.get_fluid_count(fluid) + + local max_transfer = outpost_contents - outpost_goal + + return transfer_fluid_to_caravan(caravan_data, outpost, fluid, action, max_transfer) end function P.empty_tank(caravan_data, schedule, action) - local storage_tank = schedule.entity - if not storage_tank or not storage_tank.valid then return true end - local input = caravan_data.fluid - local output = storage_tank.get_fluid(1) + local outpost = schedule.entity + local caravan_contents = caravan_data.fluid + if caravan_contents == nil then return true end - if input == nil then return true end + local fluid = caravan_contents.name + local max_transfer = caravan_contents.amount - local amount_transfered = storage_tank.insert_fluid(input) + return transfer_fluid_from_caravan(caravan_data, outpost, fluid, action, max_transfer) +end - input.amount = input.amount - amount_transfered - if input.amount == 0 then - caravan_data.fluid = nil - else - caravan_data.fluid = input - end +function P.empty_tank_until_caravan_contains(caravan_data, schedule, action) + local outpost = schedule.entity + local fluid = action.elem_value + local goal = action.item_count - if caravan_data.alt_mode and caravan_data.fluid == nil then - P.destroy_altmode_icon(caravan_data) - end + local caravan_contents = caravan_data.fluid + if caravan_contents == nil then return true end + local max_transfer = caravan_contents.amount - goal - local completed = action.async or input.amount == 0 - if amount_transfered > 0 and completed then - ImplControl.eat(caravan_data) - end - return completed + return transfer_fluid_from_caravan(caravan_data, outpost, fluid, action, max_transfer) +end + +function P.empty_tank_until_outpost_contains(caravan_data, schedule, action) + local outpost = schedule.entity + if not outpost or not outpost.valid then return true end + + if not caravan_data.fluid then return true end + + local fluid = action.elem_value + local outpost_goal = action.item_count or 0 + local outpost_contents = outpost.get_fluid_count(fluid) + + local max_transfer = outpost_goal - outpost_contents + + return transfer_fluid_from_caravan(caravan_data, outpost, fluid, action, max_transfer) end function P.is_tank_full(caravan_data, schedule, action) @@ -725,6 +810,10 @@ Caravan.actions = { ["target-fluid-count"] = P.target_fluid_count, ["is-tank-full"] = P.is_tank_full, ["is-tank-empty"] = P.is_tank_empty, + ["fill-tank-caravan"] = P.fill_tank_until_caravan_contains, + ["fill-tank-target"] = P.fill_tank_until_outpost_contains, + ["empty-tank-caravan"] = P.empty_tank_until_caravan_contains, + ["empty-tank-target"] = P.empty_tank_until_outpost_contains, } Caravan.free_actions = { -- actions that don't use fuel From 108806adc6cf65bc2caa774cfe2d2d0d194f2485 Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Sun, 22 Feb 2026 12:33:13 -0700 Subject: [PATCH 2/2] clarify internal action name --- locale/en/caravan.cfg | 8 ++++---- scripts/caravan/caravan-prototypes.lua | 24 ++++++++++++------------ scripts/caravan/gui/actions.lua | 6 +++--- scripts/caravan/impl/actions.lua | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/locale/en/caravan.cfg b/locale/en/caravan.cfg index 4ecf0625e..c5822add7 100644 --- a/locale/en/caravan.cfg +++ b/locale/en/caravan.cfg @@ -77,12 +77,12 @@ store-food=Store food store-specific-food=Store food until fill-inventory=Fill cargo fill-tank=Fill self fluid -fill-tank-caravan=Fill self fluid until caravan has -fill-tank-target=Fill self fluid until target has +fill-tank-until-caravan-has=Fill self fluid until caravan has +fill-tank-until-target-has=Fill self fluid until target has empty-inventory=Empty cargo empty-tank=Empty self fluid -empty-tank-caravan=Empty self fluid until caravan has -empty-tank-target=Empty self fluid until target has +empty-tank-until-caravan-has=Empty self fluid until caravan has +empty-tank-until-target-has=Empty self fluid until target has item-count=Until caravan has exactly N items inverse-item-count=Until target has exactly N items detonate=Detonate diff --git a/scripts/caravan/caravan-prototypes.lua b/scripts/caravan/caravan-prototypes.lua index be141ee4e..d95c2212c 100644 --- a/scripts/caravan/caravan-prototypes.lua +++ b/scripts/caravan/caravan-prototypes.lua @@ -46,10 +46,10 @@ Caravan.all_actions = { "empty-tank", "circuit-condition", "circuit-condition-static", - "fill-tank-caravan", - "fill-tank-target", - "empty-tank-caravan", - "empty-tank-target", + "fill-tank-until-caravan-has", + "fill-tank-until-target-has", + "empty-tank-until-caravan-has", + "empty-tank-until-target-has", }, ["character"] = table.invert { "time-passed", @@ -199,10 +199,10 @@ Caravan.valid_actions = { "empty-tank", "circuit-condition", "circuit-condition-static", - "fill-tank-caravan", - "fill-tank-target", - "empty-tank-caravan", - "empty-tank-target", + "fill-tank-until-caravan-has", + "fill-tank-until-target-has", + "empty-tank-until-caravan-has", + "empty-tank-until-target-has", }, ["electric-pole"] = table.invert{ "time-passed", @@ -249,10 +249,10 @@ Caravan.actions_with_item_count = table.invert{ "outpost-fluid-count", "caravan-fluid-count", "target-fluid-count", - "fill-tank-caravan", - "fill-tank-target", - "empty-tank-caravan", - "empty-tank-target", + "fill-tank-until-caravan-has", + "fill-tank-until-target-has", + "empty-tank-until-caravan-has", + "empty-tank-until-target-has", } Caravan.foods = { diff --git a/scripts/caravan/gui/actions.lua b/scripts/caravan/gui/actions.lua index e932735dd..0c8fe7f75 100644 --- a/scripts/caravan/gui/actions.lua +++ b/scripts/caravan/gui/actions.lua @@ -6,7 +6,7 @@ local number_selection = require "action_widgets/number_selection" local comparator = require "action_widgets/comparator" local caravan_prototypes = require "__pyalienlife__/scripts/caravan/caravan-prototypes" -local possibly_blocking_actions = {"fill-inventory", "empty-inventory", "store-food", "store-specific-food", "load-caravan", "unload-caravan", "load-target", "unload-target", "fill-tank", "empty-tank", "fill-tank-caravan", "fill-tank-target", "empty-tank-caravan", "empty-tank-target"} +local possibly_blocking_actions = {"fill-inventory", "empty-inventory", "store-food", "store-specific-food", "load-caravan", "unload-caravan", "load-target", "unload-target", "fill-tank", "empty-tank", "fill-tank-until-caravan-has", "fill-tank-until-target-has", "empty-tank-until-caravan-has", "empty-tank-until-target-has"} local function play_stop_button_info(caravan_data, schedule_id, action_id) local schedule = caravan_data.schedule[schedule_id] @@ -39,9 +39,9 @@ function P.build_action_flow(parent, caravan_data, action, tags) elseif Utils.contains({"load-caravan", "unload-caravan", "load-target", "unload-target"}, action.type) then -- don't know why we restrict comparison here, shouldn't it be a regular dropdown? comparator.build_static_comparator_widgets(flow, action, tags, "item", nil, "=") - elseif Utils.contains({"fill-tank-caravan", "empty-tank-target"}, action.type) then + elseif Utils.contains({"fill-tank-until-caravan-has", "empty-tank-until-target-has"}, action.type) then comparator.build_static_comparator_widgets(flow, action, tags, "fluid", nil, "≥") - elseif Utils.contains({"fill-tank-target", "empty-tank-caravan"}, action.type) then + elseif Utils.contains({"fill-tank-until-target-has", "empty-tank-until-caravan-has"}, action.type) then comparator.build_static_comparator_widgets(flow, action, tags, "fluid", nil, "≤") -- these are interrupt-only elseif Utils.contains({"food-count", "caravan-item-count", "target-item-count"}, action.type) then diff --git a/scripts/caravan/impl/actions.lua b/scripts/caravan/impl/actions.lua index 0da80cb9a..d76a81cee 100644 --- a/scripts/caravan/impl/actions.lua +++ b/scripts/caravan/impl/actions.lua @@ -810,10 +810,10 @@ Caravan.actions = { ["target-fluid-count"] = P.target_fluid_count, ["is-tank-full"] = P.is_tank_full, ["is-tank-empty"] = P.is_tank_empty, - ["fill-tank-caravan"] = P.fill_tank_until_caravan_contains, - ["fill-tank-target"] = P.fill_tank_until_outpost_contains, - ["empty-tank-caravan"] = P.empty_tank_until_caravan_contains, - ["empty-tank-target"] = P.empty_tank_until_outpost_contains, + ["fill-tank-until-caravan-has"] = P.fill_tank_until_caravan_contains, + ["fill-tank-until-target-has"] = P.fill_tank_until_outpost_contains, + ["empty-tank-until-caravan-has"] = P.empty_tank_until_caravan_contains, + ["empty-tank-until-target-has"] = P.empty_tank_until_outpost_contains, } Caravan.free_actions = { -- actions that don't use fuel