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 deeca6d6..fecbf9a7 100644 --- a/autocrafter.lua +++ b/autocrafter.lua @@ -1,4 +1,37 @@ local S = core.get_translator("pipeworks") + +if core.get_modpath("unified_inventory") then + core.register_craftitem("pipeworks:text_req", { + description = S("Requirements"), + inventory_image = "text_req.png", + groups = {not_in_creative_inventory = 1}, + stack_max = 1, + }) + + core.register_craftitem("pipeworks:text_div1000", { + description = S("Liters (divide by 1000 for m³)"), + inventory_image = "text_div1000.png", + groups = {not_in_creative_inventory = 1}, + stack_max = 1, + }) + + unified_inventory.register_craft_type("fluidshaped", { + description = S("Shaped Fluid Craft"), + icon = "pipeworks_autocrafter.png", + width = 3, + height = 4, + }) + + unified_inventory.register_on_craft_registered( + function (item_name, options) + if options.type ~= "fluidshaped" then return end + options.items[10] = "pipeworks:text_req" + options.items[11] = pipeworks.liquids[options.fluid.type].source .. " " .. (options.fluid.amount * 1000) + options.items[12] = "pipeworks:text_div1000" + end + ) +end + -- cache some recipe data to avoid calling the slow function -- core.get_craft_result() every second local autocrafterCache = {} @@ -6,6 +39,90 @@ local autocrafterCache = {} local craft_time = 1 local next = next +local function get_item_info(stack) + local name = stack:get_name() + local def = minetest.registered_items[name] + local description = def and def.description or S("Unknown item") + return description, name +end + +-- returns false if we shouldn't bother attempting to start the timer again +-- after this +local function update_meta(meta, enabled) + local state = enabled and "on" or "off" + meta:set_int("enabled", enabled and 1 or 0) + local list_backgrounds = "" + if minetest.get_modpath("i3") or minetest.get_modpath("mcl_formspec") then + list_backgrounds = "style_type[box;colors=#666]" + for i = 0, 2 do + for j = 0, 2 do + list_backgrounds = list_backgrounds .. "box[" .. + 1.5 + (i * 1.25) .. "," .. 0.25 + (j * 1.25) .. ";1,1;]" + end + end + for i = 0, 3 do + for j = 0, 2 do + list_backgrounds = list_backgrounds .. "box[" .. + 5.28 + 1.25 + (i * 1.25) .. "," .. 0.25 + (j * 1.25) .. ";1,1;]" + end + end + for i = 0, 7 do + for j = 0, 2 do + list_backgrounds = list_backgrounds .. "box[" .. + 1.5 + (i * 1.25) .. "," .. 5 + (j * 1.25) .. ";1,1;]" + end + end + end + local size = "11.5,14" + local fluid = meta:get("fluidtype") + local amount = meta:get_float("fluidamount") + local fluid_cap = meta:get_float("fluidcap") + local bar_height = 8.25 * amount / fluid_cap + local fs = + "formspec_version[4]" .. + "size[" .. size .. "]" .. + pipeworks.fs_helpers.get_prepends(size) .. + list_backgrounds .. + "list[context;recipe;1.47,0.22;3,3;]" .. + "image[5.25,1.45;1,1;[combine:16x16^[noalpha^[colorize:#141318:255]" .. + "list[context;output;5.25,1.45;1,1;]" .. + "image_button[5.25,2.6;1,0.6;pipeworks_button_" .. state .. ".png;" .. + state .. ";;;false;pipeworks_button_interm.png]" .. + "list[context;dst;6.53,0.22;4,3;]" .. + "list[context;src;1.47,5;8,3;]" ..-- + pipeworks.fs_helpers.get_inv(9,1.25) .. + "listring[current_player;main]" .. + "listring[context;src]" .. + "listring[current_player;main]" .. + "listring[context;dst]" .. + "listring[current_player;main]" .. + "image[0.22," .. (8.5 - bar_height) .. ";1," .. bar_height .. ";pipeworks_fluid_" .. (fluid or "air") .. ".png]" .. + "image[0.22,0.25;1,8.25;pipeworks_fluidbar.png]" + if minetest.get_modpath("digilines") then + fs = fs .. "field[1.47,4;4.5,0.75;channel;" .. S("Channel") .. + ";${channel}]" .. + "button[6.25,4;1.5,0.75;set_channel;" .. S("Set") .. "]" .. + "button_exit[8.05,4;2,0.75;close;" .. S("Close") .. "]" + end + meta:set_string("formspec", fs) + + -- toggling the button doesn't quite call for running a recipe change check + -- so instead we run a minimal version for infotext setting only + -- this might be more written code, but actually executes less + local output = meta:get_inventory():get_stack("output", 1) + if output:is_empty() then -- doesn't matter if paused or not + meta:set_string("infotext", S("unconfigured Autocrafter")) + return false + end + + local description, name = get_item_info(output) + local infotext = enabled and S("'@1' Autocrafter (@2)", description, name) + or S("paused '@1' Autocrafter", description) + + meta:set_string("infotext", infotext) + return enabled +end + local function count_index(invlist) local index = {} for _, stack in pairs(invlist) do @@ -27,8 +144,13 @@ end -- Get best matching recipe for what user has put in crafting grid. -- This function does not consider crafting method (mix vs craft) -local function get_matching_craft(output_name, example_recipe) - local recipes = core.get_all_craft_recipes(output_name) +local function get_matching_craft(output_name, example_recipe, fluid_input) + local recipes + if fluid_input then + recipes = pipeworks.fluid_recipes:get_all(output_name, fluid_input.type) + else + recipes = core.get_all_craft_recipes(output_name) + end if not recipes then return example_recipe end @@ -77,12 +199,20 @@ local function get_craft(pos, inventory, hash) method = "normal", width = 3, items = example_recipe }) + local fluid + if (not output) or output.item:is_empty() then + output, decremented_input, fluid = pipeworks.fluid_recipes:get({ + items = example_recipe, fluid_type = core.get_meta(pos):get("fluidtype") -- GOHERE + }) + end + local recipe = example_recipe if output and not output.item:is_empty() then - recipe = get_matching_craft(output.item:get_name(), example_recipe) + recipe = get_matching_craft(output.item:get_name(), example_recipe, fluid) end craft = { + fluid = fluid, recipe = recipe, consumption = count_index(recipe), output = output, @@ -207,12 +337,21 @@ local function has_room_for_output(list_output, index_output) return true end -local function autocraft(inventory, craft) +-- returns true if not enough fluid +local function check_fluid_insufficiency(req, input) + if not req then return false end + if not input then return true end + if input.type ~= req.type then return true end + if input.amount < req.amount then return true end +end + +local function autocraft(inventory, craft, fluid) if not craft then return false end -- check if output and all replacements fit in dst local output = craft.output.item local out_items = count_index(craft.decremented_input) + local craftfluid = craft.fluid out_items[output:get_name()] = (out_items[output:get_name()] or 0) + output:get_count() @@ -223,7 +362,7 @@ local function autocraft(inventory, craft) -- check if we have enough material available local inv_index = count_index(inventory:get_list("src")) local consumption = calculate_consumption(inv_index, craft.consumption) - if not consumption then + if (not consumption) or (craftfluid and check_fluid_insufficiency(craftfluid, fluid)) then return false end @@ -234,6 +373,7 @@ local function autocraft(inventory, craft) inventory:remove_item("src", ItemStack(itemname)) end end + if craftfluid then fluid.amount = fluid.amount - craftfluid.amount end -- craft the result into the dst inventory and add any "replacements" as well inventory:add_item("dst", output) @@ -262,9 +402,12 @@ local function run_autocrafter(pos, elapsed) return false end + local fluid = {type = meta:get("fluidtype"), amount = meta:get_float("fluidamount")} for _ = 1, math.floor(elapsed / craft_time) do - local continue = autocraft(inventory, craft) + local continue = autocraft(inventory, craft, fluid) if not continue then return false end + meta:set_float("fluidamount", fluid.amount) + update_meta(meta, meta:get_int("enabled") == 1) end return true end @@ -354,46 +497,52 @@ local function update_meta(meta, enabled) for i = 0, 2 do for j = 0, 2 do list_backgrounds = list_backgrounds .. "box[" .. - 0.22 + (i * 1.25) .. "," .. 0.22 + (j * 1.25) .. ";1,1;]" + 1.5 + (i * 1.25) .. "," .. 0.25 + (j * 1.25) .. ";1,1;]" end end for i = 0, 3 do for j = 0, 2 do list_backgrounds = list_backgrounds .. "box[" .. - 5.28 + (i * 1.25) .. "," .. 0.22 + (j * 1.25) .. ";1,1;]" + 5.28 + 1.25 + (i * 1.25) .. "," .. 0.25 + (j * 1.25) .. ";1,1;]" end end for i = 0, 7 do for j = 0, 2 do list_backgrounds = list_backgrounds .. "box[" .. - 0.22 + (i * 1.25) .. "," .. 5 + (j * 1.25) .. ";1,1;]" + 1.5 + (i * 1.25) .. "," .. 5 + (j * 1.25) .. ";1,1;]" end end end - local size = "10.2,14" + local size = "11.5,14" + local fluid = meta:get("fluidtype") + local amount = meta:get_float("fluidamount") + local fluid_cap = meta:get_float("fluidcap") + local bar_height = 8.25 * amount / fluid_cap local fs = - "formspec_version[2]" .. + "formspec_version[4]" .. "size[" .. size .. "]" .. pipeworks.fs_helpers.get_prepends(size) .. list_backgrounds .. - "list[context;recipe;0.22,0.22;3,3;]" .. - "image[4,1.45;1,1;[combine:16x16^[noalpha^[colorize:#141318:255]" .. - "list[context;output;4,1.45;1,1;]" .. - "image_button[4,2.6;1,0.6;pipeworks_button_" .. state .. ".png;" .. + "list[context;recipe;1.47,0.22;3,3;]" .. + "image[5.25,1.45;1,1;[combine:16x16^[noalpha^[colorize:#141318:255]" .. + "list[context;output;5.25,1.45;1,1;]" .. + "image_button[5.25,2.6;1,0.6;pipeworks_button_" .. state .. ".png;" .. state .. ";;;false;pipeworks_button_interm.png]" .. - "list[context;dst;5.28,0.22;4,3;]" .. - "list[context;src;0.22,5;8,3;]" .. - pipeworks.fs_helpers.get_inv(9) .. + "list[context;dst;6.53,0.22;4,3;]" .. + "list[context;src;1.47,5;8,3;]" ..-- + pipeworks.fs_helpers.get_inv(9,1.25) .. "listring[current_player;main]" .. "listring[context;src]" .. "listring[current_player;main]" .. "listring[context;dst]" .. - "listring[current_player;main]" + "listring[current_player;main]" .. + "image[0.22," .. (8.5 - bar_height) .. ";1," .. bar_height .. ";pipeworks_fluid_" .. (fluid or "air") .. ".png]" .. + "image[0.22,0.25;1,8.25;pipeworks_fluidbar.png]" if core.get_modpath("digilines") then - fs = fs .. "field[0.22,4.1;4.5,0.75;channel;" .. S("Channel") .. + fs = fs .. "field[1.47,4;4.5,0.75;channel;" .. S("Channel") .. ";${channel}]" .. - "button[5,4.1;2,0.75;set_channel;" .. S("Set") .. "]" .. - "button_exit[7.2,4.1;2,0.75;close;" .. S("Close") .. "]" + "button[6.25,4;2,0.75;set_channel;" .. S("Set") .. "]" .. + "button_exit[8.45,4;2,0.75;close;" .. S("Close") .. "]" end meta:set_string("formspec", fs) @@ -453,6 +602,113 @@ local function upgrade_autocrafter(pos, meta) end end +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 = 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 + +--[[ input = { + input = , + fluid_type = +} ]] +pipeworks.fluid_recipes.get = function(self, input) + local path = self.trie + local empty = {item = ItemStack("")} + local dec_input = table.copy(input) + for k,v in ipairs(dec_input.items) do + path = path[v:get_name()] + if path == nil then return empty, input end + dec_input.items[k] = ItemStack(v) + dec_input.items[k]:set_count(v:get_count()-1) + if path == nil then return empty, input end + if path.tail then + if path.output[dec_input.fluid_type] then + return path.output[dec_input.fluid_type], dec_input, path.fluid[dec_input.fluid_type] + else + return empty, input + end + end + end + return empty, input +end + +-- name = +-- fluid_type = +pipeworks.fluid_recipes.get_all = function(self, name, fluid_type) + local out = {} + for _,v in ipairs(self) do + if v.output[fluid_type] and v.output[fluid_type].item:get_name() == name then + out[#out + 1] = v + end + end + return out +end + core.register_node("pipeworks:autocrafter", { description = S("Autocrafter"), drawtype = "normal", @@ -460,6 +716,7 @@ core.register_node("pipeworks:autocrafter", { groups = {snappy = 3, tubedevice = 1, tubedevice_receiver = 1, dig_generic = 1, axey=1, handy=1, pickaxey=1}, is_ground_content = false, _mcl_hardness=0.8, + pipe_connections = { top = 1, bottom = 1, left = 1, right = 1, front = 1, back = 1 }, tube = {insert_object = function(pos, node, stack, direction) local meta = core.get_meta(pos) local inv = meta:get_inventory() @@ -479,6 +736,7 @@ core.register_node("pipeworks:autocrafter", { }, on_construct = function(pos) local meta = core.get_meta(pos) + meta:set_float("fluidcap", 8) local inv = meta:get_inventory() inv:set_size("src", 3 * 8) inv:set_size("recipe", 3 * 3) @@ -511,9 +769,13 @@ core.register_node("pipeworks:autocrafter", { local inv = meta:get_inventory() return (inv:is_empty("src") and inv:is_empty("dst")) end, - after_place_node = pipeworks.scan_for_tube_objects, + after_place_node = function(pos) + pipeworks.scan_for_tube_objects(pos) + pipeworks.scan_for_pipe_objects(pos) + end, after_dig_node = function(pos) pipeworks.scan_for_tube_objects(pos) + pipeworks.scan_for_pipe_objects(pos) end, on_destruct = function(pos) autocrafterCache[core.hash_node_position(pos)] = nil @@ -646,4 +908,27 @@ core.register_node("pipeworks:autocrafter", { }, }, }) -pipeworks.ui_cat_tube_list[#pipeworks.ui_cat_tube_list + 1] = "pipeworks:autocrafter" + +-- autocrafter fluid stuff +local autocraftername = "pipeworks:autocrafter" +pipeworks.flowables.register.simple(autocraftername) +pipeworks.flowables.register.output(autocraftername, 0, 0, function(pos, node, currentpressure, finitemode, fluid_type) + if fluid_type == nil then return 0, fluid_type end -- you can't put empty in something and expect displacement + local meta = core.get_meta(pos) + local fluid_cap = meta:get_float("fluidcap") + local fluid_amount = meta:get_float("fluidamount") + local current_fluid_type = meta:get("fluidtype") + if current_fluid_type ~= fluid_type then + if fluid_amount == 0 then + meta:set_string("fluidtype", fluid_type) + else + return 0, fluid_type + end + end + local taken = math.min(fluid_cap - fluid_amount, currentpressure) + meta:set_float("fluidamount", fluid_amount + taken) + update_meta(meta, meta:get_int("enabled") == 1) + return taken, fluid_type +end, function()end) + +pipeworks.ui_cat_tube_list[#pipeworks.ui_cat_tube_list + 1] = autocraftername 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 bd0d029d..944403de 100644 --- a/common.lua +++ b/common.lua @@ -211,19 +211,22 @@ function fs_helpers.cycling_button(meta, base, meta_name, values) return base..";"..(texture_name and texture_name..";" or "")..field..";"..core.formspec_escape(text)..(addopts and ";"..addopts or "").."]" end -function fs_helpers.get_inv(y) +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 + 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 inv_size = i3.settings.inv_size or (hotbar_len * 4) + 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["..(i * size + inv_x + (i * spacing))..","..inv_y..";"..size..","..size..";]") + table.insert(fs, "box["..(inv_x + (i * (spacing + size)))..","..inv_y..";"..size..","..size..";]") end table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") @@ -232,14 +235,14 @@ function fs_helpers.get_inv(y) table.insert(fs, "style_type[box;colors=#666]") for i=0, 2 do for j=0, hotbar_len - 1 do - table.insert(fs, "box["..0.2+(j*0.1)+(j*size)..","..(inv_y+size+spacing+0.05)+(i*0.1)+(i*size)..";"..size..","..size..";]") + 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.."]") + 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 + 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 @@ -248,7 +251,7 @@ function fs_helpers.get_inv(y) table.insert(fs, "style_type[box;colors=#77777710,#77777710,#777,#777]") for i = 0, hotbar_len - 1 do - table.insert(fs, "box["..(i * size + inv_x + (i * spacing))..","..inv_y..";"..size..","..size..";]") + table.insert(fs, "box["..(inv_x + (i * (spacing + size)))..","..inv_y..";"..size..","..size..";]") end table.insert(fs, "style_type[list;size="..size..";spacing="..spacing.."]") @@ -257,14 +260,14 @@ function fs_helpers.get_inv(y) table.insert(fs, "style_type[box;colors=#666]") for i=0, 2 do for j=0, hotbar_len - 1 do - table.insert(fs, "box["..0.2+(j*0.1)+(j*size)..","..(inv_y+size+spacing+0.05)+(i*0.1)+(i*size)..";"..size..","..size..";]") + 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,"..y..";8,4;]") + table.insert(fs, "list[current_player;main;".. (0.22 + x) ..","..y..";8,4;]") end return table.concat(fs, "") diff --git a/crafts.lua b/crafts.lua index 072801f8..1753f134 100644 --- a/crafts.lua +++ b/crafts.lua @@ -316,3 +316,49 @@ if pipeworks.enable_node_breaker then } }) end + +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..aaf39b50 100644 --- a/devices.lua +++ b/devices.lua @@ -127,6 +127,57 @@ local pipes_devicelist = { -- Now define the nodes. +local sourcename = "pipeworks:source" +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) + meta:set_string("fluidamount",fields.pressure or meta:get_string("fluidamount")) + meta:set_string("fluidtype",(pipeworks.liquids[fields.fluidtype] and fields.fluidtype) or meta:get_string("fluidtype")) + 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")..";${fluidtype}]".. + "field[3,0.5;1.5,0.75;pressure;"..S("Pressure")..";${fluidamount}]" + 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("fluidamount") - meta:get_float("pipeworks.pressure"), meta:get("fluidtype") +end) + local states = { "on", "off" } for s in ipairs(states) do @@ -370,7 +421,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.def.tiles[1], { name = "pipeworks_spigot.png" } }, use_texture_alpha = texture_alpha_mode and "blend" or true, @@ -579,7 +630,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("pipeworks.fluid_type")] + meta:set_string("infotext", S("@1: @2", (fluid_def and fluid_def.description) or S("none"), (meta:get("pipeworks.pressure") or 0))) + end +}) -- tanks diff --git a/flowing_logic.lua b/flowing_logic.lua index 35d8a5ad..eaad6489 100644 --- a/flowing_logic.lua +++ b/flowing_logic.lua @@ -1,10 +1,12 @@ +-- This file is entirely deprecated, as new behavior requires "pressure" flow logic while this file is only ever used in "classic" flow logic. + -- This file provides the actual flow and pathfinding logic that makes water -- move through the pipes. -- -- 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 +18,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 +85,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 +98,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 +121,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/init.lua b/init.lua index 5f2ea549..69571c43 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?"..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,13 +64,26 @@ 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.."/models.lua") 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") @@ -80,16 +106,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 diff --git a/mod.conf b/mod.conf index a0507e00..24fb540d 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 = 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 min_minetest_version = 5.5.0 diff --git a/pressure_logic/abms.lua b/pressure_logic/abms.lua index b87aef87..66c6f2f7 100644 --- a/pressure_logic/abms.lua +++ b/pressure_logic/abms.lua @@ -34,27 +34,37 @@ end -- 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("pipeworks.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 label_pressure = "pipeworks.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 100644 index 00000000..8e8e8612 Binary files /dev/null and b/textures/pipeworks_source_frame.png differ diff --git a/textures/text_div1000.png b/textures/text_div1000.png new file mode 100644 index 00000000..800bf954 Binary files /dev/null and b/textures/text_div1000.png differ diff --git a/textures/text_req.png b/textures/text_req.png new file mode 100644 index 00000000..d8d1b5f0 Binary files /dev/null and b/textures/text_req.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 6e415f32..b5708f14 100644 --- a/trashcan.lua +++ b/trashcan.lua @@ -1,5 +1,6 @@ local S = core.get_translator("pipeworks") -core.register_node("pipeworks:trashcan", { +local voidname = "pipeworks:trashcan" +core.register_node(voidname, { description = S("Trash Can"), drawtype = "normal", tiles = { @@ -20,6 +21,7 @@ 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) local size = "10.2,9" @@ -43,10 +45,18 @@ core.register_node("pipeworks:trashcan", { 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, on_metadata_inventory_put = function(pos, listname, index, stack, player) core.get_meta(pos):get_inventory():set_stack(listname, index, ItemStack("")) end, + 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, }) -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)