diff --git a/.luacheckrc b/.luacheckrc index 49466327..6e47ae8f 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -5,7 +5,8 @@ std = "minetest+max" globals = { "pipeworks", - "luaentity" + "luaentity", + "core" } read_globals = { diff --git a/autocrafter.lua b/autocrafter.lua index 2484d8a9..e61008f8 100644 --- a/autocrafter.lua +++ b/autocrafter.lua @@ -12,6 +12,12 @@ local autocrafter_cache = {} -- Index of group items used in recipes, self-populating when accessed local group_index = {} +local devicename = "pipeworks:autocrafter" + +local fluid_type_meta_key = "pipeworks:fluid_type" +local fluid_amount_meta_key = "pipeworks:fluid_amount" +local max_fluid_meta_key = "pipeworks:max_fluid" + ---------------------- -- Helper functions -- ---------------------- @@ -130,12 +136,26 @@ local function get_fake_player(pos) return fake_player end -local function get_craft_result(items, fake_player) +local function get_craft_result(items, fake_player, fluid) local output, leftover = core.get_craft_result({method = "normal", width = 3, items = items}) output, leftover = output.item, leftover.items + local fluid_cost, leftover_fluid = nil, fluid + local is_fluid_recipe = false if output:is_empty() then - return + if not fluid then return end + local not_enough + output, leftover, fluid_cost, leftover_fluid, not_enough = pipeworks.fluid_recipes:get({items = items}, fluid) + output, leftover = output.item, leftover.items + if not_enough then return false end + if output:is_empty() then + return + end + end + + if is_fluid_recipe then + return {output}, {}, fluid_cost, leftover_fluid end + -- Execute on_craft callbacks using a fake inventory and player local fake_inv = fake_player:get_inventory() fake_inv:set_list("craft", leftover) @@ -158,7 +178,7 @@ local function get_craft_result(items, fake_player) end end end - return outputs, replacements + return outputs, replacements, fluid_cost, leftover_fluid end local function start_autocrafter(pos) @@ -183,12 +203,19 @@ local function set_craft(pos, recipe, output) start_autocrafter(pos) end +local function get_fluid(meta) + return { + amount = meta:get_float(fluid_amount_meta_key), + type = meta:get_string(fluid_type_meta_key), + } +end + local function set_craft_by_input(pos, input) local recipe = normalize_items(input) if not recipe then return end - local outputs = get_craft_result(recipe, get_fake_player(pos)) + local outputs = get_craft_result(recipe, get_fake_player(pos), {type = input.fluid_type or core.get_meta(pos):get_string(fluid_type_meta_key), amount = math.huge}) if not outputs then return end @@ -196,8 +223,10 @@ local function set_craft_by_input(pos, input) return true end -local function set_craft_by_output(pos, output) - local recipes = core.get_all_craft_recipes(output:get_name()) +local function set_craft_by_output(pos, output, fluid_type) + local output_name = output:get_name() + local recipes = core.get_all_craft_recipes(output_name) or + fluid_type and pipeworks.fluid_recipes:get_all_with(output_name, fluid_type) or pipeworks.fluid_recipes:get_all(output_name) if not recipes then return end @@ -275,14 +304,52 @@ local function get_ingredients(src, recipe) return ingredients end -local function autocraft(inv, recipe, fake_player, keep_items) +local base_formspec = table.concat({ + fs_helpers.prepends(11.5, has_digilines and 14.25 or 13.25), + fs_helpers.inv_list(1.5, has_digilines and 5.25 or 4.25, 8, 3, "src", S("Input inventory")), + fs_helpers.inv_list(1.5, 0.25, 3, 3, "recipe", S("Recipe")), + fs_helpers.inv_list(5.25, 1.5, 1, 1, "output", S("Output preview")), + "image[5.25,1.5;1,1;[combine:16x16^[noalpha^[colorize:#141318:255]", + fs_helpers.inv_list(6.5, 0.25, 4, 3, "dst", S("Crafted items")), + has_digilines and fs_helpers.field(2.75, 4.25, 7.25, "channel", S("Digiline Channel")) or "", + fs_helpers.player_inv(1.5, has_digilines and 9.25 or 8.25), +}) + +local tooltip_text = { + [0] = S("Output all items"), + [1] = S("Keep non-consumables"), + [2] = S("Keep all items"), +} + +local function update_formspec(meta) + local keep_items = meta:get_int("keep_items") + local fluid = meta:get(fluid_type_meta_key) + local amount = meta:get_float(fluid_amount_meta_key) + local fluid_cap = meta:get_float(max_fluid_meta_key) + local bar_height = 8.25 * amount / fluid_cap + local fs = table.concat({ + base_formspec, + fs_helpers.toggle_button(5.25, 0.45, meta, "enabled", true), + "image_button[5.25,2.75;1,1;pipeworks_arrow_"..keep_items..".png;keep_items;;;false;]", + "tooltip[keep_items;"..tooltip_text[keep_items].."]", + "image[0.25," .. (8.5 - bar_height) .. ";1," .. bar_height .. ";" .. (fluid and ("pipeworks_fluid_" .. fluid) or "blank") .. ".png]", + "image[0.25,0.25;1,8.25;pipeworks_fluidbar.png]", + "tooltip[0.25,0.25;1,8.25;" .. S("@1L @2", ("%d"):format(amount*1000), (pipeworks.liquids[fluid] or {description = S("Empty")}).description) .. "]", + }) + meta:set_string("formspec", fs) +end + +local function autocraft(inv, recipe, fake_player, keep_items, meta) local src = inv:get_list("src") local ingredients = get_ingredients(src, recipe) if not ingredients then return end - local outputs, replacements = get_craft_result(ingredients, fake_player) - if not outputs then + local outputs, replacements, _, leftover_fluid = get_craft_result(ingredients, fake_player, get_fluid(meta)) + if outputs == false then + -- Insufficient fluid, don't clear the output + return + elseif outputs == nil then -- Broken recipe, clear the output inv:set_stack("output", 1, "") return @@ -306,6 +373,8 @@ local function autocraft(inv, recipe, fake_player, keep_items) -- Crafting was sucessful, so the modified lists can be saved inv:set_list("src", src) inv:set_list("dst", dst) + meta:set_float(fluid_amount_meta_key, leftover_fluid.amount) + update_formspec(meta) return true end @@ -339,7 +408,7 @@ local function run_autocrafter(pos, elapsed) local keep_items = meta:get_int("keep_items") local continue = true for _=1, math.min(crafts_remaining, batch_size) do - if not autocraft(inv, recipe, fake_player, keep_items) then + if not autocraft(inv, recipe, fake_player, keep_items, meta) then crafts_remaining = 0 continue = false break @@ -357,34 +426,6 @@ local function run_autocrafter(pos, elapsed) return continue and (enabled or crafts_remaining > 0 or queued > 0) end -local base_formspec = table.concat({ - fs_helpers.prepends(10.25, has_digilines and 14.25 or 13.25), - fs_helpers.inv_list(0.25, has_digilines and 5.25 or 4.25, 8, 3, "src", S("Input inventory")), - fs_helpers.inv_list(0.25, 0.25, 3, 3, "recipe", S("Recipe")), - fs_helpers.inv_list(4, 1.5, 1, 1, "output", S("Output preview")), - "image[4,1.5;1,1;[combine:16x16^[noalpha^[colorize:#141318:255]", - fs_helpers.inv_list(5.25, 0.25, 4, 3, "dst", S("Crafted items")), - has_digilines and fs_helpers.field(1.5, 4.25, 7.25, "channel", S("Digiline Channel")) or "", - fs_helpers.player_inv(0.25, has_digilines and 9.25 or 8.25), -}) - -local tooltip_text = { - [0] = S("Output all items"), - [1] = S("Keep non-consumables"), - [2] = S("Keep all items"), -} - -local function update_formspec(meta) - local keep_items = meta:get_int("keep_items") - local fs = table.concat({ - base_formspec, - fs_helpers.toggle_button(4, 0.45, meta, "enabled", true), - "image_button[4,2.75;1,1;pipeworks_arrow_"..keep_items..".png;keep_items;;;false;]", - "tooltip[keep_items;"..tooltip_text[keep_items].."]", - }) - meta:set_string("formspec", fs) -end - local function receive_fields(pos, _, fields, player) if (fields.quit and not fields.key_enter_field) or not pipeworks.may_configure(pos, player) then return @@ -627,7 +668,7 @@ local function digilines_action(pos, _, channel, msg) end end -core.register_node("pipeworks:autocrafter", { +core.register_node(devicename, { description = S("Autocrafter"), drawtype = "normal", tiles = {"pipeworks_autocrafter.png"}, @@ -653,6 +694,7 @@ core.register_node("pipeworks:autocrafter", { on_construct = function(pos) local meta = core.get_meta(pos) meta:set_int("keep_items", 1) + meta:set_float(max_fluid_meta_key, 8) local inv = meta:get_inventory() inv:set_size("src", 3 * 8) inv:set_size("recipe", 3 * 3) @@ -757,7 +799,7 @@ core.register_node("pipeworks:autocrafter", { update_formspec(core.get_meta(pos)) end, }) -table.insert(pipeworks.ui_cat_tube_list, "pipeworks:autocrafter") +table.insert(pipeworks.ui_cat_tube_list, devicename) ---------------------- -- Group index code -- @@ -844,3 +886,28 @@ end -- Use a metatable to make access cleaner setmetatable(group_index, {__index = find_group}) + +------------------------- +-- Fluid functionality -- +------------------------- + +pipeworks.flowables.register.simple(devicename) +pipeworks.flowables.register.output(devicename, 0, 0, function(pos, node, currentpressure, finitemode, fluid_type) + if fluid_type == nil then return 0, fluid_type end + local meta = core.get_meta(pos) + local fluid_cap = meta:get_float(max_fluid_meta_key) + local fluid_amount = meta:get_float(fluid_amount_meta_key) + local current_fluid_type = meta:get(fluid_type_meta_key) + if current_fluid_type ~= fluid_type then + if fluid_amount == 0 then + meta:set_string(fluid_type_meta_key, fluid_type) + set_craft_by_input(pos, core.get_meta(pos):get_inventory():get_list("recipe")) + else + return 0, fluid_type + end + end + local taken = math.min(fluid_cap - fluid_amount, currentpressure) + meta:set_float(fluid_amount_meta_key, fluid_amount + taken) + update_formspec(meta) + return taken, fluid_type +end, function()end) diff --git a/autodetect-finite-water.lua b/autodetect-finite-water.lua index 969345b0..4628828c 100644 --- a/autodetect-finite-water.lua +++ b/autodetect-finite-water.lua @@ -1,4 +1,4 @@ --- enable finite liquid in the presence of dynamic liquid to preserve water volume. +-- enable finite liquid in the presence of dynamic liquid to preserve fluid volume. local enable = false if core.get_modpath("dynamic_liquid") then @@ -6,4 +6,4 @@ if core.get_modpath("dynamic_liquid") then enable = true end -pipeworks.toggles.finite_water = enable +pipeworks.toggles.finite = enable diff --git a/common.lua b/common.lua index b4218071..e8dda5ff 100644 --- a/common.lua +++ b/common.lua @@ -58,8 +58,12 @@ else } end -pipeworks.liquid_texture = core.registered_nodes[pipeworks.liquids.water.flowing].tiles[1] -if type(pipeworks.liquid_texture) == "table" then pipeworks.liquid_texture = pipeworks.liquid_texture.name end +if pipeworks.liquids.water then + pipeworks.liquid_texture = core.registered_nodes[pipeworks.liquids.water.flowing].tiles[1] + if type(pipeworks.liquid_texture) == "table" then pipeworks.liquid_texture = pipeworks.liquid_texture.name end +else + pipeworks.liquid_texture = "blank.png" +end -- Helper functions @@ -200,6 +204,121 @@ function pipeworks.table_recursive_replace(tbl, pattern, replace_with) end end +------------------------ +-- Formspec functions -- +------------------------ + +local fs_helpers = {} +pipeworks.fs_helpers = fs_helpers +function fs_helpers.on_receive_fields(pos, fields) + local meta = core.get_meta(pos) + for field in pairs(fields) do + if field:match("^fs_helpers_cycling:") then + local l = field:split(":") + local new_value = tonumber(l[2]) + local meta_name = l[3] + meta:set_int(meta_name, new_value) + end + end +end + +function fs_helpers.cycling_button(meta, base, meta_name, values) + local current_value = meta:get_int(meta_name) + local new_value = (current_value + 1) % (#values) + local val = values[current_value + 1] + local text + local texture_name = nil + local addopts = nil + --when we get a table, we know the caller wants an image_button + if type(val) == "table" then + text = val["text"] + texture_name = val["texture"] + addopts = val["addopts"] + else + text = val + end + local field = "fs_helpers_cycling:"..new_value..":"..meta_name + return base..";"..(texture_name and texture_name..";" or "")..field..";"..core.formspec_escape(text)..(addopts and ";"..addopts or "").."]" +end + +function fs_helpers.get_inv(y,x) + local x = x or 0 + local fs = {} + if core.get_modpath("i3") then + local inv_x = (i3.settings.legacy_inventory and 0.75 or 0.22) + x + + local inv_y = (y + 0.4) or 6.9 + local size, spacing = 1, 0.1 + local hotbar_len = i3.settings.hotbar_len or (i3.settings.legacy_inventory and 8 or 9) + local hotbar_width = i3.settings.inv_size and (i3.settings.inv_size / hotbar_len) or 4 + --local inv_size = i3.settings.inv_size or (hotbar_len * hotbar_width) is useless + + table.insert(fs, "style_type[box;colors=#77777710,#77777710,#777,#777]") + + for i = 0, hotbar_len - 1 do + table.insert(fs, "box["..(inv_x + (i * (spacing + size)))..","..inv_y..";"..size..","..size..";]") + end + + table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") + table.insert(fs, "list[current_player;main;"..inv_x..","..inv_y..";"..hotbar_len..",1;]") + + table.insert(fs, "style_type[box;colors=#666]") + for i=0, 2 do + for j=0, hotbar_len - 1 do + table.insert(fs, "box["..(inv_x + j * (spacing + size))..","..(inv_y + size + spacing + 0.05) + (i * (spacing + size))..";"..size..","..size..";]") + end + end + + table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") + table.insert(fs, "list[current_player;main;"..inv_x..","..(inv_y + 1.15)..";"..hotbar_len..","..hotbar_width..";"..hotbar_len.."]") + elseif core.get_modpath("mcl_formspec") then + local inv_x = 0.22 + x + local inv_y = (y + 0.4) or 6.9 + local size, spacing = 1, 0.1 + local hotbar_len = 9 + local inv_size = hotbar_len * 4 + + table.insert(fs, "style_type[box;colors=#77777710,#77777710,#777,#777]") + + for i = 0, hotbar_len - 1 do + table.insert(fs, "box["..(inv_x + (i * (spacing + size)))..","..inv_y..";"..size..","..size..";]") + end + + table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") + table.insert(fs, "list[current_player;main;"..inv_x..","..inv_y..";"..hotbar_len..",1;]") + + table.insert(fs, "style_type[box;colors=#666]") + for i=0, 2 do + for j=0, hotbar_len - 1 do + table.insert(fs, "box["..(inv_x + j * (spacing + size))..","..(inv_y + size + spacing + 0.05) + (i * (spacing + size))..";"..size..","..size..";]") + end + end + + table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") + table.insert(fs, "list[current_player;main;"..inv_x..","..(inv_y + 1.15)..";"..hotbar_len..","..(inv_size / hotbar_len)..";"..hotbar_len.."]") + else + table.insert(fs, "list[current_player;main;".. (0.22 + x) ..","..y..";8,4;]") + end + + return table.concat(fs, "") +end + +function fs_helpers.get_prepends(size) + local prepend = {} + + if core.get_modpath("i3") then + prepend = { + "no_prepend[]", + "bgcolor[black;neither]", + "background9[0,0;"..size..";i3_bg_full.png;false;10]", + "style_type[button;border=false;bgimg=[combine:16x16^[noalpha^[colorize:#6b6b6b]", + "listcolors[#0000;#ffffff20]" + } + end + + return table.concat(prepend, "") +end + --------- -- Env -- --------- diff --git a/crafts.lua b/crafts.lua index 072801f8..8aa9c765 100644 --- a/crafts.lua +++ b/crafts.lua @@ -316,3 +316,71 @@ if pipeworks.enable_node_breaker then } }) end + +pipeworks.specialized_fluid_containers = { + register = function(self, container, fluid, remainder) -- fluid = {type = "type", amount = 1000} + local container_def = core.registered_items[container] + assert(container_def, "Container \"" .. container .. "\" doesn't exist!") + local remainder_def + if remainder then + remainder_def = core.registered_items[remainder] + assert(remainder_def, "Remainder \"" .. container .. "\" doesn't exist!") + end + + self[container_def.name] = {remainder = remainder and remainder_def.name, fluid = table.copy(fluid)} + if remainder then + self.fill[remainder_def.name] = self.fill[remainder_def.name] or {} + self.fill[remainder_def.name][fluid.type] = {result = container_def.name, fluid = table.copy(fluid)} + end + + return true + end, + + fill = {}, +} + +if pipeworks.enable_autocrafter then + +local buckets = {} +buckets.air = materials.empty_bucket +buckets.water = materials.water_bucket +if core.get_modpath("bucket") then + buckets.river_water = "bucket:bucket_river_water" + buckets.lava = "bucket:bucket_lava" +elseif core.get_modpath("mcl_buckets") then + buckets.river_water = "mcl_buckets:bucket_river_water" + buckets.lava = "mcl_buckets:bucket_lava" +end + +if buckets.air and buckets.water then + local def = { + fluid = {type = "water", amount = 1}, + output = ItemStack(buckets.water), + items = { + buckets.air, "", "", "", "", "", "", "", "" + } + } + pipeworks.fluid_recipes:register(def) +end +if buckets.air and buckets.lava then + local def = { + fluid = {type = "lava", amount = 1}, + output = ItemStack(buckets.lava), + items = { + buckets.air, "", "", "", "", "", "", "", "" + } + } + pipeworks.fluid_recipes:register(def) +end +if buckets.river_water and buckets.lava then + local def = { + fluid = {type = "river_water", amount = 1}, + output = ItemStack(buckets.river_water), + items = { + buckets.air, "", "", "", "", "", "", "", "" + } + } + pipeworks.fluid_recipes:register(def) +end + +end diff --git a/default_settings.lua b/default_settings.lua index 39889a5f..b8342a7a 100644 --- a/default_settings.lua +++ b/default_settings.lua @@ -44,25 +44,32 @@ pipeworks.toggles = {} -- set to one of the following strings. -- "classic": classic mode written by VanessaE -- "pressure": pressure metadata based, written by thetaepsilon. --- has caveats such as water speed issues though. +-- has caveats such as fluid speed issues though. -- setting to nil inhibits all flow logic, useful for debugging ABM crashes, -- or for rendering the pipes purely decorative. ]] -pipeworks.toggles.pipe_mode = "classic" +pipeworks.toggles.pipe_mode = "pressure" --[[ --- force-enable finite water handling mode. --- this changes the way that water node placement is handled; +-- force-enable finite fluid handling mode. +-- this changes the way that fluid node placement is handled; -- volume will always be preserved, --- and water is assumed to move itself downwards. +-- and fluid is assumed to move itself downwards. -- nil (the default) means autodetect from installed finite liquid mods, -- true is force-on, false is force-off. -- note that you should NOT normally explicitly set this to true/false, -- unless the mod you want this for is not covered by auto-detection -- (please see autodetect-finite-water.lua). --- please file an issue if you have a finite water mod not covered there, +-- please file an issue if you have a finite fluid mod not covered there, -- and feel it necessary to explicitly set this toggle -pipeworks.toggles.finite_water = nil +pipeworks.toggles.finite = nil ]] +-- casewise finite fluid toggles +-- can be overriden by setting toggles.finite to true +pipeworks.toggles.finites = { + water = false, + river_water = true, + lava = true, +} for name, value in pairs(settings) do local setting_type = type(value) diff --git a/devices.lua b/devices.lua index b0a73f11..84d05d2f 100644 --- a/devices.lua +++ b/devices.lua @@ -6,6 +6,9 @@ local texture_alpha_mode = core.features.use_texture_alpha_string_modes local polys = "" if pipeworks.enable_lowpoly then polys = "_lowpoly" end +local fluid_type_meta_key = "pipeworks:fluid_type" +local fluid_pressure_meta_key = "pipeworks.pressure" + -- rotation handlers function pipeworks.fix_after_rotation(pos, node, user, mode, new_param2) @@ -127,6 +130,64 @@ local pipes_devicelist = { -- Now define the nodes. +local sourcename = "pipeworks:source" +local fluid_amount_meta_key = "pipeworks:fluid_amount" +core.register_node(sourcename, { + description = S("Infinite Fluid Source"), + tiles = { "pipeworks_source_frame.png" }, + paramtype = "light", + groups = {snappy=3, pipe=1, dig_generic = 4, axey=1, handy=1, pickaxey=1}, + is_ground_content = false, + _mcl_hardness=0.8, + _sound_def = { + key = "node_sound_metal_defaults", + }, + walkable = true, + pipe_connections = { top = 1, bottom = 1, front = 1, back = 1, left = 1, right = 1 }, + on_receive_fields = function(pos, _, fields, sender) + local playername = sender:get_player_name() + if core.is_protected(pos, playername) then + core.record_protection_violation(pos, playername) + core.chat_send_player(playername, S("Changes not saved, node protected!")) + return + end + if not core.is_creative_enabled(playername) then + core.chat_send_player(playername, S("Changes not saved, \"creative\" priv missing!")) + return + end + local meta = core.get_meta(pos) + local newpressure = fields.pressure + if newpressure then + meta:set_string(fluid_amount_meta_key, newpressure) + end + local newtype = (pipeworks.liquids[fields.fluidtype] and fields.fluidtype) + if newtype then + meta:set_string(fluid_type_meta_key, newtype) + end + end, + on_construct = function(pos) + local meta = core.get_meta(pos) + local fs = + "formspec_version[4]".. + "size[4.75,1.5]".. + "field[0.25,0.5;2.5,0.75;fluidtype;"..S("Fluid Type")..";${"..fluid_type_meta_key.."}]".. + "field[3,0.5;1.5,0.75;pressure;"..S("Pressure")..";${"..fluid_amount_meta_key.."}]" + meta:set_string("formspec",fs) + end, + after_place_node = function(pos) + pipeworks.scan_for_pipe_objects(pos) + end, + after_dig_node = function(pos) + pipeworks.scan_for_pipe_objects(pos) + end +}) + +new_flow_logic_register.simple(sourcename) +new_flow_logic_register.intake(sourcename, math.huge, function(pos) + local meta = core.get_meta(pos) + return meta:get_float(fluid_amount_meta_key) - meta:get_float(fluid_pressure_meta_key), meta:get(fluid_type_meta_key) +end) + local states = { "on", "off" } for s in ipairs(states) do @@ -370,7 +431,7 @@ core.register_node(nodename_spigot_loaded, { drawtype = "mesh", mesh = "pipeworks_spigot_pouring"..polys..".obj", tiles = { - core.registered_nodes[pipeworks.liquids.water.source].tiles[1], + pipeworks.liquids.water and pipeworks.liquids.water.def.tiles[1] or "blank.png", { name = "pipeworks_spigot.png" } }, use_texture_alpha = texture_alpha_mode and "blend" or true, @@ -579,7 +640,17 @@ new_flow_logic_register.directional_horizonal_rotate(nodename_sensor_loaded, tru -- activate flow sensor at roughly half the pressure pumps drive pipes local sensor_pressure_set = { { nodename_sensor_empty, 0.0 }, { nodename_sensor_loaded, 1.0 } } new_flow_logic_register.transition_simple_set(sensor_pressure_set, { mesecons=pipeworks.mesecons_rules }) - +core.register_abm({ + label = "flow sensor info", + nodenames = { "pipeworks:flow_sensor_empty", "pipeworks:flow_sensor_loaded" }, + interval = 1, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + local meta = core.get_meta(pos) + local fluid_def = pipeworks.liquids[meta:get(fluid_type_meta_key)] + meta:set_string("infotext", S("@1: @2", (fluid_def and fluid_def.description) or S("none"), (meta:get(fluid_pressure_meta_key) or 0))) + end +}) -- tanks diff --git a/flowing_logic.lua b/flowing_logic.lua index 35d8a5ad..92effcef 100644 --- a/flowing_logic.lua +++ b/flowing_logic.lua @@ -4,7 +4,7 @@ -- Contributed by mauvebic, 2013-01-03, rewritten a bit by Vanessa Ezekowitz -- -local finitewater = core.settings:get_bool("liquid_finite") +local finite = core.settings:get_bool("liquid_finite") pipeworks.check_for_liquids = function(pos) local coords = { @@ -16,12 +16,12 @@ pipeworks.check_for_liquids = function(pos) {x=pos.x,y=pos.y,z=pos.z+1}, } for i =1,6 do local name = core.get_node(coords[i]).name - if name and string.find(name,"water") then - if finitewater then core.remove_node(coords[i]) end - return true + if name and pipeworks.fluid_types[name] then + if finite then core.remove_node(coords[i]) end + return pipeworks.fluid_types[name] end end - return false + return nil end pipeworks.check_for_inflows = function(pos,node) @@ -83,7 +83,7 @@ end pipeworks.spigot_check = function(pos, node) local belowname = core.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name - if belowname and (belowname == "air" or belowname == pipeworks.liquids.water.flowing or belowname == pipeworks.liquids.water.source) then + if belowname and (belowname == "air" or core.registered_nodes[belowname].liquid_alternative_flowing) then local spigotname = core.get_node(pos).name local fdir=node.param2 % 4 local check = { @@ -96,14 +96,14 @@ pipeworks.spigot_check = function(pos, node) if near_node and string.find(near_node.name, "_loaded") then if spigotname and spigotname == "pipeworks:spigot" then core.add_node(pos,{name = "pipeworks:spigot_pouring", param2 = fdir}) - if finitewater or belowname ~= pipeworks.liquids.water.source then + if finite or belowname ~= pipeworks.liquids.water.source then core.add_node({x=pos.x,y=pos.y-1,z=pos.z},{name = pipeworks.liquids.water.source}) end end else if spigotname == "pipeworks:spigot_pouring" then core.add_node({x=pos.x,y=pos.y,z=pos.z},{name = "pipeworks:spigot", param2 = fdir}) - if belowname == pipeworks.liquids.water.source and not finitewater then + if belowname == pipeworks.liquids.water.source and not finite then core.remove_node({x=pos.x,y=pos.y-1,z=pos.z}) end end @@ -119,14 +119,14 @@ pipeworks.fountainhead_check = function(pos, node) if near_node and string.find(near_node.name, "_loaded") then if fountainhead_name and fountainhead_name == "pipeworks:fountainhead" then core.add_node(pos,{name = "pipeworks:fountainhead_pouring"}) - if finitewater or abovename ~= pipeworks.liquids.water.source then + if finite or abovename ~= pipeworks.liquids.water.source then core.add_node({x=pos.x,y=pos.y+1,z=pos.z},{name = pipeworks.liquids.water.source}) end end else if fountainhead_name == "pipeworks:fountainhead_pouring" then core.add_node({x=pos.x,y=pos.y,z=pos.z},{name = "pipeworks:fountainhead"}) - if abovename == pipeworks.liquids.water.source and not finitewater then + if abovename == pipeworks.liquids.water.source and not finite then core.remove_node({x=pos.x,y=pos.y+1,z=pos.z}) end end diff --git a/fluid_recipes.lua b/fluid_recipes.lua new file mode 100644 index 00000000..1478747d --- /dev/null +++ b/fluid_recipes.lua @@ -0,0 +1,163 @@ +local S = core.get_translator("pipeworks") + +pipeworks.fluid_recipes = { + trie = {} +} + +--[[def = {items = { + +}, +output = out|{outs}, -- Itemstacks +fluid = { + type = , + amount = +}} +]] +pipeworks.fluid_recipes.register = function(self, def) + if def.output == nil then return end + if def.items == nil then return end + local newdef = { + items = {}, + fluid = def.fluid, + shaped = def.shaped + } + local path = self.trie + + for _,v in ipairs(def.items) do + if type(v) == "table" then + for _,w in ipairs(v) do + newdef.items[#newdef.items + 1] = w + local child = {} + if path[w] then + child = path[w] + end + path[w] = child + path = child + end + else + newdef.items[#newdef.items + 1] = v + local child = {} + if path[v] then + child = path[v] + end + path[v] = child + path = child + end + end + + if core.get_modpath("unified_inventory") then + unified_inventory.register_craft({ + output = def.output, + type = "fluidshaped", + items = table.copy(newdef.items), + fluid = newdef.fluid, + width = 3, + }) + end + + if type(def.output) == "table" then + newdef.output = def.output + else + newdef.output = {item = def.output} + end + + if not path.fluid then path.fluid = {} end + if not path.output then path.output = {} end + + path.fluid[newdef.fluid.type] = newdef.fluid + path.output[newdef.fluid.type] = newdef.output + path.tail = true + self[#self + 1] = newdef +end + +local check_fluid_sufficiency = function(input, req) + if not req then return true end + if req.amount <= 0 then return true end + if not input then return false end + return input.type == req.type and input.amount >= req.amount +end + +--[[ +input = {items = }, +fluid = +]] +pipeworks.fluid_recipes.get = function(self, input, fluid) + local path = self.trie + local empty = {item = ItemStack("")} + local emptyfluid = {type = "", amount = 0} + local dec_input = { + items = table.copy(input.items) + } + for k,v in ipairs(dec_input.items) do + path = path[v:get_name()] + if path == nil then return empty, input, emptyfluid, fluid end + dec_input.items[k] = ItemStack(v) + dec_input.items[k]:set_count(v:get_count()-1) + if path == nil then return empty, input, emptyfluid, fluid end + if path.tail then + local fluid_type = fluid.type + if path.output[fluid_type] then + local fluidcost = path.fluid[fluid_type] + if not check_fluid_sufficiency(fluid, fluidcost) then return empty, input, emptyfluid, fluid, true end + return path.output[fluid_type], dec_input, fluidcost, {type = fluid_type, amount = fluid.amount - fluidcost.amount} + else + return empty, input, emptyfluid, fluid + end + end + end + return empty, input, emptyfluid, fluid +end + +-- name = +-- fluid_type = +pipeworks.fluid_recipes.get_all_with = function(self, name, fluid_type) + local out = {} + local n = 0 + for _,v in ipairs(self) do + if v.fluid.type == fluid_type and v.output.item:get_name() == name then + n = n + 1 + out[n] = v + end + end + return out +end + +-- name = +pipeworks.fluid_recipes.get_all = function(self, name) + local out = {} + local n = 0 + for _,v in ipairs(self) do + if v.output.item:get_name() == name then + n = n + 1 + out[n] = v + end + end + return out +end + +local format_fluid_amount = function(amount) + return (amount * 1000) .. "L" +end + +if core.global_exists("unified_inventory") then + unified_inventory.register_craft_type("fluidshaped", { + description = S("Shaped Fluid Craft"), + icon = "pipeworks_autocrafter.png", + width = 3, + height = 4, + }) + + local reqstack = ItemStack("air") + local meta = reqstack:get_meta() + meta:set_string("count_meta","FLUID\nREQ:") + meta:set_int("count_alignment",10) + meta:set_string("inventory_image","blank.png") + + unified_inventory.register_on_craft_registered(function (_, options) + if options.type ~= "fluidshaped" then return end + options.items[10] = reqstack:to_string() + local fluidstack = ItemStack(pipeworks.liquids[options.fluid.type].source) + fluidstack:get_meta():set_string("count_meta", format_fluid_amount(options.fluid.amount)) + options.items[11] = fluidstack:to_string() + end) +end diff --git a/init.lua b/init.lua index 6d046277..8bb8057b 100644 --- a/init.lua +++ b/init.lua @@ -1,3 +1,5 @@ +local S = core.get_translator("pipeworks") + -- Pipeworks mod by Vanessa Ezekowitz - 2013-07-13 -- -- This mod supplies various steel pipes and plastic pneumatic tubes @@ -8,18 +10,29 @@ pipeworks = { ui_cat_tube_list = {}, worldpath = core.get_worldpath(), modpath = core.get_modpath("pipeworks"), - liquids = { - water = { - source = core.registered_nodes["mapgen_water_source"].name, - flowing = core.registered_nodes["mapgen_water_source"].liquid_alternative_flowing - }, - river_water = { - source = core.registered_nodes["mapgen_river_water_source"].name, - flowing = core.registered_nodes["mapgen_river_water_source"].liquid_alternative_flowing + register_fluid = function(self, alias, name, density, description) + local def = core.registered_nodes[alias] + if not def then return false end + self.liquids[name] = { + def = def, + source = def.liquid_alternative_source, + description = description or string.gsub(def.description, "%s?".."Source".."%s?", ""), + flowing = def.liquid_alternative_flowing, + density = density, -- in g/cm³ as standard } - } + self.fluid_types[def.liquid_alternative_source] = name + return true + end, + liquids = {}, + fluid_types = {}, -- easier indexing + gravity = vector.new(0, -0.025, 0), -- pressure bias factor, unit unspecified } +-- fluid registration +pipeworks:register_fluid("mapgen_water_source", "water", 1.05, S("Water")) +pipeworks:register_fluid("mapgen_river_water_source", "river_water", 1, S("River Water")) +pipeworks:register_fluid("mapgen_lava_source", "lava", 4, S("Lava")) + dofile(pipeworks.modpath.."/default_settings.lua") -- Read the external config file if it exists. local worldsettingspath = pipeworks.worldpath.."/pipeworks_settings.txt" @@ -42,8 +55,8 @@ end ------------------------------------------- -- Load the various other parts of the mod --- early auto-detection for finite water mode if not explicitly disabled -if pipeworks.toggles.finite_water == nil then +-- early auto-detection for finite fluids mode if not explicitly disabled +if pipeworks.toggles.finite == nil then dofile(pipeworks.modpath.."/autodetect-finite-water.lua") end @@ -51,6 +64,17 @@ if core.get_modpath("signs_lib") then dofile(pipeworks.modpath.."/signs_compat.lua") end +local logicdir = "/pressure_logic/" + +-- note that even with these files the new flow logic is not yet default. +-- registration will take place but no actual ABMs/node logic will be installed, +-- unless the toggle flag is specifically enabled in the per-world settings flag. +-- -- disregard previous comments, new flow logic is default now +dofile(pipeworks.modpath..logicdir.."flowable_node_registry.lua") +dofile(pipeworks.modpath..logicdir.."abms.lua") +dofile(pipeworks.modpath..logicdir.."abm_register.lua") +dofile(pipeworks.modpath..logicdir.."flowable_node_registry_install.lua") + dofile(pipeworks.modpath.."/common.lua") dofile(pipeworks.modpath.."/fs_helpers.lua") dofile(pipeworks.modpath.."/models.lua") @@ -58,7 +82,9 @@ dofile(pipeworks.modpath.."/autoplace_pipes.lua") dofile(pipeworks.modpath.."/autoplace_tubes.lua") dofile(pipeworks.modpath.."/luaentity.lua") dofile(pipeworks.modpath.."/item_transport.lua") -dofile(pipeworks.modpath.."/flowing_logic.lua") +if pipeworks.toggles.pipe_mode == "classic" then + dofile(pipeworks.modpath.."/flowing_logic.lua") +end dofile(pipeworks.modpath.."/filter-injector.lua") dofile(pipeworks.modpath.."/chests.lua") dofile(pipeworks.modpath.."/trashcan.lua") @@ -81,16 +107,6 @@ if pipeworks.enable_sand_tube or pipeworks.enable_mese_sand_tube then dofile(pipeworks.modpath.."/tubes/vacuum.lua") end -local logicdir = "/pressure_logic/" - --- note that even with these files the new flow logic is not yet default. --- registration will take place but no actual ABMs/node logic will be installed, --- unless the toggle flag is specifically enabled in the per-world settings flag. -dofile(pipeworks.modpath..logicdir.."flowable_node_registry.lua") -dofile(pipeworks.modpath..logicdir.."abms.lua") -dofile(pipeworks.modpath..logicdir.."abm_register.lua") -dofile(pipeworks.modpath..logicdir.."flowable_node_registry_install.lua") - if pipeworks.enable_pipes then dofile(pipeworks.modpath.."/pipes.lua") end @@ -107,6 +123,7 @@ if pipeworks.enable_redefines and core.get_modpath("mcl_furnaces") then dofile(pipeworks.modpath.."/mcl_furnaces.lua") end if pipeworks.enable_autocrafter then + dofile(pipeworks.modpath.."/fluid_recipes.lua") dofile(pipeworks.modpath.."/autocrafter.lua") end diff --git a/locale/template.txt b/locale/template.txt index b9c5293f..e9081700 100644 --- a/locale/template.txt +++ b/locale/template.txt @@ -43,6 +43,8 @@ Expansion Tank (@1)= Fluid Storage Tank (@1)= Fountainhead= Straight-only Pipe= +Infinite Fluid Source= +@1L @2= ## filter-injector (slot #@1 next)= @@ -117,3 +119,10 @@ Adjustable Vacuuming Pneumatic Tube Segment (@1m)= Node Breaker= Deployer= Dispenser= + +## fluids +Empty= +Water= +River Water= +Lava= + diff --git a/mod.conf b/mod.conf index a0507e00..7531d463 100644 --- a/mod.conf +++ b/mod.conf @@ -1,5 +1,5 @@ name = pipeworks description = This mod uses mesh nodes and nodeboxes to supply a complete set of 3D pipes and tubes, along with devices that work with them. depends = basic_materials, xcompat, fakelib -optional_depends = mesecons, mesecons_mvps, digilines, signs_lib, unified_inventory, default, screwdriver, fl_mapgen, sound_api, i3, hades_core, hades_furnaces, hades_chests, mcl_mapgen_core, mcl_barrels, mcl_furnaces, mcl_experience, vizlib, mapgen +optional_depends = aom_liquids, mesecons, mesecons_mvps, digilines, signs_lib, unified_inventory, default, screwdriver, fl_mapgen, sound_api, i3, hades_core, hades_furnaces, hades_chests, mcl_mapgen_core, mcl_barrels, mcl_furnaces, mcl_experience, vizlib, mapgen, bucket, mcl_buckets, nc_terrain min_minetest_version = 5.5.0 diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index b87aef87..687b5456 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -33,28 +33,38 @@ end +local label_pressure = "pipeworks.pressure" -- new version of liquid check --- accepts a limit parameter to only delete water blocks that the receptacle can accept, +-- accepts a limit parameter to only delete fluid blocks that the receptacle can accept, -- and returns it so that the receptacle can update it's pressure values. -local check_for_liquids_v2 = function(pos, limit) +local check_for_liquids_v2 = function(pos, limit, current_type) + if limit == 0 then return 0, current_type end local coords = make_coords_offsets(pos, false) local total = 0 + local fluid_type = (core.get_meta(pos):get_float(label_pressure) > 0.001) and current_type + local fluid_nodename = fluid_type and pipeworks.liquids[current_type].source for _, tpos in ipairs(coords) do - if total >= limit then break end local name = core.get_node(tpos).name - if name == pipeworks.liquids.water.source then - core.remove_node(tpos) - total = total + 1 + if name == core.registered_nodes[name].liquid_alternative_source then -- if node is fluid source + if not fluid_type then + fluid_type = pipeworks.fluid_types[name] + if fluid_type then fluid_nodename = name end + end + if name == fluid_nodename then + core.remove_node(tpos) + total = total + 1 + if total >= limit then break end + end end end --pipeworks.logger("check_for_liquids_v2@"..formatvec(pos).." total "..total) - return total + + return total, fluid_type end flowlogic.check_for_liquids_v2 = check_for_liquids_v2 -local label_pressure = "pipeworks.water_pressure" local get_pressure_access = function(pos) local metaref = core.get_meta(pos) return { @@ -67,6 +77,18 @@ local get_pressure_access = function(pos) } end +local label_type = "pipeworks.fluid_type" +local get_fluid_type_access = function(pos) + local metaref = core.get_meta(pos) + return { + get = function() + return metaref:get(label_type) + end, + set = function(v) + metaref:set_string(label_type, v) + end + } +end -- logging is unreliable when something is crashing... --[[ @@ -79,47 +101,53 @@ end -local finitemode = pipeworks.toggles.finite_water +local finitemode = pipeworks.toggles.finite +local finites = pipeworks.toggles.finites flowlogic.run = function(pos, node) local nodename = node.name -- get the current pressure value. local nodepressure = get_pressure_access(pos) local currentpressure = nodepressure.get() local oldpressure = currentpressure + local nodefluidtype = get_fluid_type_access(pos) + local currentfluidtype = nodefluidtype.get() + local finitemode = finitemode or finites[currentfluidtype] -- if node is an input: run intake phase local inputdef = pipeworks.flowables.inputs.list[nodename] if inputdef then - currentpressure = flowlogic.run_input(pos, node, currentpressure, inputdef) + currentpressure, currentfluidtype = flowlogic.run_input(pos, node, currentpressure, inputdef, currentfluidtype) --debuglog("post-intake currentpressure is "..currentpressure) --nilexplode("run()", "currentpressure", currentpressure) end -- balance pressure with neighbours - currentpressure = flowlogic.balance_pressure(pos, node, currentpressure) + currentpressure, currentfluidtype = flowlogic.balance_pressure(pos, node, currentpressure, currentfluidtype) -- if node is an output: run output phase local outputdef = pipeworks.flowables.outputs.list[nodename] if outputdef then - currentpressure = flowlogic.run_output( + currentpressure, currentfluidtype = flowlogic.run_output( pos, node, currentpressure, oldpressure, outputdef, - finitemode) + finitemode, + currentfluidtype) end -- if node has pressure transitions: determine new node if pipeworks.flowables.transitions.list[nodename] then - local newnode = flowlogic.run_transition(node, currentpressure) + local newnode = flowlogic.run_transition(node, currentpressure, currentfluidtype) --pipeworks.logger("flowlogic.run()@"..formatvec(pos).." transition, new node name = "..dump(newnode).." pressure "..tostring(currentpressure)) core.swap_node(pos, newnode) flowlogic.run_transition_post(pos, newnode) end - -- set the new pressure + -- set the new pressure and type nodepressure.set(currentpressure) + nodefluidtype.set(currentfluidtype) end @@ -153,14 +181,17 @@ local get_neighbour_positions = function(pos, node) -- then, check each possible neighbour to see if they can be reached from this node. local connections = {} + local tconnections = {} + local offsets = {} for _, offset in ipairs(candidates) do local npos = vector.add(pos, offset) local neighbour = core.get_node(npos) local nodename = neighbour.name local is_simple = (pipeworks.flowables.list.simple[nodename]) if is_simple then - local n = get_pressure_access(npos) - table.insert(connections, n) + table.insert(connections, get_pressure_access(npos)) + table.insert(tconnections, get_fluid_type_access(npos)) + table.insert(offsets, offset) else -- if target node is also directional, check if it agrees it can flow in that direction local directional = pipeworks.flowables.list.directional[nodename] @@ -171,104 +202,182 @@ local get_neighbour_positions = function(pos, node) local result = directional.directionfn(neighbour, towards_origin) --pipeworks.logger(dname.."result: "..tostring(result)) if result then - local n = get_pressure_access(npos) - table.insert(connections, n) + table.insert(connections, get_pressure_access(npos)) + table.insert(tconnections, get_fluid_type_access(npos)) + table.insert(offsets, offset) end end end end - return connections + return connections, tconnections, offsets end -flowlogic.balance_pressure = function(pos, node, currentpressure) +flowlogic.balance_pressure = function(pos, node, currentpressure, currentfluidtype) + if not pipeworks.liquids[currentfluidtype] then return 0, nil end -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." " -- check the pressure of all nearby flowable nodes, and average it out. -- unconditionally include self in nodes to average over. -- result of averaging will be returned as new pressure for main flow logic callback - local totalv = currentpressure - local totalc = 1 -- pressure handles to average over - local connections = get_neighbour_positions(pos, node) + local connections, tconnections, offsets = get_neighbour_positions(pos, node) + + if #connections == 0 then return currentpressure, currentfluidtype end + + local total_pressure = currentpressure + + -- cached values + local pressures = {} + local fluid_types = {} + local biases = {} + + -- same-type connections + local migrate = {} + + -- connections that force current fluid out + local intrusive = {} + + for k, connection in ipairs(connections) do + -- cache values + pressures[k] = connection.get() + fluid_types[k] = tconnections[k].get() + biases[k] = (pipeworks.liquids[fluid_types[k]] and (pressures[k] * pipeworks.liquids[fluid_types[k]].density * vector.dot(pipeworks.gravity, offsets[k]))) or 0 -- get gravitational bias + biases[k] = math.max(math.min(biases[k], pressures[k]), -pressures[k]) + if biases[k] then pressures[k] = pressures[k] - biases[k] end -- apply bias + if (not fluid_types[k]) or (pressures[k] < 0.001) or currentfluidtype == fluid_types[k] then + -- get same-type connections + migrate[#migrate + 1] = k + -- for each neighbour, add neighbour's pressure to the total to balance out + if currentfluidtype == fluid_types[k] then total_pressure = total_pressure + pressures[k] end + elseif pressures[k] > (2 * currentpressure) then -- check for high-pressure "intrusive" connections + intrusive[#intrusive + 1] = k + end + end - -- for each neighbour, add neighbour's pressure to the total to balance out - for _, neighbour in ipairs(connections) do - local n = neighbour.get() - totalv = totalv + n - totalc = totalc + 1 + if #migrate == 0 then return currentpressure, currentfluidtype end + + -- average values + local pressure_count = #migrate + 1 + local currentpressure = total_pressure / pressure_count + for k, migrated in ipairs(migrate) do + pressures[migrated] = currentpressure + currentfluidtype = currentfluidtype or fluid_types[migrated] + fluid_types[migrated] = currentfluidtype end - local average = totalv / totalc - for _, target in ipairs(connections) do - target.set(average) + + if #intrusive > 0 then + -- take from most "intrusive" connection + local add = currentpressure / #migrate + if #intrusive == 1 then + local intruder = intrusive[1] + -- put current fluid in migration + for _, migrated in ipairs(migrate) do + pressures[migrated] = pressures[migrated] + add + end + -- overwritingly distribute intrusive + currentfluidtype = fluid_types[intruder] + currentpressure = (pressures[intruder] - biases[intruder]) * 0.5 + pressures[intruder] = currentpressure + biases[intruder] + else + -- find most "intrusive" connection + local highest_pressure = 0 + local most_intrusive + for _, intruder in ipairs(intrusive) do + if pressures[intruder] > highest_pressure then + most_intrusive = intruder + end + end + -- put current fluid in migration + for _, migrated in ipairs(migrate) do + pressures[migrated] = pressures[migrated] + add + end + -- overwritingly distribute intrusive + currentfluidtype = fluid_types[most_intrusive] + currentpressure = (pressures[most_intrusive] - biases[most_intrusive]) * 0.5 + pressures[most_intrusive] = currentpressure + biases[most_intrusive] + end + end + + -- unapply biases + for k, bias in pairs(biases) do + pressures[k] = pressures[k] + bias end - return average + -- set pressures and types + for k, pressure in ipairs(pressures) do + connections[k].set(pressure) + tconnections[k].set(fluid_types[k]) + end + + return currentpressure, currentfluidtype end -flowlogic.run_input = function(pos, node, currentpressure, inputdef) +flowlogic.run_input = function(pos, node, currentpressure, inputdef, currentfluidtype) -- intakefn allows a given input node to define it's own intake logic. - -- this function will calculate the maximum amount of water that can be taken in; + -- this function will calculate the maximum amount of fluid that can be taken in; -- the intakefn will be given this and is expected to return the actual absorption amount. local maxpressure = inputdef.maxpressure local intake_limit = maxpressure - currentpressure - if intake_limit <= 0 then return currentpressure end + if intake_limit <= 0 then return currentpressure, currentfluidtype end - local actual_intake = inputdef.intakefn(pos, intake_limit) + local actual_intake, newtype = inputdef.intakefn(pos, intake_limit, currentfluidtype) --pipeworks.logger("run_input@"..formatvec(pos).." oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake) - if actual_intake <= 0 then return currentpressure end + if actual_intake <= 0 then return currentpressure, newtype end local newpressure = actual_intake + currentpressure --debuglog("run_input() end, oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure) - return newpressure + return newpressure, newtype end -- flowlogic output helper implementation: --- outputs water by trying to place water nodes nearby in the world. --- neighbours is a list of node offsets to try placing water in. +-- outputs fluid by trying to place fluid nodes nearby in the world. +-- neighbours is a list of node offsets to try placing fluid in. -- this is a constructor function, returning another function which satisfies the output helper requirements. -- note that this does *not* take rotation into account. flowlogic.helpers.make_neighbour_output_fixed = function(neighbours) - return function(pos, node, currentpressure, finitemode) + return function(pos, node, currentpressure, finitemode, currentfluidtype) + if not currentfluidtype then return 0, currentfluidtype end + local finitemode = finitemode or finites[currentfluidtype] local taken = 0 for _, offset in pairs(neighbours) do local npos = vector.add(pos, offset) local name = core.get_node(npos).name if currentpressure < 1 then break end - -- take pressure anyway in non-finite mode, even if node is water source already. + -- take pressure anyway in non-finite mode, even if node is fluid source already. -- in non-finite mode, pressure has to be sustained to keep the sources there. - -- so in non-finite mode, placing water is dependent on the target node; + -- so in non-finite mode, placing fluid is dependent on the target node; -- draining pressure is not. - local canplace = (name == "air") or (name == pipeworks.liquids.water.flowing) + local canplace = (name == "air") or (core.registered_nodes[name] == core.registered_nodes[name].liquid_alternative_flowing) if canplace then - core.swap_node(npos, {name=pipeworks.liquids.water.source}) + core.swap_node(npos, {name=pipeworks.liquids[currentfluidtype].source}) end if (not finitemode) or canplace then taken = taken + 1 currentpressure = currentpressure - 1 end end - return taken + return taken, currentfluidtype end end -- complementary function to the above when using non-finite mode: --- removes water sources from neighbor positions when the output is "off" due to lack of pressure. +-- removes fluid sources from neighbor positions when the output is "off" due to lack of pressure. flowlogic.helpers.make_neighbour_cleanup_fixed = function(neighbours) return function(pos, node, currentpressure) --pipeworks.logger("neighbour_cleanup_fixed@"..formatvec(pos)) for _, offset in pairs(neighbours) do local npos = vector.add(pos, offset) local name = core.get_node(npos).name - if (name == pipeworks.liquids.water.source) then + if (name == core.registered_nodes[name].liquid_alternative_source) then --pipeworks.logger("neighbour_cleanup_fixed removing "..formatvec(npos)) core.remove_node(npos) end @@ -278,35 +387,37 @@ end -flowlogic.run_output = function(pos, node, currentpressure, oldpressure, outputdef, finitemode) - -- processing step for water output devices. +flowlogic.run_output = function(pos, node, currentpressure, oldpressure, outputdef, finitemode, currentfluidtype) + -- processing step for fluid output devices. -- takes care of checking a minimum pressure value and updating the resulting pressure level -- the outputfn is provided the current pressure and returns the pressure "taken". -- as an example, using this with the above spigot function, - -- the spigot function tries to output a water source if it will fit in the world. + -- the spigot function tries to output a fluid source if it will fit in the world. --pipeworks.logger("flowlogic.run_output() pos "..formatvec(pos).." old -> currentpressure "..tostring(oldpressure).." "..tostring(currentpressure).." finitemode "..tostring(finitemode)) + local finitemode = finitemode or finites[currentfluidtype] local upper = outputdef.upper local lower = outputdef.lower local result = currentpressure local threshold if finitemode then threshold = lower else threshold = upper end if currentpressure > threshold then - local takenpressure = outputdef.outputfn(pos, node, currentpressure, finitemode) + local takenpressure + takenpressure, currentfluidtype = outputdef.outputfn(pos, node, currentpressure, finitemode, currentfluidtype) local newpressure = currentpressure - takenpressure if newpressure < 0 then newpressure = 0 end result = newpressure end if (not finitemode) and (currentpressure < lower) and (oldpressure < lower) then --pipeworks.logger("flowlogic.run_output() invoking cleanup currentpressure="..tostring(currentpressure)) - outputdef.cleanupfn(pos, node, currentpressure) + outputdef.cleanupfn(pos, node, currentpressure, currentfluidtype) end - return result + return result, currentfluidtype end -- determine which node to switch to based on current pressure -flowlogic.run_transition = function(node, currentpressure) +flowlogic.run_transition = function(node, currentpressure, currentfluidtype) --WISHME: maybe use fluid type in transitions local simplesetdef = pipeworks.flowables.transitions.simple[node.name] local result = node local found = false diff --git a/pressure_logic/flowable_node_registry.lua b/pressure_logic/flowable_node_registry.lua index 6d7bf173..ee7a531d 100644 --- a/pressure_logic/flowable_node_registry.lua +++ b/pressure_logic/flowable_node_registry.lua @@ -35,7 +35,7 @@ pipeworks.flowables.list.simple_nodenames = {} -- the neighbour is to participate in pressure balancing. pipeworks.flowables.list.directional = {} --- simple intakes - try to absorb any adjacent water nodes +-- simple intakes - try to absorb any adjacent fluid nodes pipeworks.flowables.inputs = {} pipeworks.flowables.inputs.list = {} pipeworks.flowables.inputs.nodenames = {} diff --git a/pressure_logic/flowable_node_registry_install.lua b/pressure_logic/flowable_node_registry_install.lua index b561ca64..0b3c7b30 100644 --- a/pressure_logic/flowable_node_registry_install.lua +++ b/pressure_logic/flowable_node_registry_install.lua @@ -143,7 +143,7 @@ end -- Register a node as a simple intake: --- tries to absorb water source nodes from it's surroundings. +-- tries to absorb fluid source nodes from it's surroundings. -- may exceed limit slightly due to needing to absorb whole nodes. register.intake_simple = function(nodename, maxpressure) register.intake(nodename, maxpressure, pipeworks.flowlogic.check_for_liquids_v2) @@ -157,10 +157,10 @@ end -- if not (the default unless auto-detected), -- nodes above their upper threshold have their outputfn invoked (and pressure deducted), -- nodes between upper and lower are left idle, --- and nodes below lower have their cleanup fn invoked (to say remove water sources). +-- and nodes below lower have their cleanup fn invoked (to say remove fluid sources). -- the upper and lower difference acts as a hysteresis to try and avoid "gaps" in the flow. -- if finite mode is on, upper is ignored and lower is used to determine whether to run outputfn; --- cleanupfn is ignored in this mode as finite mode assumes something causes water to move itself. +-- cleanupfn is ignored in this mode as finite mode assumes something causes fluid to move itself. register.output = function(nodename, upper, lower, outputfn, cleanupfn) if pipeworks.flowables.outputs.list[nodename] then error("pipeworks.flowables.outputs duplicate registration!") diff --git a/textures/pipeworks_fluid_lava.png b/textures/pipeworks_fluid_lava.png new file mode 100644 index 00000000..c06d44c9 Binary files /dev/null and b/textures/pipeworks_fluid_lava.png differ diff --git a/textures/pipeworks_fluid_river_water.png b/textures/pipeworks_fluid_river_water.png new file mode 100644 index 00000000..90e78df1 Binary files /dev/null and b/textures/pipeworks_fluid_river_water.png differ diff --git a/textures/pipeworks_fluid_water.png b/textures/pipeworks_fluid_water.png new file mode 100644 index 00000000..82847a2c Binary files /dev/null and b/textures/pipeworks_fluid_water.png differ diff --git a/textures/pipeworks_fluidbar.png b/textures/pipeworks_fluidbar.png new file mode 100644 index 00000000..9e663d00 Binary files /dev/null and b/textures/pipeworks_fluidbar.png differ diff --git a/textures/pipeworks_source_frame.png b/textures/pipeworks_source_frame.png new file mode 100755 index 00000000..9147b55d Binary files /dev/null and b/textures/pipeworks_source_frame.png differ diff --git a/todo/pressure_logic.txt b/todo/pressure_logic.txt index 60884e1d..88000e0d 100644 --- a/todo/pressure_logic.txt +++ b/todo/pressure_logic.txt @@ -17,17 +17,5 @@ VanessaE would like this to function as an output with the following properties: * When turning off in non-finite mode, for all neighbour nodes, replace the water sources with flowing water as discussed above, but *only* if those neighbouring sources do not have any water source neighbours of their own in turn - this will prevent the block from creating a "hole" in a uniform pool of water sources. - --- Support for other fluids in pipes (Feature request/wish list) -Various sources from IRC and github issues have indicated that the ability to carry amounts of substance other than water through pipes would see uses appear for it if it were implemented (there does not appear to be anything trying to do so right now). -Extending the pressure mechanism to handle new fluids would be simple enough, it would just have to deal with more variables. -However, this feature raises the question of how to handle mixtures of fluids in pipes. - -Two possible solutions appear evident: -+ Don't mix at all. For each flowable registered, either a variant would be created for each supported liquid, or each node would declare which fluid it carries explicitly. Flowable nodes for different fluids would not interact with each other at all. - -+ Equalise "pressure" of multiple fluid variables in a similar manner to how the single water pressure value is currently balanced out, however this raises the issue of how to deal with mixtures - for instance, how is a spigot to function with a mixed liquid? does it simply refuse to function if it doesn't know how to deal with a certain mixture (as it can only output whole nodes)? likewise for certain mixtures in pipes, should it be allowed for lava and water for instance to mix like they don't interact at all? - -This mechanism also hints at a weakness of the pressure logic mechanism as it currently stands - namely that liquids are handled like gases and assumed to be compressible. - +-- Make liquids incompressible diff --git a/trashcan.lua b/trashcan.lua index 1478398b..daebdec3 100644 --- a/trashcan.lua +++ b/trashcan.lua @@ -1,14 +1,16 @@ local S = core.get_translator("pipeworks") local fs_helpers = pipeworks.fs_helpers +local voidname = "pipeworks:trashcan" + local formspec = table.concat({ fs_helpers.prepends(10.25, 8.5), - fs_helpers.node_label("pipeworks:trashcan", S("Trash Can")), + fs_helpers.node_label(voidname, S("Trash Can")), fs_helpers.inv_list(4.625, 1.25, 1, 1, "trash"), fs_helpers.player_inv(0.25, 3.5), }) -core.register_node("pipeworks:trashcan", { +core.register_node(voidname, { description = S("Trash Can"), drawtype = "normal", tiles = { @@ -29,19 +31,28 @@ core.register_node("pipeworks:trashcan", { connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}, priority = 1, -- Lower than anything else }, + pipe_connections = { top = 1, bottom = 1, front = 1, back = 1, left = 1, right = 1}, on_construct = function(pos) local meta = core.get_meta(pos) meta:set_string("formspec", formspec) meta:set_string("infotext", S("Trash Can")) meta:get_inventory():set_size("trash", 1) end, - after_place_node = pipeworks.after_place, - after_dig_node = pipeworks.after_dig, + after_place_node = function(pos) + pipeworks.scan_for_pipe_objects(pos) + pipeworks.after_place(pos) + end, + after_dig_node = function(pos) + pipeworks.scan_for_pipe_objects(pos) + pipeworks.after_dig(pos) + end, on_metadata_inventory_put = function(pos, listname, index, stack, player) core.get_meta(pos):get_inventory():set_stack(listname, index, ItemStack("")) end, - _update_formspec = function(pos) - core.get_meta(pos):set_string("formspec", formspec) - end, + _update_formspec = function(pos) + core.get_meta(pos):set_string("formspec", formspec) + end, }) -pipeworks.ui_cat_tube_list[#pipeworks.ui_cat_tube_list+1] = "pipeworks:trashcan" +pipeworks.ui_cat_tube_list[#pipeworks.ui_cat_tube_list+1] = voidname +pipeworks.flowables.register.simple(voidname) +pipeworks.flowables.register.output(voidname, 0, 0, function(pos, node, currentpressure, finitemode, currentfluidtype) return 4, currentfluidtype end, function()end)