From 4c51d723d36f8dc31626e392698cb58af66957bb Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Wed, 31 Jan 2024 13:48:08 -0800 Subject: [PATCH 01/36] First drop of changes from the development branch. --- mod.conf | 2 +- modules/network/autocraft.lua | 627 ++++++++++++++++++ modules/network/ctrl.lua | 127 +++- modules/network/init.lua | 9 +- modules/network/loan.lua | 111 ++++ modules/network/network.lua | 103 ++- modules/storage/cmonitor.lua | 266 ++++++++ modules/storage/cterminal.lua | 909 ++++++++++++++++++++++++++ modules/storage/drawer-api.lua | 42 ++ modules/storage/drawer-interop.lua | 51 ++ modules/storage/drive.lua | 158 ++--- modules/storage/init.lua | 18 + modules/storage/interface.lua | 377 +++++++++++ modules/storage/pipeworks-interop.lua | 25 + modules/storage/remote.lua | 356 ++++++++++ modules/storage/technic-interop.lua | 283 ++++++++ modules/storage/terminal.lua | 158 +++-- 17 files changed, 3451 insertions(+), 171 deletions(-) create mode 100644 modules/network/autocraft.lua create mode 100644 modules/network/loan.lua create mode 100644 modules/storage/cmonitor.lua create mode 100644 modules/storage/cterminal.lua create mode 100644 modules/storage/drawer-api.lua create mode 100644 modules/storage/drawer-interop.lua create mode 100644 modules/storage/interface.lua create mode 100644 modules/storage/pipeworks-interop.lua create mode 100644 modules/storage/remote.lua create mode 100644 modules/storage/technic-interop.lua diff --git a/mod.conf b/mod.conf index 77698b6..1465382 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name = microexpansion description = A storage managing solution to get an overview over all your items. -optional_depends = pipeworks,basic_materials,mcl_furnaces,mcl_core +optional_depends = pipeworks,basic_materials,mcl_furnaces,mcl_core,drawers,technic,technic_plus supported_games = mineclone2,mineclonia,minetest_game diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua new file mode 100644 index 0000000..e4e7f32 --- /dev/null +++ b/modules/network/autocraft.lua @@ -0,0 +1,627 @@ +local me = microexpansion + +local pipeworks_craft_time = 1 + +function me.autocraft_next_start(net) + -- We use machine reservations to allow simultaneous crafting jobs + -- todo: implement a limiter or a power consumption or 'crafting + -- cpus' for realism. + local parallel = true + if not parallel and net.pending then + -- start subsequent autocrafting jobs sequentially. + -- We really only need zero/not zero for build to queue actions or not + return net.pending.time[net.pending.max_index] + end + return 0 +end + +function me.start_crafting(pos, step_time) + local meta = minetest.get_meta(pos) + local timer = minetest.get_node_timer(pos) + timer:set(step_time, 0) +end + +local function round(v) + return math.floor(v + 0.5) +end + +-- technic_plus doesn't export machine speed. We +-- use this to know exactly how long a machine will take to process +-- anything, after that time, we know it is done and we can grab the +-- outputs, no polling. We do this for efficiency. +me.speed = { + ["default:furnace"] = 1, +} + +-- Use to wire in how long a machine takes to process something. +function me.set_speed(name, speed) + me.speed[name] = speed +end + +-- Sometimes the poor autocrafter doesn't have infinite input and output room +-- for a craft, break large ones up to fit. +-- Also machine inputs/outputs don't fit. +me.maximums = { +} + +-- Allow a maximum craft to be defined to avoid overrunning machine +-- inputs and outputs and the autocrafter inputs and output.. +function me.register_max(name, count) + me.maximums[name] = count +end + +-- This reserves a machine for length seconds. This is used to +-- schedule machines and know when they are done processing a job. +-- The jobs run sequentially. A 10 second job followed by a 5 second +-- job will finish at 15 seconds, not 5 seconds. Return the new start +-- time. todo: this solves the end and the outputs, but not the +-- inputs. They can be overloaded and we might have to delay putting +-- things into the machine. +function me.reserve(net, pos, original_start, length) + if not net.pending then + net.pending = {} + net.pending.time = {} + end + if not net.pending.busy then + net.pending.busy = {} + end + local free_time = net.pending.busy[pos] or 0 + local start = math.max(free_time, original_start) + local ending = start + length + net.pending.busy[pos] = ending + return start +end + +local length = function(a) + local count = 0 + for k,v in pairs(a) do + count = count + 1 + end + return count +end + +-- Testing: HV solar is realiable, big loans are screwy. +-- HV batteries are realiable. +local function build(net, cpos, inv, name, count, stack, sink, time) + -- The autocrafters nor the machines can take really large amounts + -- of things, help them out. + local max = me.maximums[name] + if not max then + -- If no explicit max, assume this is a pipeworks autocrafter and + -- it only has 12 outputs. + max = stack:get_stack_max()*12 + end + if net.process and net.process[name] then + max = max * length(net.process[name]) + elseif net.autocrafters[name] then + max = max * length(net.autocrafters[name]) + end + --me.log("AC: max is "..max , "error") + if max and count > max then + local next_time = time + local built = true + while count > 1 and built do + local substack = ItemStack(stack) + max = math.min(max, count) + substack:set_count(max) + local step_time + built, step_time = build(net, cpos, inv, name, max, substack, sink, time) + if not built then + -- we are done, can't craft, so stop + else + next_time = math.max(next_time, time + step_time) + end + count = count - max + end + local step_time = next_time - time + return built, step_time + end + --me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error") + local dat = {} + local second_output = nil + local main_action_time = count * pipeworks_craft_time + 1 + if net.process and net.process[name] then + local machines = net.process[name] + for k, v in pairs(machines) do + local mname = minetest.get_node(k).name + if not me.block_to_typename_map[mname] then + -- There is no way this can be. Prune. + -- Would be nice if we had a way to notice blocks going away. + -- Maybe latch into on_destruct for them? + net.process[name][k] = nil + goto continue + end + local i = #dat + 1 + dat[i] = {} + dat[i].apos = k + dat[i].ipos = v + dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory() + -- todo: figure out if we should use total. + if i == count then + break + end + ::continue:: + end + --me.log("INT: looking up output "..name, "error") + local inputs = me.find_by_output(name) + local machine_name = minetest.get_node(dat[1].apos).name + local typename = me.block_to_typename_map[machine_name] + --me.log("Looking up "..(typename or "nil").." recipe for a "..(machine_name or nil), "error") + dat[1].recip = me.get_recipe(typename, inputs) + --me.log("MACHINE: "..machine_name.." typename is "..typename.." and time is "..tostring(dat[1].recip.time), "error") + dat[1].recip.input = inputs + -- freezer can produce two outputs, we only care about the first. + if dat[1].recip.output[1] then + second_output = dat[1].recip.output[2] + dat[1].recip.output = dat[1].recip.output[1] + end + dat[1].ostack = ItemStack(dat[1].recip.output) + -- me.log("SEE: "..machine_name.." "..minetest.serialize(technic.recipes)) + local speed = me.speed[machine_name] + local craft_count = dat[1].ostack:get_count() + local total = math.ceil(count/craft_count) + -- Remove the extra machines. In theory we could remove the busy machines. + while #dat > total do + table.remove(dat) + end + -- crafting 4 carbon plates misses taking 1 carbon plate on output, make this bigger + -- we'll try 1 for now, figure out right formula. 1 looks perfect. 128 glue is short by 2 + -- 1 + 1 is a second too slow on the doped for 81., 2 +0 doesn't work, a second shy + --main_action_time = round((total+2)*dat[1].recip.time/speed) + 1 + --main_action_time = (total+1)*round(dat[1].recip.time/speed) -- one shy + --main_action_time = total*dat[1].recip.time/speed + 2 -- 2 at 80 shy, 3 at 160 shy + local subtotal = math.floor((total+#dat-1)/#dat) + --main_action_time = subtotal*1.025*dat[1].recip.time/speed + 2 -- ok + --main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1 -- too fast? + main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? + if second_output then + second_output = ItemStack(second_output) + second_output:set_count(second_output:get_count()*total) + end + --me.log("MACHINE: "..machine_name.." is speed "..speed.." and final time is "..main_action_time, "error") + elseif net.autocrafters[name] then + -- fill all recipe slots, wait, grab all output slots + -- "src" "dst" "recipe" "output" + local machines = net.autocrafters[name] + for k, v in pairs(machines) do + local i = #dat + 1 + dat[i] = {} + dat[i].apos = k + dat[i].ipos = v + dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory() + -- TODO: If we set up pipeworks ac, then we remove interface for it and craft + -- it goes to ac, and dies here. Flush net.autocrafters for all the + -- attached inventories during interface removal. + if dat[i].rinv == nil then + --me.log("no inventory", "error") + return + end + dat[i].ostack = dat[i].rinv:get_stack("output", 1) + if dat[i].ostack:get_name() ~= name then + -- invalidate it + net.autocrafters[name][dat[i].apos] = nil + --me.log("invalidating autocrafter for "..name, "error") + table.remove(dat) + if #dat == 0 then + return + end + end + -- todo: figure out if we should use total. Test with crafting planks. + if i == total then + break + end + end + -- Consider looking up the recipe and finding the replacements that way. + if name == "technic:copper_coil" or name == "technic:control_logic_unit" + or name == "technic:solar_panel" then + second_output = ItemStack("basic_materials:empty_spool 999") + end + local craft_count = dat[1].ostack:get_count() + local total = math.ceil(count/craft_count) + local subtotal = math.floor((total+#dat-1)/#dat) + main_action_time = subtotal * pipeworks_craft_time + 1 + else + me.log("can't craft a "..name, "error") + return + end + for i = 1, #dat do + dat[i].isink = function(sstack) + --me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error") + return dat[i].rinv:add_item("src", sstack) + end + end + local craft_count = dat[1].ostack:get_count() + -- These will be returned to the me system + local extra = ItemStack(name) + local total = math.ceil(count/craft_count) + extra:set_count(total*craft_count - count) + --me.log("AC: count "..count.." craft_count "..craft_count.." extra "..extra:get_count(), "error"); + -- we craft a minimum of count, to the multiple of the crafting count + count = total + --me.log("AC: numcount "..count, "error"); + + local consume = {} + if net.process and net.process[name] then + for i = 1, #dat[1].recip.input do + local inp = dat[1].recip.input[i] + --me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error") + consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() + end + else + for i = 1, 9 do + -- TODO: This assumes that all the crafters have the same exact recipe. + local inp = dat[1].rinv:get_stack("recipe", i) + if inp and not inp:is_empty() then + consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() + end + end + end + local replace = true + local next_time = {} + for i = 1, #dat do + next_time[i] = me.reserve(net, dat[i].apos, time, main_action_time) + end + --me.log("RESERVE: "..name.." stime "..time.." step "..main_action_time.." reserve "..next_time[1], "error") + --me.log("PREP: pre count is "..count, "error") + -- prepwork + --me.log("PREP: count is "..count, "error") + local prepworkbits = {} + local previous_ac_size = inv:get_size("ac") + --me.log("PREP: ac size at top is "..previous_ac_size, "error") + for name, count in pairs(consume) do + local istack = ItemStack(name) + if count >= math.pow(2,16) then + replace = false + break + end + istack:set_count(count) + local hasit = inv:contains_item("main", istack) + --me.log("ac checking "..name, "error") + if hasit then + --me.log("ac grabbing "..name, "error") + local grabbed = me.remove_item(net, inv, "main", istack) + if grabbed and grabbed:get_count() == count then + --me.log("ac grabbed "..name, "error") + net.ac_status = net.ac_status .. time.." Grabbed "..count.." "..name..".\n" + local slot = inv:get_size("ac")+1 + inv:set_size("ac", slot) + inv:set_stack("ac", slot, grabbed) + -- and later we do this: + prepworkbits[function() + --me.log("PREP: about to move "..name, "error") + local stack = inv:get_stack("ac", slot) + --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") + local leftovers = 0 + for i = 1, #dat do + -- todo: prove the below is correct. + -- This spreads across evenly when craft_count is > 0 (stainless, carbon steel for example). + local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat)) + leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count() + end + stack:set_count(leftovers) + --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") + inv:set_stack("ac", slot, stack) + end] = name + -- and then something moves the size of ac back to before we started + else + --me.log("ac could not grab "..count.." "..name, "error") + net.ac_status = net.ac_status .. time.." Could not grab "..count.." "..name..".\n" + hasit = false + end + else + -- Try and autocraft it + --me.log("AC: recursive crafting "..count.." "..istack:get_count(), "error") + net.ac_status = net.ac_status .. time.." Need to craft "..count.." "..name..".\n" + hasit = true + local final_step_time = 0 + for i = 1, #dat do + -- todo: prove the below is correct. + -- Does this spread across evenly when craft_count is > 0 (? for example)? + -- I think this works, but it is slightly wasteful, but in a good way as + -- 10 on 10 machines will each craft 1 on craft_count 2 item yielding 10 extra. + local subcount = math.floor((count+i-1)/#dat) + local inner_istack = istack + inner_istack:set_count(subcount) + local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, dat[i].isink, time) + if built then + next_time[i] = math.max(next_time[i], time + step_time) + final_step_time = math.max(final_step_time, step_time) + else + hasit = false + end + end + if hasit then + net.ac_status = net.ac_status .. time.." Craft "..count.." "..name.." in "..final_step_time.." seconds.\n" + else + me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error") + net.ac_status = net.ac_status .. time.." Can't craft "..count.." "..name..".\n" + end + end + replace = replace and hasit + end + local prepwork = function () + -- Do all the little bits of prepwork + for func, name in pairs(prepworkbits) do + --me.log("PREPing: before "..name, "error") + func() + --me.log("PREPing: done "..name, "error") + end + end + -- end of prepwork + if not replace then + -- If we don't have everything, and can't craft it, we're stuck, + -- do as much as we can, then nothing else + me.log("missing items", "error") + -- Existing items are already loaded. + return + end + local tmp_next_time = next_time + next_time = 0 + for i = 1, #dat do + next_time = math.max(next_time, tmp_next_time[i]) + end + local main_action = function() + --me.log("ACTION: prep for "..stack:get_name(), "error") + prepwork() + -- and once we are done with all the postponed work, we can reduce "ac" + -- lifetimes are more complex than you can imagine. + -- We use a simple rule. When all done, there is nothing left. At that point, + -- we can put any leftovers back into the main inventory. + -- Even this might be too soon, if we have multiple independent crafts going, we + -- need the last one. + if previous_ac_size == 0 and false then + for i = 1,inv:get_size("ac") do + local stack = inv:get_stack("ac", i) + if stack:get_count() ~= 0 then + --me.log("AC: putting "..stack:get_count().." "..stack:get_name().." back into main inventory", "error") + local leftovers = me.insert_item(stack, net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(cpos, leftovers) + end + end + end + --me.log("PREP: ac size is now down to "..previous_ac_size, "error") + inv:set_size("ac", previous_ac_size) + end + --me.log("ACTION: main for "..stack:get_name(), "error") + for i = 1, #dat do + local rmeta = minetest.get_meta(dat[i].apos) + + -- Let's start up the crafter since we loaded it up to run + if (net.process and net.process[name]) or rmeta:get_int("enabled") == 1 then + local timer = minetest.get_node_timer(dat[i].apos) + if not timer:is_started() then + --me.log("TIMER: starting ac now for "..stack:get_name(), "error") + timer:start(pipeworks_craft_time) + end + --me.log("TIMER: registering timer for "..stack:get_name(), "error") + local action_time_step = main_action_time + local action = function(net) + --me.log("ACTION: post craft for "..stack:get_name(), "error") + local inner_stack = stack + -- todo: prove the below is correct. + -- See extra below for how I think it fails. + inner_stack:set_count(craft_count*math.floor((total+i-1)/#dat)) + if i == 1 and extra:get_count() > 0 then + inner_stack:take_item(extra:get_count()) + end + --me.log("TIMER: moving "..inner_stack:get_count().." "..stack:get_name(), "error") + -- deal with output and replacements + local dst_stack = dat[i].rinv:remove_item("dst", inner_stack) + local ctime = next_time+action_time_step + if dst_stack:get_count() ~= inner_stack:get_count() then + --me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") + -- me.log("saw "..dst_stack:get_count().." items instead of "..inner_stack:get_count().." items", "error") + net.ac_status = net.ac_status .. ctime.." Missing "..(inner_stack:get_count()-dst_stack:get_count()).." "..name..", only made "..dst_stack:get_count()..".\n" + end + if not dst_stack:is_empty() then + --me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error") + local leftovers = sink(dst_stack) + if leftovers and not leftovers:is_empty() then + --me.log("autocrafter overflow, backpressuring", "error") + net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + -- If any don't fit, back pressure on the crafter, we don't + -- mean to do this, and want to chunk the crafting items smaller + dat[i].rinv:add_item("dst", leftovers) + end + end + if i == 1 and not extra:is_empty() then + -- extra is once, not per machine. It will appear in the + -- first machine as extra. + -- todo: extra I think is broken by switch the dst getter from being count based + -- to being total*craft count based. This leaves extra when we need to craft + -- for a recipe that needs less than an even multiple of the craft_count. Test, broken. + dst_stack = dat[i].rinv:remove_item("dst", extra) + if dst_stack:get_count() ~= extra:get_count() then + --me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") + --me.log("saw "..dst_stack:get_count().." items instead of "..extra:get_count().." items", "error") + net.ac_status = net.ac_status .. ctime.." Missing "..(extra:get_count() - dst_stack:get_count()).." extra "..name..".\n" + end + if not dst_stack:is_empty() then + local leftovers = me.insert_item(dst_stack, net, inv, "main") + net:set_storage_space(true) + if leftovers and not leftovers:is_empty() then + --me.log("autocrafter overflow, backpressuring", "error") + net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + -- If any don't fit, back pressure on the crafter, we don't + -- mean to do this, and want to chunk the crafting items smaller + dat[i].rinv:add_item("dst", leftovers) + end + end + end + if second_output then + local second = dat[i].rinv:remove_item("dst", second_output) + if second and not second:is_empty() then + local leftovers = me.insert_item(second, net, inv, "main") + if leftovers and not leftovers:is_empty() then + --me.log("autocrafter overflow, backpressuring", "error") + net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + -- If any don't fit, back pressure on the crafter, we don't + -- mean to do this, and want to chunk the crafting items smaller + dat[i].rinv:add_item("dst", leftovers) + end + end + end + --me.log("ACTION: done post craft for "..stack:get_name(), "error") + end + local net,cpos = me.get_connected_network(dat[i].ipos) + me.later(net, cpos, action, next_time + action_time_step) + end + end + --me.log("ACTION: main done for "..stack:get_name(), "error") + end + + local net,cpos = me.get_connected_network(dat[1].ipos) + -- queue main action for later + --me.log("LATER: main action for "..stack:get_name().." in "..next_time.." seconds", "error") + me.later(net, cpos, main_action, next_time) + + -- The step time is the prep time and the main_action_time + local step_time = next_time - time + main_action_time + return true, step_time +end + +-- time is absolute, starting from 0 from the front of a craft or +-- non-zero if a previous craft was running. +function me.later(net, cpos, action, time) + if not net.pending then + net.pending = {} + net.pending.time = {} + end + local i = (net.pending.max_index or 0) + 1 + net.pending.max_index = i + net.pending[i] = action + net.pending.time[i] = time + if not net.pending.index then + net.pending.index = 1 + end + if i == 1 then + me.log("TIMER: starting timer to fire at "..time.." seconds", "error") + me.start_crafting(cpos, time+0.1) + else + -- me.log("TIMER: did not start timer for later, index "..i.." at time "..time, "error") + -- bubble sort the entry back to the right spot + while i > 1 do + -- me.log("TIME ds: "..i.." "..net.pending.time[i].." "..net.pending.time[i-1], "error") + if tonumber(net.pending.time[i]) and tonumber(net.pending.time[i-1]) + and net.pending.time[i] < net.pending.time[i-1] then + -- if out of order, swap. This works as previously the list was sorted + local t = net.pending.time[i-1] + net.pending.time[i-1] = net.pending.time[i] + net.pending.time[i] = t + t = net.pending[i-1] + net.pending[i-1] = net.pending[i] + net.pending[i] = t + if i == 2 then + me.start_crafting(cpos, net.pending.time[1]+0.1) + end + else + break + end + i = i - 1 + end + end +end + +function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) + local ostack = linv:get_stack("output", 1) + local name = ostack:get_name() + --me.log("crafting "..name.." "..tostring(count), "error") + + local stack = ItemStack(name) + local craft_count = ostack:get_count() + --me.log("auto: craft_count "..craft_count.." count "..count, "error") + -- we craft a minimum of count, to the multiple of the crafting count + count = math.ceil(count/craft_count) + --me.log("auto: count is now "..count, "error") + stack:set_count(count*craft_count) + --me.log("auto: stack size is now "..stack:get_count(), "error") + --me.log("auto: and build count is "..(count*craft_count), "error") + + -- me.log("autocrafters: "..minetest.serialize(net.autocrafters), "error") + + if not net.process then + -- rewalk the interfaces on the network to rebuild the machines. + net:reload_network() + end + if net.autocrafters[name] or net.process[name] then + --me.log("using pipeworks autocrafter", "error") + if not net.pending or not net.ac_status then + net.ac_status = "" + end + local start_time = me.autocraft_next_start(net) or 0 + net.ac_status = net.ac_status .. start_time.." using pipeworks autocrafter\n" + local sink = function(stack) + local leftovers = me.insert_item(stack, net, inv, "main") + net:set_storage_space(true) + return leftovers + end + local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, sink, start_time) + if built then + --me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") + net.ac_status = net.ac_status .. start_time.." Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds.\n" + else + --me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") + net.ac_status = net.ac_status .. start_time.." Can't craft "..(count*craft_count).." "..name..".\n" + end + return + end + + --me.log("using microexpansion autocrafter", "error") + local consume = {} + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and inp:get_name() ~= "" then + consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() + end + end + local replace = true + for name, count in pairs(consume) do + local stack = ItemStack(name) + if count >= math.pow(2,16) then + replace = false + break + end + stack:set_count(count) + replace = replace and inv:contains_item("main", stack) + end + if replace then + for name, count in pairs(consume) do + local stack = ItemStack(name) + stack:set_count(count) + --me.log("REMOVE: "..count.." "..stack:get_name(), "error") + if not inv:contains_item("main", stack) then + fixme1() + end + local ret = me.remove_item(net, inv, "main", stack) + if ret:get_count() ~= stack:get_count() then + --me.log("AUTO: found "..(ret:get_count()).." "..(stack:get_name()).." but wanted "..stack:get_count(), "error") + -- fixme2() + end + end + local leftovers = me.insert_item(stack, net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(cpos, leftovers) + end + net:set_storage_space(true) + -- deal with replacements + local hash = minetest.hash_node_position(cpos) + local craft = autocrafterCache[hash] or me.get_craft(cpos, linv, hash) + for i = 1, 9 do + if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count() + or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name()) + and not craft.decremented_input.items[i]:is_empty() then + local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main") + net:set_storage_space(true) + if not leftovers:is_empty() then + me.leftovers(cpos, leftovers) + end + end + if replace then + linv:set_stack("output", 1, craft.output.item) + else + linv:set_list("output", {}) + end + end + end +end diff --git a/modules/network/ctrl.lua b/modules/network/ctrl.lua index e423e1f..fc88ebe 100644 --- a/modules/network/ctrl.lua +++ b/modules/network/ctrl.lua @@ -45,20 +45,76 @@ me.register_node("ctrl", { {-0.1875, -0.5, -0.1875, 0.1875, -0.25, 0.1875}, -- Bottom2 }, }, - groups = { cracky = 1, me_connect = 1, }, + groups = { cracky = 1, me_connect = 1, + -- for technic integration + technic_machine = 1, technic_hv = 1, + -- for on/off switch to conserve power + mesecon_effector_off = 1, mesecon = 2, + }, + technic_run = function(pos, node) + local meta = minetest.get_meta(pos) + -- quick cheat sheet for how to wire: + --meta:set_int("HV_EU_input", 23) + --meta:set_int("HV_EU_demand", 45) + --meta:set_int("HV_EU_supply", 1045) + local net = me.get_network(pos) + if not net then + -- This is impossible, delete? + local meta = minetest.get_meta(pos) + meta:set_int("HV_EU_input", 0) + return + end + if net.input ~= meta:get_int("HV_EU_input") then + net.input = meta:get_int("HV_EU_input") + -- technic, bless their heart, changed us if they + -- disconnect. Upon reconnection, we need to update back. + meta:set_string("infotext", "Network connected") + me.log("EU: input changed to "..net.input, "error") + -- This only needs to see changes to inbound power levels. + me.send_event(pos, "power") + end + end, + technic_on_disable = function (pos, node) + local net = me.get_network(pos) + if net and net.input ~= 0 then + --me.log("NET: we are losing power", "error") + local meta = minetest.get_meta(pos) + meta:set_string("HV_EU_input", "") + net.input = 0 + me.send_event(pos, "power") + end + end, connect_sides = "nobottom", me_update = function(pos,_,ev) local meta = minetest.get_meta(pos) if meta:get_string("source") ~= "" then return end - local cnet = me.get_network(pos) - if cnet == nil then - microexpansion.log("no network for ctrl at pos "..minetest.pos_to_string(pos),"error") + local net = me.get_network(pos) + if net == nil then + me.log("no network for ctrl at pos "..minetest.pos_to_string(pos), "error") return end - cnet:update() + net:update() end, + mesecons = {effector = { + action_on = function (pos, node) + local net = me.get_network(pos) + --me.log("SWITCH: on", "error") + -- turn OFF on mese power + local meta = minetest.get_meta(pos) + meta:set_int("enabled", 0) + net:update_demand() + end, + action_off = function (pos, node) + --me.log("SWITCH: off", "error") + local net = me.get_network(pos) + -- turn ON without mesepower + local meta = minetest.get_meta(pos) + meta:set_int("enabled", 1) + net:update_demand() + end + }}, on_construct = function(pos) local meta = minetest.get_meta(pos) local net,cp = me.get_connected_network(pos) @@ -68,8 +124,9 @@ me.register_node("ctrl", { net = network.new({controller_pos = pos}) table.insert(me.networks,net) end - me.send_event(pos,"connect",{net=net}) - meta:set_string("infotext", "Network Controller") + me.send_event(pos, "connect", {net=net}) + meta:set_int("enabled", 0) + net:update_demand() end, after_place_node = function(pos, player) local name = player:get_player_name() @@ -108,22 +165,22 @@ me.register_node("ctrl", { on_destruct = function(pos) local net,idx = me.get_network(pos) --disconnect all those who need the network - me.send_event(pos,"disconnect",{net=net}) + me.send_event(pos, "disconnect", {net=net}) if net then if me.promote_controller(pos,net) then --reconnect with new controller - me.send_event(pos,"reconnect",{net=net}) + me.send_event(pos, "reconnect", {net=net}) else net:destruct() if idx then table.remove(me.networks,idx) end --disconnect all those that haven't realized the network is gone - me.send_event(pos,"disconnect") + me.send_event(pos, "disconnect") end else -- disconnect just in case - me.send_event(pos,"disconnect") + me.send_event(pos, "disconnect") end end, after_destruct = function(pos) @@ -133,6 +190,38 @@ me.register_node("ctrl", { machine = { type = "controller", }, + on_timer = function(pos, elapsed) + ::top:: + --me.log("TIMER: starting service", "error") + local net = me.get_network(pos) + if not net then return false end + if not net.pending then return false end + local i = net.pending.index + local action = net.pending[i] + -- me.log("TIMER: doing service", "error") + if not action then + net.pending = nil + return false + end + local prev_time = net.pending.time[i] + action(net) + net.pending[i] = nil + net.pending.time[i] = nil + net.pending.index = i + 1 + if net.pending[i+1] then + local next_action_time = net.pending.time[i+1] + local step_time = next_action_time - prev_time + --me.log("TIMER: starting next timer for "..step_time.." seconds", "error") + if step_time == 0 then + goto top + end + me.start_crafting(pos, step_time) + else + -- me.log("TIMER: ending service", "error") + net.pending = nil + end + return false + end, }) minetest.register_lbm({ @@ -176,7 +265,7 @@ me.register_machine("cable", { }, recipe = { { 12, "shapeless", { - "microexpansion:steel_infused_obsidian_ingot", "microexpansion:machine_casing" + "microexpansion:steel_infused_obsidian_ingot", "microexpansion:machine_casing" }, } }, @@ -211,7 +300,7 @@ me.register_machine("cable", { end, on_construct = function(pos) --perhaps this needs to be done after the check if it can be placed - me.send_event(pos,"connect") + me.send_event(pos, "connect") end, after_place_node = function(pos, placer) if not placer then @@ -237,19 +326,21 @@ me.register_machine("cable", { end, after_destruct = function(pos) --FIXME: write drives before disconnecting - me.send_event(pos,"disconnect") + me.send_event(pos, "disconnect") end, me_update = function(pos,_,ev) if ev then if ev.type ~= "disconnect" then return end end --maybe this shouldn't be called on every update + if false then local meta = minetest.get_meta(pos) if me.get_connected_network(pos) then meta:set_string("infotext", "Network connected") else meta:set_string("infotext", "No Network") end + end end, machine = { type = "conductor", @@ -260,3 +351,11 @@ if me.uinv_category_enabled then unified_inventory.add_category_item("storage", "microexpansion:ctrl") unified_inventory.add_category_item("storage", "microexpansion:cable") end + +if technic then + -- quick cheat sheet for how to wire: + -- producer receiver, producer_receiver, battery + technic.register_machine("HV", "microexpansion:ctrl", technic.receiver) +else +no_technic() +end diff --git a/modules/network/init.lua b/modules/network/init.lua index 9708589..abcdcd0 100644 --- a/modules/network/init.lua +++ b/modules/network/init.lua @@ -33,7 +33,7 @@ local function split_stack_values(stack) end --]] -function me.insert_item(stack, inv, listname) +function me.insert_item(stack, net, inv, listname) if me.settings.huge_stacks == false then return inv:add_item(listname, stack) end @@ -60,9 +60,16 @@ function me.insert_item(stack, inv, listname) if not found then return inv:add_item(listname, stack) end + return ItemStack(), slot end +function me.remove_item(net, inv, listname, stack) + return inv:remove_item(listname, stack) +end + +dofile(path.."/loan.lua") -- Loan Management dofile(path.."/network.lua") -- Network Management +dofile(path.."/autocraft.lua") -- Autocrafting -- generate iterator to find all connected nodes function me.connected_nodes(start_pos,include_ctrl) diff --git a/modules/network/loan.lua b/modules/network/loan.lua new file mode 100644 index 0000000..421b249 --- /dev/null +++ b/modules/network/loan.lua @@ -0,0 +1,111 @@ +local me = microexpansion +local loan = { +} +me.loan = loan + +function me.loan.get_stack(net, inv, loan_slot) + local lstack = inv:get_stack("loan", loan_slot) + return lstack +end + +function me.loan.set_stack(net, inv, loan_slot, lstack) + inv:set_stack("loan", loan_slot, lstack) +end + +function me.loan.get_size(net, inv) + return inv:get_size("loan") +end + +function me.loan.set_size(net, inv, size) + return inv:set_size("loan", size) +end + +function me.loan.bump_loan(net, inv, loan_slot, remaining, addition_real_loaned) + local lstack = me.loan.get_stack(net, inv, loan_slot) + local ref + local real_count + if lstack:is_empty() then + -- TODO: should never happen, verify and remove + return + end + ref = me.network.get_ref(lstack) + real_count = nil + local prev_lstack_count = lstack:get_count() + if ref.drawer then + local stack = ItemStack(lstack) + -- the drawer api only allow up to 2^16-1, ask them for a better api, 2^48 max + local count = math.pow(2,16)-1 + -- ensure that lstack:get_count() + spare below is at most 2^16-1 + count = math.min(count, math.pow(2,16)-1 - lstack:get_count()) + -- ensure at most 2^16-1 as the stack api doesn't allow more + count = math.min(count, remaining) + -- if the loan size is already maximal, then we can't update the loan any + if count > 0 then + stack:set_count(count) + + -- bump up the actual inventory, spare is how many fit + local excess = drawers.drawer_insert_object(ref.pos, stack, ref.slot) + local spare = count - excess:get_count() + + -- bump loan by spare + lstack:set_count(lstack:get_count() + spare) + me.log("LOAN: bump_loan to "..lstack:get_count(), "error") + me.loan.set_stack(net, inv, loan_slot, lstack) + net.counts[lstack:get_name()] = net.counts[lstack:get_name()] + spare + me.log("COUNT: loan now to "..net.counts[lstack:get_name()].." "..lstack:get_name()..", "..spare.." more", "error") + me.log("LOAN: adril "..addition_real_loaned.." loan "..prev_lstack_count.." and adjustment(spare) "..spare, "error") + addition_real_loaned = math.min(addition_real_loaned, spare) + me.add_capacity(ref.ipos, addition_real_loaned) + + -- reduce remaining by spare + remaining = remaining - spare + end + else + local rinv = minetest.get_meta(ref.pos):get_inventory() + local real_stack = rinv:get_stack(ref.invname, ref.slot) + local max = real_stack:get_stack_max() + if real_stack:get_count() < max then + local spare = max - real_stack:get_count() + spare = math.min(spare, remaining) + -- me.log("bumping "..lstack:get_name().." by "..spare, "error") + + -- bump up the actual inventory by spare + real_stack:set_count(real_stack:get_count() + spare) + rinv:set_stack(ref.invname, ref.slot, real_stack) + + -- bump loan by spare + lstack:set_count(lstack:get_count() + spare) + -- me.log("bumping "..lstack:get_name().." to "..lstack:get_count(), "error") + me.log("LOAN: bump_loan to "..lstack:get_count(), "error") + me.loan.set_stack(net, inv, loan_slot, lstack) + -- me.log("COUNTS: "..minetest.serialize(net.counts), "error") + -- TODO: If we have a single item on loan (1k cell) and we remove it from chest and insert into me, crash + --me.dbg() + net.counts[lstack:get_name()] = net.counts[lstack:get_name()] + spare + me.log("COUNT: bump_loan loan now to "..net.counts[lstack:get_name()].." "..lstack:get_name()..", "..spare.." more", "error") + me.log("LOAN: adril "..addition_real_loaned.." loan "..prev_lstack_count.." and adjustment(spare) "..spare, "error") + addition_real_loaned = math.min(addition_real_loaned, spare) + me.add_capacity(ref.ipos, addition_real_loaned) + + -- reduce remaining by spare + remaining = remaining - spare + end + end + -- This code is misguided. We've already added them as real, we can + -- only add them to a loan if those items are added to that + -- inventory being loaded. This is only possible if there is room. + if false and remaining > 0 then + if not net.bias then + net.bias = {} + end + if not net.bias["loan"] then + net.bias["loan"] = {} + end + if not net.bias["loan"][loan_slot] then + net.bias["loan"][loan_slot] = 0 + end + net.bias["loan"][loan_slot] = net.bias["loan"][loan_slot] + remaining + me.log("LARGE: bump_loan bias is "..net.bias["loan"][loan_slot].." "..lstack:get_name()..", "..remaining.." more", "error") + end + return remaining +end diff --git a/modules/network/network.lua b/modules/network/network.lua index 4cef251..46bff75 100644 --- a/modules/network/network.lua +++ b/modules/network/network.lua @@ -17,7 +17,7 @@ local access_level = microexpansion.constants.security.access_levels --- construct a new network -- @function [parent=#network] new --- @param #table o the object to become a network or nil +-- @param #table or the object to become a network or nil -- @return #table the new network object function network.new(o) return setmetatable(o or {}, {__index = network}) @@ -173,7 +173,7 @@ end function network:remove_power_capacity(power) self.power_storage = self.power_storage - power if self.power_storage < 0 then - microexpansion.log("power storage of network "..self.." dropped below zero","warning") + me.log("power storage of network "..self.." dropped below zero","warning") end end @@ -212,12 +212,12 @@ function network:get_item_capacity() return cap end -local function remove_slots(inv,ln,target,csize) +function network:remove_slots(inv,ln,target,csize) for i = target, csize do local s = inv:get_stack(ln,i) if not s:is_empty() then inv:set_stack(ln, i, "") - me.insert_item(s, inv, ln) + me.insert_item(s, self, inv, ln) end end --perhaps allow list removal @@ -257,7 +257,7 @@ function network:set_storage_space(count,listname) inv:set_size(ln, needed) elseif needed < 0 then needed = needed + csize - remove_slots(inv,ln,needed,csize) + self:remove_slots(inv,ln,needed,csize) end -- autosave network data me.autosave() @@ -265,6 +265,7 @@ end function network:update() self:set_storage_space(true) + self:update_demand() end function network:get_inventory_name() @@ -302,8 +303,10 @@ local function create_inventory(net) end local inside_stack = inv:get_stack(listname, index) local stack_name = stack:get_name() - if minetest.get_item_group(stack_name, "microexpansion_cell") > 0 then - return 0 + if minetest.get_item_group(stack_name, "microexpansion_cell") > 0 and + stack:get_meta():get_string("items") ~= "" and + stack:get_meta():get_string("items") ~= "return {}" then + return 0 end -- improve performance by skipping unnessecary calls if inside_stack:get_name() ~= stack_name or inside_stack:get_count() >= inside_stack:get_stack_max() then @@ -329,7 +332,10 @@ local function create_inventory(net) end, on_put = function(inv, listname, _, stack) inv:remove_item(listname, stack) - me.insert_item(stack, inv, listname) + local leftovers = me.insert_item(stack, net, inv, listname) + if not leftovers:is_empty() then + me.leftovers(net.controller_pos, leftovers) + end net:set_storage_space(true) end, allow_take = function(_, _, _, stack, player) @@ -382,6 +388,84 @@ function network:load() end end +-- Helper to check to see if the controller is on and powered. +function network:powered(name) + if not name and minetest.localplayer then + -- this works for the client side only + name = minetest.localplayer:get_name() + -- todo: on the server side, how do we get the player name? + end + local net = self + local meta = minetest.get_meta(net.controller_pos) + local run = meta:get_int("enabled") == 1 + if not run then + if name then minetest.chat_send_player(name, "Please enable by turning controller switch.") end + return false + end + --me.log("NETWORK: powered power level input is "..meta:get_int("HV_EU_input").." and demand is "..meta:get_int("HV_EU_demand"), "error") + run = not technic or (meta:get_int("HV_EU_input") >= meta:get_int("HV_EU_demand") and meta:get_int("HV_EU_input") > 0) + if not run then + if name then minetest.chat_send_player(name, "Please provide HV power to ME controller.") end + return false + end + return true +end + +function network:update_demand() + local pos = self.controller_pos + local meta = minetest.get_meta(pos) + local net = self + if meta:get_int("enabled") == 0 then + if meta:get_int("HV_EU_demand") ~= 0 then + meta:set_int("HV_EU_demand", 0) + meta:set_string("infotext", "Disabled") + me.send_event(pos, "power") + end + return + end + local demand = 120 -- controller is 120 + for ipos in me.connected_nodes(pos) do + local name = me.get_node(ipos).name + if name == "microexpansion:cable" then + demand = demand + 1 -- cables are 1 + elseif name == "microexpansion:interface" then + local meta = minetest.get_meta(ipos) + local inventories = minetest.deserialize(meta:get_string("connected")) + demand = demand + #inventories * 20 + 40 -- interfaces are 40 and 20 for each machine or inventory + else + demand = demand + 20 -- everything else is 20 + end + end + if meta:get_int("HV_EU_demand") ~= demand then + local name = meta:get_string("owner") + meta:set_string("infotext", "Network Controller (owned by "..name..")") + me.log("NET: demand changed to "..demand, "error") + meta:set_int("HV_EU_demand", demand) + me.send_event(pos, "power") + end +end + +-- We don't save this data, rather we rewalk upon first use. If 1% of +-- the people play per reboot, then this saves 99% of the work. +-- Also, we don't actually read or write any of this data normally, +-- only for active users, using 1% of the memory. +-- TODO: I think all the storage for me should be handled the same way. +-- As it is, we needlessly read and write all the networks for all the users and +-- writing isn't crash friendly, whereas rewalking is crash friendly. +-- We don't reload the loans, that is saved and restored already. +function network:reload_network() + self.autocrafters = {} + self.autocrafters_by_pos = {} + self.process = {} + for ipos in me.connected_nodes(self.controller_pos) do + local name = me.get_node(ipos).name + if name == "microexpansion:interface" then + me.reload_interface(self, ipos, nil) + end + end + self:update_demand() +end + function network:serialize() local sert = {} for i,v in pairs(self) do @@ -403,3 +487,6 @@ function network:destruct() self.controller_pos = nil self.inv = nil end + +function network:update_counts() +end diff --git a/modules/storage/cmonitor.lua b/modules/storage/cmonitor.lua new file mode 100644 index 0000000..1380e09 --- /dev/null +++ b/modules/storage/cmonitor.lua @@ -0,0 +1,266 @@ +-- crafting monitor +-- microexpansion/cmonitor.lua + +local me = microexpansion + +-- [me chest] Get formspec +local function chest_formspec(pos, start_id, listname, page_max, q) + local list + local page_number = "" + local buttons = "" + local query = q or "" + local net,cpos = me.get_connected_network(pos) + + if cpos then + local inv = net:get_inventory() + if listname and (inv:get_size(listname) > 0 or net:get_item_capacity() > 0) then + local ctrlinvname = net:get_inventory_name() + if listname == "ac" then + list = "list[detached:"..ctrlinvname..";" + .. listname .. ";0,0.3;4,4;" .. (start_id - 1) .. "]" + else + list = "list[context;" .. listname .. ";0,0.3;4,4;" .. (start_id - 1) .. "]" + end + if minetest.get_modpath("i3") then + list = list .. [[ + list[current_player;main;0,8.5;9,4;] + ]] + else + list = list .. [[ + list[current_player;main;0,8.5;8,1;] + list[current_player;main;0,9.73;8,3;8] + ]] + end + list = list .. [[ + listring[current_player;main] + listring[detached:]]..ctrlinvname..[[;ac] + listring[current_player;main] + ]] + local status = "The status of the crafter is: " .. + ((net.pending and "running " .. #net.pending .. " steps\n") or "idle\n") + status = status .. (net.ac_status or "") + buttons = [[ + button[0.8,5.1;0.8,0.9;prev;<] + button[2.65,5.1;0.8,0.9;next;>] + tooltip[prev;Previous] + tooltip[next;Next] + field[0.29,4.6;2.2,1;filter;;]]..query..[[] + button[2.1,4.5;0.8,0.5;search;?] + button[2.75,4.5;1.6,0.5;refresh;Refresh] + button[0,5.28;0.8,0.5;clear;X] + tooltip[search;Search] + tooltip[refresh;Refresh] + tooltip[clear;Reset] + textarea[4.75,0;4.65,8;;]] .. status .. ";]" + else + list = "label[3,2;" .. minetest.colorize("blue", "Crafter is idle") .. "]" + end + else + list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]" + end + if page_max then + page_number = "label[1.55,5.25;" .. math.floor((start_id / 16)) + 1 .. + "/" .. page_max .."]" + end + + if net and not net:powered() then + list = "label[3,2;" .. minetest.colorize("red", "No power!") .. "]" + buttons = "" + page_number = "" + end + + return [[ + size[9,12.5] + ]].. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + list .. + [[ + label[0,-0.23;ME Crafting Monitor] + field_close_on_enter[filter;false] + ]].. + page_number .. + buttons +end + +local function update_chest(pos,_,ev) + --for now all events matter + + local net = me.get_connected_network(pos) + local meta = minetest.get_meta(pos) + if net == nil then + meta:set_int("page", 1) + meta:set_string("formspec", chest_formspec(pos, 1)) + return + end + local inv = net:get_inventory() + local page_max = math.floor(inv:get_size("ac") / 16) + 1 + + meta:set_string("inv_name", "ac") + meta:set_string("formspec", chest_formspec(pos, 1, "ac", page_max)) +end + +-- [me cmonitor] Register node +me.register_node("cmonitor", { + description = "ME Crafting Monitor", + usedfor = "Monitors crafting in ME networks", + tiles = { + "chest_top", + "chest_top", + "chest_side", + "chest_side", + "chest_side", + "chest_front", + }, + recipe = { + { 1, { + {"microexpansion:cterminal", "default:chest"}, + }, + } + }, + is_ground_content = false, + groups = { cracky = 1, me_connect = 1 }, + paramtype = "light", + paramtype2 = "facedir", + me_update = update_chest, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", chest_formspec(pos, 1)) + meta:set_string("inv_name", "none") + meta:set_int("page", 1) + + local own_inv = meta:get_inventory() + + local net = me.get_connected_network(pos) + me.send_event(pos, "connect", {net=net}) + if net then + update_chest(pos) + end + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + can_dig = function(pos, player) + return true + end, + allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + --me.log("Allow a move from "..from_list.." to "..to_list, "error") + local meta = minetest.get_meta(pos) + if to_list == "search" then + local net = me.get_connected_network(pos) + local linv = minetest.get_meta(pos):get_inventory() + local inv = net:get_inventory() + local stack = linv:get_stack(from_list, from_index) + stack:set_count(count) + local leftovers = me.insert_item(stack, net, inv, "ac") + return count - leftovers:get_count() + end + return count + end, + allow_metadata_inventory_take = function(pos, listname, index, stack, player) + -- This is used for removing items from "search". + --me.log("Allow a take from "..listname, "error") + local count = stack:get_count() + return count + end, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + return stack:get_count() + end, + on_metadata_inventory_put = function(pos, listname, _, stack) + if listname == "search" or listname == "ac" then + -- done above in allow, nothing left to do here + end + end, + on_metadata_inventory_take = function(pos, listname, index, stack) + --me.log("A taking of "..stack:get_name().." from "..listname, "error") + if listname ~= "ac" then + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + me.remove_item(net, inv, "ac", stack) + end + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + end, + on_receive_fields = function(pos, _, fields, sender) + local net,cpos = me.get_connected_network(pos) + if net then + if cpos then + me.log("network and ctrl_pos","info") + else + me.log("network but no ctrl_pos","warning") + end + else + if cpos then + me.log("no network but ctrl_pos","warning") + else + me.log("no network and no ctrl_pos","info") + end + end + local meta = minetest.get_meta(pos) + local page = meta:get_int("page") + local inv_name = meta:get_string("inv_name") + local own_inv = meta:get_inventory() + local ctrl_inv + if cpos then + ctrl_inv = net:get_inventory() + else + me.log("no network connected","warning") + return + end + local inv + if inv_name == "ac" then + inv = ctrl_inv + assert(inv,"no control inv") + else + inv = own_inv + assert(inv,"no own inv") + end + local page_max = math.floor(inv:get_size(inv_name) / 16) + 1 + if inv_name == "none" then + return + end + if fields.next then + if page + 16 > inv:get_size(inv_name) then + return + end + meta:set_int("page", page + 16) + meta:set_string("formspec", chest_formspec(pos, page + 16, inv_name, page_max)) + elseif fields.prev then + if page - 16 < 1 then + return + end + meta:set_int("page", page - 16) + meta:set_string("formspec", chest_formspec(pos, page - 16, inv_name, page_max)) + elseif fields.search or fields.key_enter_field == "filter" then + own_inv:set_size("search", 0) + if fields.filter == "" then + meta:set_int("page", 1) + meta:set_string("inv_name", "ac") + meta:set_string("formspec", chest_formspec(pos, 1, "ac", page_max)) + else + local tab = {} + --me.log("SEARCH: ac size is "..ctrl_inv:get_size("ac").." and searching for "..fields.filter, "error") + for i = 1, ctrl_inv:get_size("ac") do + local match = ctrl_inv:get_stack("ac", i):get_name():find(fields.filter) + --me.log("SEARCH: "..ctrl_inv:get_stack("ac", i):get_name().." "..((match and "true") or "false"), "error") + if match then + tab[#tab + 1] = ctrl_inv:get_stack("ac", i) + end + end + own_inv:set_list("search", tab) + meta:set_int("page", 1) + meta:set_string("inv_name", "search") + meta:set_string("formspec", chest_formspec(pos, 1, "search", page_max, fields.filter)) + end + elseif fields.refresh then + meta:set_string("formspec", chest_formspec(pos, page, inv_name, page_max)) + elseif fields.clear then + own_inv:set_size("search", 0) + ctrl_inv:set_size("ac", 0) + meta:set_int("page", 1) + meta:set_string("inv_name", "ac") + net.pending = nil + meta:set_string("formspec", chest_formspec(pos, 1, "ac", page_max)) + end + end, +}) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua new file mode 100644 index 0000000..dd23de9 --- /dev/null +++ b/modules/storage/cterminal.lua @@ -0,0 +1,909 @@ +-- crafting terminal +-- microexpansion/cterminal.lua + +-- TODO: Bugs, can't craft sticks, oil extract by using the +-- output. Does work when updating the recipe. Groups are hanky. We +-- only use the last recipe registered. Would be nice to be able to +-- cycle trough them. We handle this by merely requiring the user to +-- select the input recipe they want. + +-- TODO: Bugs in original, if you remove controller, this wipes all drives +-- Spacing sucks. + +-- The search list doesn't update when main updates or when autocrafting updates. + +local me = microexpansion +local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false + +-- caches some recipe data to avoid to call the slow function minetest.get_craft_result() every second +me.autocrafterCache = {} +local autocrafterCache = me.autocrafterCache + +-- [me chest] Get formspec +local function chest_formspec(pos, start_id, listname, page_max, q, c) + local list + local page_number = "" + local buttons = "" + local query = q or "" + local crafts = (c and "true") or "false" + local net,cpos = me.get_connected_network(pos) + + if cpos then + local inv = net:get_inventory() + if listname and (inv:get_size(listname) > 0 or net:get_item_capacity() > 0) then + local ctrlinvname = net:get_inventory_name() + if listname == "main" then + list = "list[detached:"..ctrlinvname..";" + .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + else + list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + end + if minetest.get_modpath("i3") then + list = list .. [[ + list[current_player;main;0,8.5;9,4;] + ]] + else + list = list .. [[ + list[current_player;main;0,8.5;8,1;] + list[current_player;main;0,9.73;8,3;8] + ]] + end + list = list .. [[ + list[context;recipe;0.22,5.22;3,3;] + list[context;output;4,6.22;1,1;] + ]] + list = list .. [[ + listring[current_player;main] + listring[detached:]]..ctrlinvname..[[;main] + listring[current_player;main] + listring[context;recipe] + listring[current_player;main] + listring[context;output] + listring[current_player;main] + listring[context;search] + listring[current_player;main] + ]] + buttons = [[ + button[3.56,4.35;1.8,0.9;tochest;To Drive] + tooltip[tochest;Move everything from your inventory to the ME network.] + button[5.4,4.35;0.8,0.9;prev;<] + button[7.25,4.35;0.8,0.9;next;>] + tooltip[prev;Previous] + tooltip[next;Next] + field[0.29,4.6;2.2,1;filter;;]]..query..[[] + button[2.1,4.5;0.8,0.5;search;?] + button[2.75,4.5;0.8,0.5;clear;X] + tooltip[search;Search] + tooltip[clear;Reset] + field[6,5.42;2,1;autocraft;;1] + tooltip[autocraft;Number of items to Craft] + checkbox[6,6.45;crafts;crafts;]]..crafts..[[] + tooltip[crafts;Show only craftable items] + ]] + else + list = "label[3,2;" .. minetest.colorize("red", "No connected storage!") .. "]" + end + else + list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]" + end + if page_max then + page_number = "label[6.15,4.5;" .. math.floor((start_id / 32)) + 1 .. + "/" .. page_max .."]" + end + + if net and not net:powered() then + list = "label[3,2;" .. minetest.colorize("red", "No power!") .. "]" + buttons = "" + page_number = "" + end + + return [[ + size[9,12.5] + ]].. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + list .. + [[ + label[0,-0.23;ME Crafting Terminal] + field_close_on_enter[filter;false] + field_close_on_enter[autocraft;false] + ]].. + page_number .. + buttons +end + +local function update_chest(pos,_,ev) + --me.log("CTERM: got event "..((ev and ev.type) or ""), "error") + --for now all events matter + + local net = me.get_connected_network(pos) + local meta = minetest.get_meta(pos) + if net == nil then + meta:set_int("page", 1) + meta:set_string("formspec", chest_formspec(pos, 1)) + return + end + local size = net:get_item_capacity() + local page_max = me.int_to_pagenum(size) + 1 + + meta:set_string("inv_name", "main") + meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max)) +end + +-- From pipeworks/autocrafter.lua +local function count_index(invlist) + local index = {} + for _, stack in pairs(invlist) do + if not stack:is_empty() then + local stack_name = stack:get_name() + index[stack_name] = (index[stack_name] or 0) + stack:get_count() + end + end + return index +end + +-- From pipeworks/autocrafter.lua +function me.get_craft(pos, inventory, hash) + local hash = hash or minetest.hash_node_position(pos) + local craft = autocrafterCache[hash] + if not craft then + local recipe = inventory:get_list("recipe") + for i = 1, 9 do + if recipe[i]:get_count() > 1 then + recipe[i] = ItemStack(recipe[i]:get_name()) + end + end + local output, decremented_input = minetest.get_craft_result({method = "normal", width = 3, items = recipe}) + craft = {recipe = recipe, consumption=count_index(recipe), output = output, decremented_input = decremented_input} + autocrafterCache[hash] = craft + end + return craft +end + +-- From pipeworks/autocrafter.lua +-- note, that this function assumes allready being updated to virtual items +-- and doesn't handle recipes with stacksizes > 1 +local function after_recipe_change(pos, inventory) + local meta = minetest.get_meta(pos) + -- if we emptied the grid, there's no point in keeping it running or cached + if inventory:is_empty("recipe") then + autocrafterCache[minetest.hash_node_position(pos)] = nil + inventory:set_stack("output", 1, "") + return + end + local recipe = inventory:get_list("recipe") + + local hash = minetest.hash_node_position(pos) + local craft = autocrafterCache[hash] + + if craft then + -- check if it changed + local cached_recipe = craft.recipe + for i = 1, 9 do + if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then + autocrafterCache[hash] = nil -- invalidate recipe + craft = nil + break + end + end + end + + craft = craft or me.get_craft(pos, inventory, hash) + local output_item = craft.output.item + inventory:set_stack("output", 1, output_item) +end + +-- From pipeworks/autocrafter.lua +-- clean out unknown items and groups, which would be handled like unknown items in the crafting grid +-- if minetest supports query by group one day, this might replace them +-- with a canonical version instead +local function normalize(item_list) + for i = 1, #item_list do + local name = item_list[i] + if not minetest.registered_items[name] then + item_list[i] = "" + if name == "group:stick" then + item_list[i] = "default:stick" + elseif name == "group:glass" then + item_list[i] = "default:glass" + elseif name == "group:wood" then + name = "moretrees:oak_planks" + if minetest.registered_items[name] then + item_list[i] = name + else + item_list[i] = "default:wood" + end + elseif name == "group:wood" then + item_list[i] = "moretrees:oak_trunk" + elseif name == "group:wool" then + item_list[i] = "wool:white" + elseif name == "group:sand" then + item_list[i] = "default:sand" + elseif name == "group:stone" then + item_list[i] = "default:cobble" + elseif name == "group:leaves" then + name = "moretrees:sequoia_leaves" + if minetest.registered_items[name] then + item_list[i] = name + else + item_list[i] = "default:leaves" + end + elseif name == "group:coal" then + item_list[i] = "default:coal" + elseif name == "group:tree" then + item_list[i] = "default:tree" + end + end + end + return item_list +end + +-- 0 to 34 +function me.uranium_dust(p) + return "technic:uranium"..(p == 7 and "" or p).."_dust" +end +for pa = 0, 34 do + -- uranium_dust(pa) + -- me.uranium_dust(pa-1).." 2" + -- No, this would require a billion uranium to do this. :-( + -- Make a uranium centrifuge controller and have it be smart. + --me.register_output_by_typename("separating", "") +end + +me.output_by_typename = { + -- aka me.register_output_by_typename("cooking", "default:stone") + -- shared by technic and techage + ["cooking"] = { "default:stone", "default:copper_ingot", "default:gold_ingot", "default:tin_ingot" } +} + +-- Used to register what machine types (typename) produce which outputs. +-- Used to figure out what machine to use to create the given output. +-- If multiple outputs are produced, only use the main output, not the +-- incidental output. +function me.register_output_by_typename(typename, output) + if not me.output_by_typename[typename] then + me.output_by_typename[typename] = {} + end + table.insert(me.output_by_typename[typename], output) +end + +-- shared by technic and techage +me.register_output_by_typename("cooking", "mesecons_materials:glue") +me.register_output_by_typename("cooking", "mesecons_materials:fiber") +me.register_output_by_typename("cooking", "basic_materials:plastic_sheet") +me.register_output_by_typename("cooking", "basic_materials:paraffin") + +function me.register_output_to_inputs(output, inputs) + --me.log("REG: output "..output.." from inputs "..dump(inputs)) + me.map_output_to_inputs[output] = inputs +end + + +me.map_output_to_inputs = { + -- furnace ("cooking") + ["default:stone"] = { ItemStack("default:cobble") }, +} + + + +function me.find_by_output(name) + -- TODO: we'd love to be able to look this stuff up in the recipes. + -- technic.recipes["technic:doped_silicon_wafer"].alloy.recipes[4].input[1] + return me.map_output_to_inputs[name] +end + +function me.register_inventory(name, func) + -- me.log("INVENTORY: registering "..name, "error") + if not me.registered_inventory then + me.registered_inventory = {} + end + me.registered_inventory[name] = func +end + +-- Allow any type of machine process to be registered. For example, +-- "alloy" for an alloy furnace for example. These must be done +-- before specific me.register_inventory calls, as that one needs to +-- override this call. +function me.register_typename(name, typename) + me.block_to_typename_map[name] = typename + me.register_inventory(name, function() end) +end + +me.block_to_typename_map = { +} + +-- default wiring +me.register_typename("default:furnace", "cooking") + +-- default:furnace "cooking" only has 4 output slots +me.register_max("default:copper_ingot", 99*4) +me.register_max("default:gold_ingot", 99*4) +me.register_max("default:steel_ingot", 99*4) +me.register_max("default:tin_ingot", 99*4) +me.register_max("default:stone", 99*4) +me.register_max("mesecons_materials:glue", 99*4) +me.register_max("mesecons_materials:fiber", 99*4) +me.register_max("basic_materials:plastic_sheet", 99*4) +me.register_max("basic_materials:paraffin", 99*4) + +if not technic then + -- default:furnace ("cooking") + me.register_output_to_inputs("default:copper_ingot", { ItemStack("default:copper_lump") }) + me.register_output_to_inputs("default:gold_ingot", { ItemStack("default:gold_lump") }) + me.register_output_to_inputs("default:tin_ingot", { ItemStack("default:tin_lump") }) + me.register_output_to_inputs("mesecons_materials:glue", { ItemStack("default:aspen_sapling") }) +end +me.register_output_to_inputs("mesecons_materials:fiber", { ItemStack("mesecons_materials:glue") }) +me.register_output_to_inputs("basic_materials:plastic_sheet", { ItemStack("basic_materials:paraffin") }) +me.register_output_to_inputs("basic_materials:paraffin", { ItemStack("basic_materials:oil_extract") }) + +-- These must be called after the true name of the machine is defined. +function me.register_machine_alias(alias, name) + me.block_to_typename_map[alias] = me.block_to_typename_map[name] + me.set_speed(alias, me.speed[name]) +end + +function me.get_recipe(typename, inputs) + if technic then + return technic.get_recipe(typename, inputs) + end + if typename == "cooking" then + local result, new_input = minetest.get_craft_result({ + method = "cooking", + width = 1, + items = inputs}) + if not result or result.time == 0 then + return nil + elseif not new_input.items[1]:is_empty() and new_input.items[1]:get_name() ~= items[1]:get_name() then + items[1]:take_item(1) + return {time = result.time, + new_input = {items[1]}, + output = {new_input.items[1], result.item}} + else + return {time = result.time, + new_input = new_input.items, + output = result.item} + end + end +end + +-- TODO: Removing an output when the recipe is empty that is in +-- net.autocrafters should not be allowed as the output in that case +-- is virtual. It can be removed if rinv:"output" has the item iff +-- that item is removed from the autocrafter's output. +local function on_output_change(pos, linv, stack) + local name = stack:get_name() + -- me.log("PROCESS: "..name.." was found0", "error") + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local has_enough = true + local function clear_recipe() + local has_enough = true + for i = 1, 9 do + local prev = linv:get_stack("recipe", i) + if prev and prev:get_name() ~= "" and not inv:room_for_item("main", prev) then + -- full, no room to remove + has_enough = false + elseif prev and prev:get_name() ~= "" and me.insert_item(prev, net, inv, "main"):get_count() > 0 then + net:set_storage_space(true) + -- Don't have to worry about this happening until minetest is fully multithreaded + has_enough = false + else + net:set_storage_space(true) + linv:set_stack("recipe", i, ItemStack("")) + end + end + return has_enough + end + if not net.process then + -- rewalk the interfaces on the network to rebuild loans and machines. + net:reload_network() + end + if net and net.process[name] then + -- me.log("PROCESS: "..name.." was found1", "error") + has_enough = clear_recipe() + local pos,ipos = next(net.process[name]) + if has_enough and pos then + -- me.log("PROCESS: "..name.." was found2", "error") + local inputs = me.find_by_output(name) + -- me.log("PROCESS: inputs are "..dump(inputs), "error") + local machine_name = minetest.get_node(pos).name + local typename = me.block_to_typename_map[machine_name] + local recip = typename and me.get_recipe(typename, inputs) + if recip and recip.output then + recip.intput = inputs + -- me.log("PROCESS: "..name.." was found for "..typename.." on a "..machine_name, "error") + -- freezer can produce two outputs, we only care about the first. + if recip.output[1] then + recip.output = recip.output[1] + end + stack = ItemStack(recip.output) + linv:set_stack("output", 1, stack) + -- me.log("PROCESS: and the output is "..minetest.serialize(recip.output), "error") + -- me.log("PROCESS: and the output is "..stack:get_name(), "error") + else + --me.log("PROCESS: "..name.." was missing from recipe on a "..machine_name, "error") + linv:set_stack("output", 1, ItemStack()) + end + end + return 0 + elseif net and net.autocrafters[name] then + has_enough = clear_recipe() + if has_enough then + local pos,ipos = next(net.autocrafters[name]) + if pos then + local rinv = minetest.get_meta(pos):get_inventory() + stack = ItemStack(rinv:get_stack("output", 1)) + linv:set_stack("output", 1, stack) + else + -- me.log("pos in autocrafters was missing", "error") + linv:set_stack("output", 1, ItemStack()) + end + else + linv:set_stack("output", 1, ItemStack()) + end + return 0 + end + local input = minetest.get_craft_recipe(name) + if not input.items or input.type ~= "normal" then return 0 end + local items, width = normalize(input.items), input.width + local item_idx, width_idx = 1, 1 + for i = 1, 9 do + local prev = linv:get_stack("recipe", i) + if prev and prev:get_name() ~= "" and not inv:room_for_item("main", prev) then + -- full, no room to remove + has_enough = false + if width_idx <= width then + item_idx = item_idx + 1 + end + elseif prev and prev:get_name() ~= "" and me.insert_item(prev, net, inv, "main"):get_count() > 0 then + net:set_storage_space(true) + -- Don't have to worry about this happening until minetest is fully multithreaded + has_enough = false + if width_idx <= width then + item_idx = item_idx + 1 + end + elseif width_idx <= width then + net:set_storage_space(true) + if inv:contains_item("main", items[item_idx]) then + me.remove_item(net, inv, "main", ItemStack(items[item_idx])) + linv:set_stack("recipe", i, items[item_idx]) + else + has_enough = false + linv:set_stack("recipe", i, ItemStack("")) + end + item_idx = item_idx + 1 + else + linv:set_stack("recipe", i, ItemStack("")) + end + width_idx = (width_idx < 3) and (width_idx + 1) or 1 + end + -- we'll set the output slot in after_recipe_change to the actual result of the new recipe + after_recipe_change(pos, linv) + return 0 +end + +-- [me cterminal] Register node +me.register_node("cterminal", { + description = "ME Crafting Terminal", + usedfor = "Can interact with storage cells in ME networks", + tiles = { + "chest_top", + "chest_top", + "chest_side", + "chest_side", + "chest_side", + "chest_front", + }, + recipe = { + { 1, { + {"microexpansion:term", "default:chest"}, + }, + } + }, + is_ground_content = false, + groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 }, + paramtype = "light", + paramtype2 = "facedir", + me_update = update_chest, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("inv_name", "none") + meta:set_int("page", 1) + + local own_inv = meta:get_inventory() + own_inv:set_size("src", 1) + own_inv:set_size("recipe", 3*3) + own_inv:set_size("output", 1) + + local net = me.get_connected_network(pos) + me.send_event(pos, "connect", {net=net}) + update_chest(pos) + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + can_dig = function(pos, player) + if not player then + return false + end + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return false + end + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if not inv:is_empty("recipe") then + return false + end + local net,cp = me.get_connected_network(pos) + if not net then + return true + end + return net:get_access_level(name) >= access_level.modify + end, + allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + --me.log("Allow a move from "..from_list.." to "..to_list, "error") + local meta = minetest.get_meta(pos) + if to_list == "recipe" and from_list == "search" then + local net = me.get_connected_network(pos) + local linv = minetest.get_meta(pos):get_inventory() + local inv = net:get_inventory() + local stack = linv:get_stack(from_list, from_index) + local meta = minetest.get_meta(pos) + count = math.min(count, stack:get_stack_max()) + stack:set_count(count) + me.remove_item(net, inv, "main", stack) + return count + end + if to_list == "output" then + local linv = minetest.get_meta(pos):get_inventory() + local stack = linv:get_stack(from_list, from_index) + return on_output_change(pos, linv, stack) + end + if from_list == "crafts" then + return 0 + end + if to_list == "search" then + local net = me.get_connected_network(pos) + local linv = minetest.get_meta(pos):get_inventory() + local inv = net:get_inventory() + local stack = linv:get_stack(from_list, from_index) + stack:set_count(count) + -- local meta = minetest.get_meta(pos) + -- meta:set_string("infotext", "allow moving: "..stack:get_name()) + -- TODO: Check capacity? Test. + local leftovers = me.insert_item(stack, net, inv, "main") + return count - leftovers:get_count() + end + return count + end, + allow_metadata_inventory_take = function(pos, listname, index, stack, player) + -- This is used for removing items from "search", "recipe" and "output". + --me.log("Allow a take from "..listname, "error") + local count = stack:get_count() + if listname == "search" or listname == "recipe" then + count = math.min(count, stack:get_stack_max()) + end + if listname == "crafts" then + return 0 + end + --[[if listname == "main" then + -- This should be unused, we don't have a local inventory called main. + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local ret = me.remove_item(net, inv, "main", stack) + --me.log("REMOVE: after remove count is "..ret:get_count(), "error") + return ret:get_count() + end + ]] + return count + end, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + -- me.dbg() + if listname == "output" then + local linv = minetest.get_meta(pos):get_inventory() + return on_output_change(pos, linv, stack) + elseif listname == "search" or listname == "main" then + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + -- TODO: Check full inv, should be fixed now, confirm. + local leftovers = me.insert_item(stack, net, inv, "main") + return stack:get_count() - leftovers:get_count() + end + return stack:get_count() + end, + on_metadata_inventory_put = function(pos, listname, _, stack) + -- me.dbg() + if listname == "recipe" then + local linv = minetest.get_meta(pos):get_inventory() + after_recipe_change(pos, linv) + elseif listname == "search" or listname == "main" then + -- done above in allow, nothing left to do here + elseif listname == output then + -- done above + else + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local leftovers = me.insert_item(stack, net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(net.controller_pos, leftovers) + end + net:set_storage_space(true) + end + end, + on_metadata_inventory_take = function(pos, listname, index, stack) + --me.log("A taking of "..stack:get_name().." from "..listname, "error") + if listname == "output" then + local linv = minetest.get_meta(pos):get_inventory() + local num_left = linv:get_stack("output", 1):get_count() + -- We only need to consume the recipe if there are no more items + -- if num_left ~= 0 then return end + if num_left > 1 then return end + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local replace = true + -- This assumes that all inputs are only just 1 item, always true? + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and inp:get_name() ~= "" then + local consume = ItemStack(inp:get_name()) + replace = replace and (inp:get_count() > 1 or inv:contains_item("main", consume)) + end + end + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and inp:get_name() ~= "" then + if inp:get_count() == 1 then + if inv:contains_item("main", inp) then + local r = me.remove_item(net, inv, "main", inp) + if r:get_count() ~= 1 then + linv:set_stack("recipe", i, ItemStack("")) + replace = false + end + else + linv:set_stack("recipe", i, ItemStack("")) + replace = false + end + else + local stack_copy = ItemStack(inp) + stack_copy:set_count(inp:get_count()-1) + linv:set_stack("recipe", i, stack_copy) + end + end + end + -- deal with replacements + local hash = minetest.hash_node_position(pos) + local craft = autocrafterCache[hash] or me.get_craft(pos, linv, hash) + for i = 1, 9 do + if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count() + or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name()) + and not craft.decremented_input.items[i]:is_empty() then + local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(pos, leftovers) + end + end + if replace then + linv:set_stack("output", 1, craft.output.item) + else + linv:set_list("output", {}) + end + end + elseif listname == "recipe" then + local linv = minetest.get_meta(pos):get_inventory() + after_recipe_change(pos, linv) + elseif listname ~= "main" then + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + me.remove_item(net, inv, "main", stack) + end + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + --me.log("A move from "..from_list.." to "..to_list, "error") + if to_list == "recipe" or from_list == "recipe" then + local inv = minetest.get_meta(pos):get_inventory() + after_recipe_change(pos, inv) + end + end, + tube = { + can_insert = function(pos, _, stack) --pos, node, stack, direction + -- TODO: update to use capacity_cache? + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local max_slots = inv:get_size("main") + local max_items = net.capacity_cache + + local slots, items = 0, 0 + -- Get amount of items in drive + for i = 1, max_slots do + local dstack = inv:get_stack("main", i) + if dstack:get_name() ~= "" then + slots = slots + 1 + local num = dstack:get_count() + if num == 0 then num = 1 end + items = items + num + end + end + items = items + stack:get_count() + return max_items > items + end, + insert_object = function(pos, _, stack) + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + local leftovers = me.insert_item(stack, net, inv, "main") + net:set_storage_space(true) + return leftovers + end, + connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, + }, + after_place_node = pipeworks_enabled and pipeworks.after_place, + after_dig_node = pipeworks_enabled and pipeworks.after_dig, + on_receive_fields = function(pos, _, fields, sender) + local net,cpos = me.get_connected_network(pos) + if net then + if cpos then + me.log("network and ctrl_pos","info") + else + me.log("network but no ctrl_pos","warning") + end + else + if cpos then + me.log("no network but ctrl_pos","warning") + else + me.log("no network and no ctrl_pos","info") + end + end + local meta = minetest.get_meta(pos) + local page = meta:get_int("page") + local inv_name = meta:get_string("inv_name") + local crafts = meta:get_string("crafts") == "true" + local own_inv = meta:get_inventory() + local ctrl_inv + if cpos then + ctrl_inv = net:get_inventory() + else + me.log("no network connected","warning") + return + end + local inv + if inv_name == "main" then + inv = ctrl_inv + assert(inv,"no control inv") + else + inv = own_inv + assert(inv,"no own inv") + end + local page_max = math.floor(inv:get_size(inv_name) / 32) + 1 + if inv_name == "none" then + return + end + if fields.next then + if page + 32 > inv:get_size(inv_name) then + return + end + meta:set_int("page", page + 32) + meta:set_string("formspec", chest_formspec(pos, page + 32, inv_name, page_max, fields.filter, crafts)) + elseif fields.prev then + if page - 32 < 1 then + return + end + meta:set_int("page", page - 32) + meta:set_string("formspec", chest_formspec(pos, page - 32, inv_name, page_max, fields.filter, crafts)) + elseif fields.crafts then + meta:set_int("page", 1) + --me.log("CRAFT: craftables: "..dump(net and net.process), "error") + --me.log("CRAFT: got fields: "..dump(fields), "error") + inv_name = "main" + if fields.crafts == "true" then + crafts = true + meta:set_string("crafts", "true") + inv_name = "crafts" + local tab = {} + if net then + if not net.process then + net:reload_network() + end + for name,pos in pairs(net.autocrafters) do + tab[#tab + 1] = ItemStack(name) + end + tab[#tab + 1] = ItemStack("") + for name,pos in pairs(net.process) do + tab[#tab + 1] = ItemStack(name) + end + end + own_inv:set_size(inv_name, #tab) + own_inv:set_list(inv_name, tab) + meta:set_string("inv_name", inv_name) + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) + else + crafts = false + meta:set_string("crafts", "false") + if fields.filter == "" then + own_inv:set_size("crafts", 0) + meta:set_string("inv_name", inv_name) + page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.fields, crafts)) + end + end + if fields.filter ~= "" then + inv = own_inv + if inv_name == "main" then + inv = ctrl_inv + end + local tab = {} + for i = 1, inv:get_size(inv_name) do + local match = inv:get_stack(inv_name, i):get_name():find(fields.filter) + if match then + tab[#tab + 1] = inv:get_stack(inv_name, i) + end + end + inv_name = "search" + own_inv:set_size(inv_name, #tab) + own_inv:set_list(inv_name, tab) + meta:set_string("inv_name", inv_name) + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) + end + elseif fields.search or fields.key_enter_field == "filter" then + own_inv:set_size("search", 0) + --me.log("CRAFT: got fields: "..dump(fields), "error") + meta:set_int("page", 1) + inv_name = "main" + inv = ctrl_inv + if crafts then + inv_name = "crafts" + inv = own_inv + end + if fields.filter == "" then + meta:set_string("inv_name", inv_name) + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) + else + local tab = {} + for i = 1, inv:get_size(inv_name) do + local match = inv:get_stack(inv_name, i):get_name():find(fields.filter) + if match then + tab[#tab + 1] = inv:get_stack(inv_name, i) + end + end + inv_name = "search" + own_inv:set_list(inv_name, tab) + meta:set_string("inv_name", inv_name) + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) + end + elseif fields.clear then + --me.log("CRAFT: got fields: "..dump(fields), "error") + own_inv:set_size("search", 0) + own_inv:set_size("crafts", 0) + meta:set_int("page", 1) + inv_name = "main" + meta:set_string("inv_name", inv_name) + meta:set_string("crafts", "false") + page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 + meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max)) + elseif fields.tochest then + local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()}) + -- TODO: test and fix, net:set_storage_space(pinv:get_size("main")) + local space = net:get_item_capacity() + local contents = ctrl_inv:get_list("main") or {} + for _,s in pairs(contents) do + if not s:is_empty() then + space = space - s:get_count() + end + end + me.move_inv(net, { inv=pinv, name="main" }, { inv=ctrl_inv, name="main", huge=true }, space) + net:set_storage_space(true) + elseif fields.autocraft or fields.key_enter_field == "autocraft" then + if fields.autocraft ~= "" and tonumber(fields.autocraft) ~= nil then + local count = tonumber(fields.autocraft) + fields.autocraft = nil + if not own_inv:get_stack("output", 1):is_empty() and count < math.pow(2,16) then + me.autocraft(autocrafterCache, pos, net, own_inv, ctrl_inv, count) + end + end + end + end, +}) diff --git a/modules/storage/drawer-api.lua b/modules/storage/drawer-api.lua new file mode 100644 index 0000000..aa50023 --- /dev/null +++ b/modules/storage/drawer-api.lua @@ -0,0 +1,42 @@ +--[[ +Minetest Mod Storage Drawers - A Mod adding storage drawers + +Copyright (C) 2017-2020 Linus Jahn +Copyright (C) 2016 Mango Tango + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +function drawers.drawer_take_large_item(pos, itemstack) + local drawer_visuals = drawers.drawer_visuals[core.hash_node_position(pos)] + + if not drawer_visuals then + return ItemStack("") + end + + for _, visual in pairs(drawer_visuals) do + if visual.itemName == itemstack:get_name() then + return visual:take_items(itemstack:get_count()) + end + end + + return ItemStack() +end diff --git a/modules/storage/drawer-interop.lua b/modules/storage/drawer-interop.lua new file mode 100644 index 0000000..16752a7 --- /dev/null +++ b/modules/storage/drawer-interop.lua @@ -0,0 +1,51 @@ +-- Interoperability file for drawers support. +local me = microexpansion + +local S = function(d) + return d +end + +me.register_inventory("drawers:wood1", function(net, ctrl_inv, int_meta, n, pos, doinventories) + if true then return end -- no loans yet + if not doinventories then return end + local c = drawers.drawer_get_content(n.pos, "") + if c.name ~= "" and c.count > 1 then + -- A poor man's locking system will have us never remove the last item from a drawer. + c.count = c.count-1 + local stack = ItemStack(c.name) + local bias = nil + if c.count > math.pow(2,15) then -- assumes me.settings.huge_stacks == true + bias = c.count - math.pow(2,15) + c.count = math.pow(2,15) + end + stack:set_count(c.count) + net:create_loan(stack, {pos=n.pos, drawer=true, slot="", ipos=pos}, ctrl_inv, int_meta, bias) + end + -- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "") + -- meta:set_int("count", meta:get_int("count")+1) + -- drawers.remove_visuals(n.pos) + -- drawers.spawn_visuals(n.pos) +end) + +me.register_inventory("drawers:wood2", function(net, ctrl_inv, int_meta, n, pos, doinventories) + if not doinventories then return end + -- local c = drawers.drawer_get_content(n.pos, "") + -- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "") +end) + +me.register_inventory("drawers:wood4", function(net, ctrl_inv, int_meta, n, pos, doinventories) + if not doinventories then return end + -- local c = drawers.drawer_get_content(n.pos, "") + -- local rest = drawers.drawer_insert_object(n.pos, ItemStack("default:stone"), "") +end) + +me.register_inventory("drawers:controller", function(net, ctrl_inv, int_meta, n, pos) + -- inv:add_item("src", ItemStack("default:stone")) +end) + +drawers.register_drawer_upgrade("microexpansion:upgrade_me64k", { + description = S("Microexpansion Drawer Upgrade (x64*4)"), + inventory_image = "drawers_upgrade_mithril.png", + groups = {drawer_upgrade = 8000}, + recipe_item = "microexpansion:cell_64k" +}) diff --git a/modules/storage/drive.lua b/modules/storage/drive.lua index 403938b..a3756c3 100644 --- a/modules/storage/drive.lua +++ b/modules/storage/drive.lua @@ -40,7 +40,7 @@ local function get_drive_controller(pos) for i,d in pairs(netdrives) do if d.dpos then if vector.equals(pos, d.dpos) then - return d,i + return d,i end end end @@ -52,10 +52,10 @@ local function set_drive_controller(dpos,setd,cpos,i) local dt = netdrives[i] if dt then if setd then - dt.dpos = dpos + dt.dpos = dpos end if cpos ~= nil then - dt.cpos = cpos + dt.cpos = cpos end else netdrives[i] = {dpos = dpos, cpos = cpos} @@ -64,10 +64,10 @@ local function set_drive_controller(dpos,setd,cpos,i) local dt = get_drive_controller(dpos) if dt then if setd then - dt.dpos = dpos + dt.dpos = dpos end if cpos ~= nil then - dt.cpos = cpos + dt.cpos = cpos end else table.insert(netdrives,{dpos = dpos, cpos = cpos}) @@ -89,13 +89,13 @@ local function write_to_cell(cell, items, item_count) return cell end -local function write_drive_cells(pos,network) +local function write_drive_cells(pos, net) local meta = minetest.get_meta(pos) local own_inv = meta:get_inventory() - if network == nil then + if net == nil then return false end - local ctrl_inv = network:get_inventory() + local ctrl_inv = net:get_inventory() local cells = {} for i = 1, own_inv:get_size("main") do local cell = own_inv:get_stack("main", i) @@ -113,39 +113,39 @@ local function write_drive_cells(pos,network) local cell_items = {} for i = 1, ctrl_inv:get_size("main") do - local stack_inside = ctrl_inv:get_stack("main", i) - local item_string = stack_inside:to_string() + local stack = ctrl_inv:get_stack("main", i) + local item_string = stack:to_string() if item_string ~= "" then item_string = item_string:split(" ") - local item_count = stack_inside:get_count() + local item_count = stack:get_count() if item_count > 1 and item_string[2] ~= tostring(item_count) then - microexpansion.log("stack count differs from second field of the item string","warning") + me.log("stack count differs from second field of the item string", "warning") end while item_count ~= 0 and cell_idx ~= nil do - --print(("stack to store: %q"):format(table.concat(item_string," "))) + --print(("stack to store: %q"):format(table.concat(item_string," "))) if size < items_in_cell_count + item_count then - local space = size - items_in_cell_count - item_string[2] = tostring(space) - table.insert(cell_items,table.concat(item_string," ")) - items_in_cell_count = items_in_cell_count + space + local space = size - items_in_cell_count + item_string[2] = tostring(space) + table.insert(cell_items,table.concat(item_string," ")) + items_in_cell_count = items_in_cell_count + space - own_inv:set_stack("main", cell_idx, write_to_cell(cells[cell_idx],cell_items,items_in_cell_count)) - cell_idx = next(cells, cell_idx) - if cell_idx == nil then - --there may be other drives within the network - microexpansion.log("too many items to store in drive","info") - break - end - size = microexpansion.get_cell_size(cells[cell_idx]:get_name()) - items_in_cell_count = 0 - cell_items = {} - item_count = item_count - space - else - items_in_cell_count = items_in_cell_count + item_count - item_string[2] = tostring(item_count) - table.insert(cell_items,table.concat(item_string," ")) - item_count = 0 - end + own_inv:set_stack("main", cell_idx, write_to_cell(cells[cell_idx],cell_items,items_in_cell_count)) + cell_idx = next(cells, cell_idx) + if cell_idx == nil then + --there may be other drives within the network + me.log("too many items to store in drive","info") + break + end + size = microexpansion.get_cell_size(cells[cell_idx]:get_name()) + items_in_cell_count = 0 + cell_items = {} + item_count = item_count - space + else + items_in_cell_count = items_in_cell_count + item_count + item_string[2] = tostring(item_count) + table.insert(cell_items,table.concat(item_string," ")) + item_count = 0 + end end end if cell_idx == nil then @@ -173,18 +173,18 @@ local function take_all(pos,net) if name ~= "" then local its = minetest.deserialize(stack:get_meta():get_string("items")) for _,s in pairs(its) do - table.insert(items,s) + table.insert(items,s) end end - end + end for _,ostack in pairs(items) do --this returns 99 (max count) even if it removes more ctrl_inv:remove_item("main", ostack) - print(ostack) + --print(ostack) end - + net:update() - me.send_event(pos,"items") + me.send_event(pos, "items") end local function add_all(pos,net) @@ -198,23 +198,25 @@ local function add_all(pos,net) if name ~= "" then local its = minetest.deserialize(stack:get_meta():get_string("items")) if its then - for _,s in pairs(its) do - table.insert(items,s) - end + for _,s in pairs(its) do + table.insert(items,s) + end end end - end + end for _,ostack in pairs(items) do - me.insert_item(ostack, ctrl_inv, "main") - print(ostack) + local leftovers = me.insert_item(ostack, net, ctrl_inv, "main") + if not leftovers:is_empty() then + me.leftovers(pos, leftovers) + end end - + net:update() - me.send_event(pos,"items",{net = net}) + me.send_event(pos, "items", {net = net}) end function me.disconnect_drive(pos,ncpos) - microexpansion.log("disconnecting drive at "..minetest.pos_to_string(pos),"action") + me.log("disconnecting drive at "..minetest.pos_to_string(pos),"action") local fc,i = get_drive_controller(pos) if not fc.cpos then return @@ -229,7 +231,7 @@ function me.disconnect_drive(pos,ncpos) if fnet then take_all(pos,fnet) else - microexpansion.log("drive couldn't take items from its former network","warning") + me.log("drive couldn't take items from its former network","warning") end end @@ -241,29 +243,27 @@ local function update_drive(pos,_,ev) local cnet = ev.net or me.get_connected_network(pos) if cnet then if not fc then - microexpansion.log("connecting drive at "..minetest.pos_to_string(pos),"action") + me.log("connecting drive at "..minetest.pos_to_string(pos), "action") set_drive_controller(pos,true,cnet.controller_pos,i) add_all(pos,cnet) elseif not fc.cpos then - microexpansion.log("connecting drive at "..minetest.pos_to_string(pos),"action") + me.log("connecting drive at "..minetest.pos_to_string(pos), "action") set_drive_controller(pos,false,cnet.controller_pos,i) add_all(pos,cnet) elseif not vector.equals(fc.cpos,cnet.controller_pos) then - microexpansion.log("reconnecting drive at "..minetest.pos_to_string(pos),"action") + me.log("reconnecting drive at "..minetest.pos_to_string(pos), "action") write_drive_cells(pos,me.get_network(fc.cpos)) set_drive_controller(pos,false,cnet.controller_pos,i) add_all(pos,cnet) me.disconnect_drive(pos,cnet.controller_pos) else if ev.origin.name == "microexpansion:ctrl" then - me.disconnect_drive(pos,false) + me.disconnect_drive(pos,false) end end - else - if fc then - if fc.cpos then - me.disconnect_drive(pos,false) - end + elseif fc then + if fc.cpos then + me.disconnect_drive(pos,false) end end end @@ -280,9 +280,9 @@ if minetest.get_modpath("mcl_core") then else drive_recipe = { { 1, { - {"default:steel_ingot", "default:chest", "default:steel_ingot" }, - {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" }, - {"default:steel_ingot", "default:chest", "default:steel_ingot" }, + {"default:steel_ingot", "default:chest", "default:steel_ingot" }, + {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" }, + {"default:steel_ingot", "default:chest", "default:steel_ingot" }, }, } } @@ -309,21 +309,21 @@ microexpansion.register_node("drive", { on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_string("formspec", - "size[9,9.5]".. + "size[9,7.5]".. microexpansion.gui_bg .. microexpansion.gui_slots .. [[ label[0,-0.23;ME Drive] - list[context;main;0,0.3;8,4] - list[current_player;main;0,5.5;8,1;] - list[current_player;main;0,6.73;8,3;8] + list[context;main;0,0.3;5,2] + list[current_player;main;0,3.5;8,1;] + list[current_player;main;0,4.73;8,3;8] listring[current_name;main] listring[current_player;main] field_close_on_enter[filter;false] ]]) local inv = meta:get_inventory() inv:set_size("main", 10) - me.send_event(pos,"connect") + me.send_event(pos, "connect") end, can_dig = function(pos, player) if not player then @@ -346,14 +346,14 @@ microexpansion.register_node("drive", { return inv:is_empty("main") end, after_destruct = function(pos) - me.send_event(pos,"disconnect") + me.send_event(pos, "disconnect") end, allow_metadata_inventory_put = function(pos, _, _, stack, player) local name = player:get_player_name() local network = me.get_connected_network(pos) if network then if network:get_access_level(player) < access_level.interact then - return 0 + return 0 end elseif minetest.is_protected(pos, name) then minetest.record_protection_violation(pos, name) @@ -366,7 +366,7 @@ microexpansion.register_node("drive", { end end, on_metadata_inventory_put = function(pos, _, _, stack) - me.send_event(pos,"item_cap") + me.send_event(pos, "item_cap") local network = me.get_connected_network(pos) if network == nil then return @@ -375,14 +375,18 @@ microexpansion.register_node("drive", { local items = minetest.deserialize(stack:get_meta():get_string("items")) if items == nil then print("no items") - me.send_event(pos,"items",{net=network}) + me.send_event(pos, "items", {net=network}) return end - network:set_storage_space(#items) - for _,s in pairs(items) do - me.insert_item(s, ctrl_inv, "main") + -- network:set_storage_space(#items) + for _,stack in pairs(items) do + network:set_storage_space(true) + local leftovers = me.insert_item(stack, network, ctrl_inv, "main") + if not leftovers:is_empty() then + me.leftovers(pos, leftovers) + end end - me.send_event(pos,"items",{net=network}) + me.send_event(pos, "items", {net=network}) end, allow_metadata_inventory_take = function(pos,_,_,stack, player) --args: pos, listname, index, stack, player local name = player:get_player_name() @@ -390,7 +394,7 @@ microexpansion.register_node("drive", { if network then write_drive_cells(pos,network) if network:get_access_level(player) < access_level.interact then - return 0 + return 0 end elseif minetest.is_protected(pos, name) then minetest.record_protection_violation(pos, name) @@ -403,7 +407,7 @@ microexpansion.register_node("drive", { if network == nil then return end - me.send_event(pos,"item_cap",{net=network}) + me.send_event(pos, "item_cap", {net=network}) local ctrl_inv = network:get_inventory() local items = minetest.deserialize(stack:get_meta():get_string("items")) if items == nil then @@ -417,7 +421,7 @@ microexpansion.register_node("drive", { --print(stack:to_string()) network:update() - me.send_event(pos,"items",{net=network}) + me.send_event(pos, "items", {net=network}) end, }) diff --git a/modules/storage/init.lua b/modules/storage/init.lua index 96a9364..2bf89b9 100644 --- a/modules/storage/init.lua +++ b/modules/storage/init.lua @@ -13,3 +13,21 @@ dofile(module_path.."/storage.lua") -- Load machines dofile(module_path.."/drive.lua") dofile(module_path.."/terminal.lua") +dofile(module_path.."/cterminal.lua") +dofile(module_path.."/cmonitor.lua") +dofile(module_path.."/interface.lua") +dofile(module_path.."/remote.lua") + +local drawers_enabled = minetest.get_modpath("drawers") and true or false +if drawers_enabled then + dofile(module_path.."/drawer-api.lua") -- Extra Drawer api + dofile(module_path.."/drawer-interop.lua") +end +local technic_enabled = minetest.get_modpath("technic") and true or false +if technic_enabled then + dofile(module_path.."/technic-interop.lua") +end +local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false +if pipeworks_enabled then + dofile(module_path.."/pipeworks-interop.lua") +end diff --git a/modules/storage/interface.lua b/modules/storage/interface.lua new file mode 100644 index 0000000..f96d6f9 --- /dev/null +++ b/modules/storage/interface.lua @@ -0,0 +1,377 @@ +-- interface +-- microexpansion/interface.lua + +local me = microexpansion +local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false + +-- Interfaces work by walking all connected blocks. We walk machines and inventories. +-- This explains which nodes are connected. +-- Missing from technic_plus, the generators, might be nice to be able +-- to feed them wood when the power is low. +-- Odd machines like the reactor or the force field aren't supported. +-- We'd have to figure out what we'd want to do with them. +local function can_connect(name) + if me.registered_inventory[name] then + return true + end + return false +end + +function me.walk_connected(pos) + local nodes = {} + local visited = {} + visited[pos.x] = {} + visited[pos.x][pos.y] = {} + visited[pos.x][pos.y][pos.z] = true + local to_visit = {pos} + while #to_visit > 0 do + local pos = table.remove(to_visit) + local adjacent = { + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y, z=pos.z-1}, + } + for _,apos in pairs(adjacent) do + if not visited[apos.x] then + visited[apos.x] = {} + end + if not visited[apos.x][apos.y] then + visited[apos.x][apos.y] = {} + end + if visited[apos.x][apos.y][apos.z] ~= true then + visited[apos.x][apos.y][apos.z] = true + local napos = minetest.get_node(apos) + local nn = napos.name + if can_connect(nn) then + table.insert(nodes, {pos=apos, name=nn}) + table.insert(to_visit, apos) + end + end + end + end + + return nodes +end + +local function chest_formspec(pos, s) + if not s then s = { fill = "false" } end + local net = me.get_connected_network(pos) + local list + list = [[ + list[context;import;0,0.3;9,1] + list[context;export;0,0.3;9,1] + list[current_player;main;0,3.5;8,1;] + list[current_player;main;0,4.73;8,3;8] + checkbox[0,1.3;fill;fill;]]..s.fill..[[] + tooltip[fill;Allow inventory filling] + listring[current_name;import] + listring[current_player;main] + ]] + + if net and not net:powered() then + list = "label[3,2;" .. minetest.colorize("red", "No power!") .. "]" + end + + local formspec = + "size[9,7.5]".. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + "label[0,-0.23;ME Interface]" .. + list + return formspec +end + +local function update(pos,_,ev) + --me.log("INTERFACE: got event "..((ev and ev.type) or ""), "error") + if ev.type == "connect" then + -- net.update_counts() + elseif ev.type == "disconnect" then + -- + elseif ev.type == "power" then + local int_meta = minetest.get_meta(pos) + local fill = int_meta:get_string("fill") + int_meta:set_string("formspec", chest_formspec(pos, { fill=fill })) + end +end + +function me.reload_inventory(name, net, ctrl_inv, int_meta, n, pos, doinventories) + local func = me.registered_inventory and me.registered_inventory[name] + if func then + func(net, ctrl_inv, int_meta, n, pos, doinventories) + end +end + +function me.chest_reload(net, ctrl_inv, int_meta, n, pos, doinventories) + if true then return end -- no loans yet + local fill = int_meta:get_string("fill") == true + local meta = minetest.get_meta(n.pos) + local inv = meta:get_inventory() + if (fill and not doinventories) or (doinventories and fill) then + for i = 1, inv:get_size("main") do + local stack = inv:get_stack("main", i) + if stack:is_empty() then + stack = ItemStack("empty") + --me.log("EMPTY: creating empty loan", "error") + -- me.dbg() + net:create_loan(stack, {pos=n.pos, invname="main", slot=i, ipos=pos}, ctrl_inv, int_meta) + end + end + end + if not doinventories then return end + for i = 1, inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + net:create_loan(stack, {pos=n.pos, invname="main", slot=i, ipos=pos}, ctrl_inv, int_meta) + end + end +end + + +me.register_inventory("default:chest", me.chest_reload) +me.register_inventory("microexpansion:drive", me.chest_reload) + +-- This never rewalks connected machines. To do that add a gui +-- rewalk and/or remove, replace. +function me.reload_interface(net, pos, doinventories) + if not net then return end + local ctrl_inv = net:get_inventory() + local int_meta = minetest.get_meta(pos) + local inv = int_meta:get_inventory() + local inventories = minetest.deserialize(int_meta:get_string("connected")) + -- not appropriate + -- me.send_event(pos, "connect") + int_meta:set_string("infotext", "chests: "..#inventories) + if not net.counts then + net.counts = {} + end + if not net.autocrafters then + net.autocrafters = {} + end + if not net.autocrafters_by_pos then + net.autocrafters_by_pos = {} + end + if not net.process then + net.process = {} + end + for _, n in pairs(inventories) do + local node = minetest.get_node(n.pos) + local name = node.name + -- me.log("INT: found a "..name, "error") + local outputs = me.output_by_typename[me.block_to_typename_map[name]] + if outputs then + for _, name in pairs(outputs) do + if not net.process[name] then + net.process[name] = {} + end + -- me.log("INT: registering "..name.." for the "..node.name, "error") + net.process[name][n.pos] = pos + end + else + me.reload_inventory(name, net, ctrl_inv, int_meta, n, pos, doinventories) + end + end + -- me.send_event(pos,... +end + +-- [me chest] Register node +me.register_node("interface", { + description = "ME Interface", + usedfor = "Interface for ME system", + tiles = { + "interface", + "interface", + "chest_side", + "chest_side", + "chest_side", + "chest_side", -- TODO: Maybe customize it? + }, + recipe = { + { 1, { + {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" }, + {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot" }, + {"default:steel_ingot", "default:chest", "default:steel_ingot" }, + }, + } + }, + is_ground_content = false, + groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 }, + paramtype = "light", + paramtype2 = "facedir", + me_update = update, + on_construct = function(pos) + local int_meta = minetest.get_meta(pos) + int_meta:set_string("formspec", chest_formspec(pos)) + local inv = int_meta:get_inventory() + inv:set_size("export", 3) + inv:set_size("import", 3) + local inventories = me.walk_connected(pos) + int_meta:set_string("connected", minetest.serialize(inventories)) + + local net = me.get_connected_network(pos) + if net == nil then + int_meta:set_string("infotext", "No Network") + return + end + me.reload_interface(net, pos, true) + net:update_demand() + end, + can_dig = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:is_empty("import") + end, + on_destruct = function(pos) + local net = me.get_connected_network(pos) + if net == nil then return end + local inv = net:get_inventory() + + local int_meta = minetest.get_meta(pos) + local inventories = minetest.deserialize(int_meta:get_string("connected")) + int_meta:set_string("infotext", "") + + local to_remove = {} + for _, n in pairs(inventories) do + local pos = n.pos + if not to_remove[pos.x] then + to_remove[pos.x] = {} + end + if not to_remove[pos.x][pos.y] then + to_remove[pos.x][pos.y] = {} + end + if not to_remove[pos.x][pos.y][pos.z] then + to_remove[pos.x][pos.y][pos.z] = true + end + end + + net:update_counts() + local loan_slot = me.loan.get_size(net, inv) + local ref + while loan_slot > 0 do + local lstack = me.loan.get_stack(net, inv, loan_slot) + if lstack:is_empty() then + -- TODO: Don't think this can happen now, update_counts + -- me.log("interface empty loan at "..loan_slot, "error") + goto continue + foobar() + end + -- me.log("interface removing loan at "..loan_slot, "error") + ref = me.network.get_ref(lstack) + if ref and to_remove[ref.pos.x] and to_remove[ref.pos.x][ref.pos.y] and to_remove[ref.pos.x][ref.pos.y][ref.pos.z] then + net:remove_loan(ref.pos, inv, lstack, loan_slot, ref) + end + ::continue:: + loan_slot = loan_slot - 1 + end + net:update_counts() + + -- pos is a table and does not have value semantics. + if net.autocrafters_by_pos then + for k, v in pairs(net.autocrafters_by_pos) do + if k.x == pos.x and k.y == pos.y and k.z == pos.z then + pos = k + break + end + end + if net.autocrafters_by_pos[pos] then + for name, apos in pairs(net.autocrafters_by_pos[pos]) do + -- deindex these upon removal of the interface controlling them + net.autocrafters_by_pos[pos][name] = nil + net.autocrafters[name][apos] = nil + end + end + end + if net.process then + -- todo: This is a little slow, speed it up? Interface removal is infrequent. + for _, v in pairs(net.process) do + for k, ipos in pairs(v) do + if ipos.x == pos.x and ipos.y == pos.y and ipos.z == pos.z then + pos = ipos + break + end + end + end + for name, v in pairs(net.process) do + for apos, ipos in pairs(v) do + if ipos == pos then + --me.log("INTERFACE: killing a mchine for "..name, "error") + net.process[name][apos] = nil + end + end + end + end + net:update_demand() + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + allow_metadata_inventory_put = function(pos, listname, index, stack) + return stack:get_count() + end, + on_metadata_inventory_put = function(pos, _, _, stack) + local net = me.get_connected_network(pos) + if net == nil then + return + end + local ctrl_inv = net:get_inventory() + end, + allow_metadata_inventory_take = function(pos,_,_,stack) --args: pos, listname, index, stack, player + local net = me.get_connected_network(pos) + return stack:get_count() + end, + on_metadata_inventory_take = function(pos, _, _, stack) + local net = me.get_connected_network(pos) + if net == nil then + return + end + local ctrl_inv = net:get_inventory() + end, + -- tube connection + tube = { + can_insert = function(pos, _, stack) --pos, node, stack, direction + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + + local max_slots = inv:get_size("main") + local max_items = net.capacity_cache + + local slots, items = 0, 0 + -- Get amount of items in drive + for i = 1, max_slots do + local mstack = inv:get_stack("main", i) + if mstack:get_name() ~= "" then + slots = slots + 1 + local num = mstack:get_count() + if num == 0 then num = 1 end + items = items + num + end + end + items = items + stack:get_count() + return max_items > items + end, + insert_object = function(pos, _, stack) + local net = me.get_connected_network(pos) + local leftovers = stack + if net then + local inv = net:get_inventory() + leftovers = me.insert_item(stack, net, inv, "main") + net:set_storage_space(true) + end + return leftovers + end, + connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, + }, + after_place_node = pipeworks_enabled and pipeworks.after_place, + after_dig_node = pipeworks_enabled and pipeworks.after_dig, + on_receive_fields = function(pos, _, fields, sender) + local meta = minetest.get_meta(pos) + local fill = meta:get_string("fill") + if fields.fill then + fill = fields.fill + meta:set_string("fill", fields.fill) + end + meta:set_string("formspec", chest_formspec(pos, { fill=fill })) + end, +}) diff --git a/modules/storage/pipeworks-interop.lua b/modules/storage/pipeworks-interop.lua new file mode 100644 index 0000000..c982097 --- /dev/null +++ b/modules/storage/pipeworks-interop.lua @@ -0,0 +1,25 @@ +-- Interoperability file for pipework support. +local me = microexpansion + +me.register_inventory("pipeworks:autocrafter", function(net, ctrl_inv, int_meta, n, pos) + local meta = minetest.get_meta(n.pos) + local rinv = meta:get_inventory() + -- Autoinsert all the outputs + --for i = 1, rinv:get_size("dst") + -- local stack = rinv:get_stack("dst", i) + -- local leftovers = me.insert_item(stack, net, ctrl_inv, "main") + -- rinv:set_stack("dst", i, leftovers) + --end + -- register the crafted items so the autocrafter can use them + local craft = rinv:get_stack("output", 1) + if not craft:is_empty() then + if not net.autocrafters_by_pos[pos] then + net.autocrafters_by_pos[pos] = {} + end + net.autocrafters_by_pos[pos][craft:get_name()] = n.pos + if not net.autocrafters[craft:get_name()] then + net.autocrafters[craft:get_name()] = {} + end + net.autocrafters[craft:get_name()][n.pos] = pos + end +end) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua new file mode 100644 index 0000000..fc0a616 --- /dev/null +++ b/modules/storage/remote.lua @@ -0,0 +1,356 @@ +local me = microexpansion + +--technic = rawget(_G, "technic") or {} + +--local S = technic.getter + +local S = function(t) + return t +end + +local function get_metadata(toolstack) + local m = minetest.deserialize(toolstack:get_metadata()) + if not m then m = {} end + -- They can use it for just a little bit, then, they will have to charge it. + if not m.charge then m.charge = 3000 end + if not m.page then m.page = 1 end + if not m.query then m.query = "" end + if not m.crafts then m.crafts = "false" end + if not m.inv_name then m.inv_name = "main" end + if not m.query then m.query = "" end + return m +end + +local function chest_formspec(s, pos, start_id, listname) + local list + local page_number = "" + local buttons = "" + local net,cpos = me.get_connected_network(pos) + + if net then + local inv = net:get_inventory() + if listname and (inv:get_size(listname) > 0 or net:get_item_capacity() > 0) then + local ctrlinvname = net:get_inventory_name() + if listname == "main" then + list = "list[detached:"..ctrlinvname..";" + .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + else + list = "list[current_player;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + end + if minetest.get_modpath("i3") then + list = list .. [[ + list[current_player;main;0,8.5;9,4;] + ]] + else + list = list .. [[ + list[current_player;main;0,8.5;8,1;] + list[current_player;main;0,9.73;8,3;8] + ]] + end + list = list .. [[ + list[current_player;recipe;0.22,5.22;3,3;] + list[current_player;output;4,6.22;1,1;] + ]] + list = list .. [[ + listring[current_player;main] + listring[detached:]]..ctrlinvname..[[;main] + listring[current_player;main] + listring[current_player;recipe] + listring[current_player;main] + listring[current_player;output] + listring[current_player;main] + ]] + buttons = [[ + button[3.56,4.35;1.8,0.9;tochest;To Drive] + tooltip[tochest;Move everything from your inventory to the ME network.] + button[5.4,4.35;0.8,0.9;prev;<] + button[7.25,4.35;0.8,0.9;next;>] + tooltip[prev;Previous] + tooltip[next;Next] + field[0.29,4.6;2.2,1;filter;;]]..s.query..[[] + button[2.1,4.5;0.8,0.5;search;?] + button[2.75,4.5;0.8,0.5;clear;X] + tooltip[search;Search] + tooltip[clear;Reset] + field[6,5.42;2,1;autocraft;;1] + tooltip[autocraft;Number of items to Craft] + checkbox[6,6.45;crafts;crafts;]]..s.crafts..[[] + tooltip[crafts;Show only craftable items] + ]] + else + list = "label[3,2;" .. minetest.colorize("red", "No connected storage!") .. "]" + end + else + list = "label[3,2;" .. minetest.colorize("red", "No connected network!") .. "]" + end + if s.page_max then + page_number = "label[6.15,4.5;" .. math.floor((start_id / 32)) + 1 .. + "/" .. s.page_max .."]" + end + + return [[ + size[9,12.5] + ]].. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + list .. + [[ + label[0,-0.23;ME Remote Crafting Terminal] + label[5,-0.23;Charge level: ]]..s.charge..[[] + field_close_on_enter[filter;false] + field_close_on_enter[autocraft;false] + ]].. + page_number .. + buttons +end + +minetest.register_tool("microexpansion:remote", { + description = S("Microexpansion Remote"), + inventory_image = "technic_prospector.png", + wear_represents = "technic_RE_charge", + on_refill = technic and technic.refill_RE_charge, + on_use = function(toolstack, user, pointed_thing) + if not user or not user:is_player() or user.is_fake_player then return end + local toolmeta = get_metadata(toolstack) + if pointed_thing.type == "node" then + local pos = pointed_thing.under + pos.z = pos.z - 1 + local net,cpos = me.get_connected_network(pos) + if net then + if not net:powered(user:get_player_name()) then return end + minetest.chat_send_player(user:get_player_name(), "Connected to ME network, right-click to use.") + toolmeta.terminal = pos + local pinv = user:get_inventory() + pinv:set_size("recipe", 3*3) + pinv:set_size("output", 1) + toolstack:set_metadata(minetest.serialize(toolmeta)) + user:set_wielded_item(toolstack) + else + minetest.chat_send_player(user:get_player_name(), "Left-click on ME block to connect to ME network.") + return + end + end + end, + on_secondary_use = function(toolstack, user, pointed_thing) + if not user or not user:is_player() or user.is_fake_player then return end + local toolmeta = get_metadata(toolstack) + local pos = toolmeta.terminal + if not pos then + minetest.chat_send_player(user:get_player_name(), "Left-click on ME block to connect to ME network.") + return + end + local net,cpos = me.get_connected_network(pos) + + local charge_to_take = 100 + if net then + -- 25 to 1062 eu per operation, rich people pay for distance. + local distance = vector.distance(net.controller_pos, user:get_pos()) + charge_to_take = math.pow(math.log(distance),2) * 10 + end + + if toolmeta.charge < charge_to_take then + minetest.chat_send_player(user:get_player_name(), "No power left, recharge in technic battery.") + return + end + + if technic and not technic.creative_mode then + toolmeta.charge = toolmeta.charge - charge_to_take + toolstack:set_metadata(minetest.serialize(toolmeta)) + technic.set_RE_wear(toolstack, toolmeta.charge, technic.power_tools[toolstack:get_name()]) + end + + if net and not net:powered(user:get_player_name()) then return end + + local page = toolmeta.page + local inv_name = toolmeta.inv_name + local query = toolmeta.query + local crafts = toolmeta.crafts + + local inv + local own_inv = user:get_inventory() + local ctrl_inv + if net then + ctrl_inv = net:get_inventory() + end + if inv_name == "main" then + inv = ctrl_inv + else + inv = own_inv + end + if net then + toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 + toolstack:set_metadata(minetest.serialize(toolmeta)) + end + + minetest.show_formspec(user:get_player_name(), "microexpansion:remote_control", + chest_formspec(toolmeta, pos, page, inv_name)) + + return toolstack + end, +}) + +minetest.register_on_player_receive_fields(function(user, formname, fields) + if formname ~= "microexpansion:remote_control" then return false end + if not user or not user:is_player() or user.is_fake_player then return end + local toolstack = user:get_wielded_item() + if toolstack:get_name() ~= "microexpansion:remote" then return true end + + local toolmeta = get_metadata(toolstack) + + local pos = toolmeta.terminal + local net + if pos then + net = me.get_connected_network(pos) + end + + local page_max + local inv + local own_inv = user:get_inventory() + local ctrl_inv + if net then + ctrl_inv = net:get_inventory() + end + local inv_name = toolmeta.inv_name + if inv_name == "main" then + inv = ctrl_inv + else + inv = own_inv + end + + local page = toolmeta.page + local did_update = false + local update_search = false + local do_autocraft = false + for field, value in pairs(fields) do + --me.log("REMOTE: form "..field.." value "..value, "error") + if field == "next" then + if page + 32 <= inv:get_size(inv_name) then + page = page + 32 + toolmeta.page = page + did_update = true + end + elseif field == "prev" then + if page - 32 >= 1 then + page = page - 32 + toolmeta.page = page + did_update = true + end + elseif field == "crafts" then + toolmeta.crafts = value + page = 1 + toolmeta.page = page + update_search = true + elseif (field == "key_enter_field" and value == "filter") + or field == "filter" or field == "search" then + if field == "filter" then + toolmeta.query = value + end + if (field == "key_enter_field" and value == "filter") or field == "search" then + page = 1 + toolmeta.page = page + update_search = true + end + elseif field == "clear" then + own_inv:set_size("me_search", 0) + own_inv:set_size("me_crafts", 0) + page = 1 + toolmeta.page = page + toolmeta.inv_name = "main" + toolmeta.query = "" + toolmeta.crafts = "false" + toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 + update_search = true + did_update = true + elseif field == "tochest" then + elseif field == "autocraft" then + if tonumber(value) ~= nil then + toolmeta.autocraft = value + end + elseif field == "key_enter_field" and value == "autocraft" then + local count = tonumber(toolmeta.autocraft) + if not own_inv:get_stack("output", 1):is_empty() and count < math.pow(2,16) then + do_autocraft = true + end + end + end + + if do_autocraft then + local count = tonumber(toolmeta.autocraft) + me.autocraft(me.autocrafterCache, pos, net, own_inv, ctrl_inv, count) + end + + if update_search then + inv_name = "main" + if toolmeta.crafts == "true" then + inv_name = "me_crafts" + local tab = {} + if net then + if not net.process then + net:reload_network() + end + for name,pos in pairs(net.autocrafters) do + tab[#tab + 1] = ItemStack(name) + end + tab[#tab + 1] = ItemStack("") + for name,pos in pairs(net.process) do + tab[#tab + 1] = ItemStack(name) + end + end + own_inv:set_size(inv_name, #tab) + own_inv:set_list(inv_name, tab) + toolmeta.inv_name = inv_name + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + toolmeta.page_max = page_max + did_update = true + else + if toolmeta.query == "" then + own_inv:set_size("me_crafts", 0) + toolmeta.inv_name = inv_name + page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 + toolmeta.page_max = page_max + did_update = true + end + end + if toolmeta.query ~= "" then + inv = own_inv + if inv_name == "main" then + inv = ctrl_inv + end + local tab = {} + for i = 1, inv:get_size(inv_name) do + local match = inv:get_stack(inv_name, i):get_name():find(toolmeta.query) + if match then + tab[#tab + 1] = inv:get_stack(inv_name, i) + end + end + inv_name = "me_search" + own_inv:set_size(inv_name, #tab) + own_inv:set_list(inv_name, tab) + toolmeta.inv_name = inv_name + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + toolmeta.page_max = page_max + did_update = true + end + end + + if did_update then + minetest.show_formspec(user:get_player_name(), "microexpansion:remote_control", + chest_formspec(toolmeta, pos, page, inv_name)) + end + toolstack:set_metadata(minetest.serialize(toolmeta)) + user:set_wielded_item(toolstack) + return true +end) + +minetest.register_craft({ + output = "microexpansion:remote", + recipe = { + {"basic_materials:brass_ingot", "", "pipeworks:teleport_tube_1"}, + {"", (technic and "technic:control_logic_unit") or "", "basic_materials:brass_ingot"}, + {"", "", ""}, + } +}) + +if technic then + technic.register_power_tool("microexpansion:remote", 450000) +end diff --git a/modules/storage/technic-interop.lua b/modules/storage/technic-interop.lua new file mode 100644 index 0000000..5facf94 --- /dev/null +++ b/modules/storage/technic-interop.lua @@ -0,0 +1,283 @@ +-- Interoperability file for technic and technic_plus support. +local me = microexpansion + + +-- technic_plus doesn't export machine speed, so wire it in here. We +-- use this to know exactly how long a machine will take to process +-- anything, after that time, we know it is done and we can grab the +-- outputs, no polling. We do this for efficiency. + +-- The speeds of the various machines: +me.set_speed("technic:electric_furnace", 2) +me.set_speed("technic:mv_electric_furnace", 4) +me.set_speed("technic:hv_electric_furnace", 12) +me.set_speed("technic:lv_alloy_furnace", 1) +me.set_speed("technic:mv_alloy_furnace", 1.5) +me.set_speed("technic:lv_compressor", 1) +me.set_speed("technic:mv_compressor", 2) +me.set_speed("technic:hv_compressor", 5) +me.set_speed("technic:lv_extractor", 1) +me.set_speed("technic:mv_extractor", 2) +me.set_speed("technic:lv_grinder", 1) +me.set_speed("technic:mv_grinder", 2) +me.set_speed("technic:hv_grinder", 5) +me.set_speed("technic:mv_centrifuge", 2) +me.set_speed("technic:mv_freezer", 0.5) + +-- ======================================================================== -- + + +-- Register maximal output sizes for all the ingredients we produce. +-- We also break up deeply recursive crafts that would blow a pipeworks + +-- autocrafter if it tried to make something. +-- might not be necessary, but maybe it is. It might limit +-- oversupply of inputs to batteries. +me.register_max("technic:lv_battery_box0", 3) +me.register_max("technic:battery", 12) + +-- HV furnace "cooking" only has 4 output slots +me.register_max("technic:chromium_ingot", 99*4) +--me.register_max("technic:uranium_ingot", 99*4) +me.register_max("technic:zinc_ingot", 99*4) +me.register_max("technic:lead_ingot", 99*4) +me.register_max("technic:cast_iron_ingot", 99*4) + +-- HV grinder only has 4 output slots +me.register_max("technic:coal_dust", 99*4) +me.register_max("technic:copper_dust", 99*4) +me.register_max("technic:gold_dust", 99*4) +me.register_max("technic:wrought_iron_dust", 99*4) +me.register_max("technic:tin_dust", 99*4) +me.register_max("technic:chromium_dust", 99*4) +me.register_max("technic:uranium_dust", 99*4) +me.register_max("technic:zinc_dust", 99*4) +me.register_max("technic:lead_dust", 99*4) +me.register_max("technic:sulfur_dust", 99*4) +me.register_max("technic:stone_dust", 99*4) +me.register_max("default:gravel", 99*4) +me.register_max("default:sand", 99*4) +me.register_max("default:snowblock", 99*4) +me.register_max("technic:rubber_tree_grindings", 99*4) +me.register_max("technic:technic:silver_dust", 99*4) + +-- MV alloy furnace only has 4 output slots +me.register_max("technic:doped_silicon_wafer", 99*4) +me.register_max("technic:silicon_wafer", 99*4) +me.register_max("basic_materials:brass_ingot", 99*4) +me.register_max("default:bronze_ingot", 99*4) +me.register_max("technic:stainless_steel_ingot", 99*4) +me.register_max("technic:rubber", 99*4) +me.register_max("bucket:bucket_lava", 4) +me.register_max("technic:carbon_steel_ingot", 99*4) + +-- LV extractor only has 4 output slots +me.register_max("technic:raw_latex", 99*4) + +-- HV compressor only has 4 output slots +me.register_max("technic:composite_plate", 99*4) +me.register_max("technic:copper_plate", 99*4) +me.register_max("technic:graphite", 99*4) +me.register_max("technic:carbon_plate", 99*4) +me.register_max("technic:uranium_fuel", 99*4) +me.register_max("default:diamond", 99*4) + +-- freezer only has 4 output slots +me.register_max("default:ice", 99*4) + +-- ======================================================================== -- + + +-- The type of machines all the machines are: We have to list these +-- before me.register_inventory. +me.register_typename("technic:electric_furnace", "cooking") +me.register_typename("technic:mv_electric_furnace", "cooking") +me.register_typename("technic:hv_electric_furnace", "cooking") +me.register_typename("technic:lv_grinder", "grinding") +me.register_typename("technic:mv_grinder", "grinding") +me.register_typename("technic:hv_grinder", "grinding") +me.register_typename("technic:coal_alloy_furnace", "alloy") +me.register_typename("technic:lv_alloy_furnace", "alloy") +me.register_typename("technic:mv_alloy_furnace", "alloy") +me.register_typename("technic:lv_extractor", "extracting") +me.register_typename("technic:mv_extractor", "extracting") +me.register_typename("technic:lv_compressor", "compressing") +me.register_typename("technic:mv_compressor", "compressing") +me.register_typename("technic:hv_compressor", "compressing") +me.register_typename("technic:mv_centrifuge", "separating") +me.register_typename("technic:mv_freezer", "freezing") + +-- We need active nodes defined as well, as the recipe system doesn't otherwise have +-- recipes for them. +me.register_machine_alias("technic:electric_furnace_active", "technic:electric_furnace") +me.register_machine_alias("technic:mv_electric_furnace_active", "technic:mv_electric_furnace") +me.register_machine_alias("technic:hv_electric_furnace_active", "technic:hv_electric_furnace") +me.register_machine_alias("technic:lv_grinder_active", "technic:lv_grinder") +me.register_machine_alias("technic:mv_grinder_active", "technic:mv_grinder") +me.register_machine_alias("technic:hv_grinder_active", "technic:hv_grinder") +me.register_machine_alias("technic:coal_alloy_furnace_active", "technic:coal_alloy_furnace") +me.register_machine_alias("technic:lv_alloy_furnace_active", "technic:lv_alloy_furnace") +me.register_machine_alias("technic:mv_alloy_furnace_active", "technic:mv_alloy_furnace") +me.register_machine_alias("technic:lv_extractor_active", "technic:lv_extractor") +me.register_machine_alias("technic:mv_extractor_active", "technic:mv_extractor") +me.register_machine_alias("technic:lv_compressor_active", "technic:lv_compressor") +me.register_machine_alias("technic:mv_compressor_active", "technic:mv_compressor") +me.register_machine_alias("technic:hv_compressor_active", "technic:hv_compressor") +me.register_machine_alias("technic:mv_centrifuge_active", "technic:mv_centrifuge") +me.register_machine_alias("technic:mv_freezer_active", "technic:mv_freezer") + +-- ======================================================================== -- + + +-- The various blocks and how to interface to them: +me.register_inventory("technic:gold_chest", me.chest_reload) +me.register_inventory("technic:mithril_chest", me.chest_reload) + +me.register_inventory("technic:quarry", function(net, ctrl_inv, int_meta, n, pos) + local meta = minetest.get_meta(n.pos) + local rinv = meta:get_inventory() + for i = 1, rinv:get_size("cache") do + local stack = rinv:get_stack("cache", i) + if not stack:is_empty() then + local leftovers = me.insert_item(stack, net, ctrl_inv, "main") + rinv:set_stack("cache", i, leftovers) + end + end + -- we can set up a timer to recheck the cache every 30 seconds and + -- clean it out for example. +end) + +me.register_inventory("technic:mv_centrifuge", function(net, ctrl_inv, int_meta, n, pos) + local meta = minetest.get_meta(n.pos) + local rinv = meta:get_inventory() + for i = 1, rinv:get_size("dst") do + local stack = rinv:get_stack("dst", i) + if not stack:is_empty() then + local leftovers = me.insert_item(stack, net, ctrl_inv, "main") + rinv:set_stack("dst", i, leftovers) + end + end + for i = 1, rinv:get_size("dst2") do + local stack = rinv:get_stack("dst2", i) + if not stack:is_empty() then + local leftovers = me.insert_item(stack, net, ctrl_inv, "main") + rinv:set_stack("dst2", i, leftovers) + end + end +end) + +-- ======================================================================== -- + + +-- The various outputs the various machine types can generate: +me.register_output_by_typename("cooking", "default:steel_ingot") +me.register_output_by_typename("cooking", "technic:chromium_ingot") +--me.register_output_by_typename("cooking", "technic:uranium_ingot") +me.register_output_by_typename("cooking", "technic:zinc_ingot") +me.register_output_by_typename("cooking", "technic:lead_ingot") +me.register_output_by_typename("cooking", "technic:cast_iron_ingot") +me.register_output_by_typename("cooking", "moreores:silver_ingot") + +me.register_output_by_typename("grinding", "technic:coal_dust") +me.register_output_by_typename("grinding", "technic:copper_dust") +me.register_output_by_typename("grinding", "technic:gold_dust") +me.register_output_by_typename("grinding", "technic:wrought_iron_dust") +me.register_output_by_typename("grinding", "technic:tin_dust") +me.register_output_by_typename("grinding", "technic:chromium_dust") +--me.register_output_by_typename("grinding", "technic:uranium_dust") +me.register_output_by_typename("grinding", "technic:zinc_dust") +me.register_output_by_typename("grinding", "technic:lead_dust") +me.register_output_by_typename("grinding", "technic:sulfur_dust") +me.register_output_by_typename("grinding", "technic:stone_dust") +me.register_output_by_typename("grinding", "default:gravel") +me.register_output_by_typename("grinding", "default:sand") +me.register_output_by_typename("grinding", "default:snowblock") +me.register_output_by_typename("grinding", "technic:rubber_tree_grindings") +me.register_output_by_typename("grinding", "technic:silver_dust") + +me.register_output_by_typename("alloy", "technic:doped_silicon_wafer") +me.register_output_by_typename("alloy", "technic:silicon_wafer") +me.register_output_by_typename("alloy", "basic_materials:brass_ingot") +me.register_output_by_typename("alloy", "default:bronze_ingot") +me.register_output_by_typename("alloy", "technic:stainless_steel_ingot") +me.register_output_by_typename("alloy", "technic:rubber") +me.register_output_by_typename("alloy", "bucket:bucket_lava") +me.register_output_by_typename("alloy", "technic:carbon_steel_ingot") + +me.register_output_by_typename("extracting", "technic:raw_latex") + +me.register_output_by_typename("compressing", "technic:composite_plate") +me.register_output_by_typename("compressing", "technic:copper_plate") +me.register_output_by_typename("compressing", "technic:graphite") +me.register_output_by_typename("compressing", "technic:carbon_plate") +me.register_output_by_typename("compressing", "technic:uranium_fuel") +me.register_output_by_typename("compressing", "default:diamond") + +-- Any of these worth doing? TODO: Uranium, sure. +--me.register_output_by_typename("separating", "") + +me.register_output_by_typename("freezing", "default:ice") + +-- ======================================================================== -- + +-- The inputs required for the given output. The inputs are exact count, the output it just +-- for 1. We'll figure out how many are actually produced later. For multiple outputs +-- only list the more interesting one. +-- furnace ("cooking") +me.register_output_to_inputs("default:copper_ingot", { ItemStack("technic:copper_dust") }) +me.register_output_to_inputs("default:gold_ingot", { ItemStack("technic:gold_dust") }) +me.register_output_to_inputs("default:steel_ingot", { ItemStack("technic:wrought_iron_dust") }) +me.register_output_to_inputs("default:tin_ingot", { ItemStack("technic:tin_dust") }) +me.register_output_to_inputs("technic:chromium_ingot", { ItemStack("technic:chromium_dust") }) +--me.register_output_to_inputs("technic:uranium_ingot", { ItemStack("technic:uranium_dust") }) +me.register_output_to_inputs("technic:zinc_ingot", { ItemStack("technic:zinc_dust") }) +me.register_output_to_inputs("technic:lead_ingot", { ItemStack("technic:lead_dust") }) +me.register_output_to_inputs("technic:cast_iron_ingot", { ItemStack("default:steel_ingot") }) +me.register_output_to_inputs("mesecons_materials:glue", { ItemStack("technic:raw_latex") }) +me.register_output_to_inputs("moreores:silver_ingot", { ItemStack("technic:silver_dust") }) + +-- grinder ("grinding") +me.register_output_to_inputs("technic:coal_dust", { ItemStack("default:coal_lump") }) +me.register_output_to_inputs("technic:copper_dust", { ItemStack("default:copper_lump") }) +me.register_output_to_inputs("technic:gold_dust", { ItemStack("default:gold_lump") }) +me.register_output_to_inputs("technic:wrought_iron_dust", { ItemStack("default:iron_lump") }) +me.register_output_to_inputs("technic:tin_dust", { ItemStack("default:tin_lump") }) +me.register_output_to_inputs("technic:chromium_dust", { ItemStack("default:chromium_lump") }) +me.register_output_to_inputs("technic:uranium_dust", { ItemStack("default:uranium_lump") }) +me.register_output_to_inputs("technic:zinc_dust", { ItemStack("technic:zinc_lump") }) +me.register_output_to_inputs("technic:lead_dust", { ItemStack("technic:lead_lump") }) +me.register_output_to_inputs("technic:sulfur_dust", { ItemStack("technic:sulfur_lump") }) +me.register_output_to_inputs("technic:stone_dust", { ItemStack("default:stone") }) +me.register_output_to_inputs("default:gravel", { ItemStack("default:cobble") }) +me.register_output_to_inputs("default:sand", { ItemStack("default:gravel") }) +me.register_output_to_inputs("default:snowblock", { ItemStack("default:ice") }) +me.register_output_to_inputs("technic:rubber_tree_grindings", { ItemStack("moretrees:rubber_tree_trunk") }) +me.register_output_to_inputs("technic:silver_dust", { ItemStack("moreores:silver_lump") }) + +-- alloy_furnace ("alloy") +-- The most useful alloy recipes. We don't do the less useful ones as we don't yet have +-- a way for the user to say, no, don't do that. +me.register_output_to_inputs("technic:doped_silicon_wafer", { ItemStack("technic:gold_dust"), ItemStack("technic:silicon_wafer") }) +me.register_output_to_inputs("technic:silicon_wafer", { ItemStack("default:sand 2"), ItemStack("technic:coal_dust 2") }) +me.register_output_to_inputs("basic_materials:brass_ingot", { ItemStack("default:copper_ingot 2"), ItemStack("technic:zinc_ingot") }) +me.register_output_to_inputs("default:bronze_ingot", { ItemStack("default:copper_ingot 7"), ItemStack("default:tin_ingot") }) +me.register_output_to_inputs("technic:stainless_steel_ingot", { ItemStack("technic:carbon_steel_ingot 4"), ItemStack("technic:chromium_ingot") }) +me.register_output_to_inputs("technic:rubber", { ItemStack("technic:raw_latex 4"), ItemStack("technic:coal_dust 2") }) +me.register_output_to_inputs("bucket:bucket_lava", { ItemStack("default:obsidian"), ItemStack("bucket:bucket_empty") }) +me.register_output_to_inputs("technic:carbon_steel_ingot", { ItemStack("default:steel_ingot 2"), ItemStack("technic:coal_dust") }) + +-- extractor ("extracting") +me.register_output_to_inputs("technic:raw_latex", { ItemStack("technic:rubber_tree_grindings 4") }) + +-- compressor ("compressing") +me.register_output_to_inputs("technic:composite_plate", { ItemStack("technic:mixed_metal_ingot") }) +me.register_output_to_inputs("technic:copper_plate", { ItemStack("default:copper_ingot 5") }) +me.register_output_to_inputs("technic:graphite", { ItemStack("technic:coal_dust 4") }) +me.register_output_to_inputs("technic:carbon_plate", { ItemStack("technic:carbon_cloth") }) +me.register_output_to_inputs("technic:uranium_fuel", { ItemStack("technic:uranium35_ingot 5") }) +me.register_output_to_inputs("default:diamond", { ItemStack("technic:graphite 25") }) + +-- centrifuge ("separating") + +-- freezer ("freezing") +me.register_output_to_inputs("default:ice", { ItemStack("bucket:bucket_water") }) diff --git a/modules/storage/terminal.lua b/modules/storage/terminal.lua index 5d8d82f..db8ce0c 100644 --- a/modules/storage/terminal.lua +++ b/modules/storage/terminal.lua @@ -1,3 +1,4 @@ +-- terminal -- microexpansion/machines.lua local me = microexpansion @@ -21,24 +22,32 @@ local function chest_formspec(pos, start_id, listname, page_max, q) else list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" end + if minetest.get_modpath("i3") then + list = list .. [[ + list[current_player;main;0,5.5;9,4;] + ]] + else + list = list .. [[ + list[current_player;main;0,5.5;8,1;] + list[current_player;main;0,6.73;8,3;8] + ]] + end list = list .. [[ - list[current_player;main;0,5.5;8,1;] - list[current_player;main;0,6.73;8,3;8] - listring[detached:]]..ctrlinvname..[[;main] - listring[current_player;main] + listring[detached:]]..ctrlinvname..[[;main] + listring[current_player;main] ]] buttons = [[ - button[3.56,4.35;1.8,0.9;tochest;To Drive] - tooltip[tochest;Move everything from your inventory to the ME network.] - button[5.4,4.35;0.8,0.9;prev;<] - button[7.25,4.35;0.8,0.9;next;>] - tooltip[prev;Previous] - tooltip[next;Next] - field[0.29,4.6;2.2,1;filter;;]]..query..[[] - button[2.1,4.5;0.8,0.5;search;?] - button[2.75,4.5;0.8,0.5;clear;X] - tooltip[search;Search] - tooltip[clear;Reset] + button[3.56,4.35;1.8,0.9;tochest;To Drive] + tooltip[tochest;Move everything from your inventory to the ME network.] + button[5.4,4.35;0.8,0.9;prev;<] + button[7.25,4.35;0.8,0.9;next;>] + tooltip[prev;Previous] + tooltip[next;Next] + field[0.29,4.6;2.2,1;filter;;]]..query..[[] + button[2.1,4.5;0.8,0.5;search;?] + button[2.75,4.5;0.8,0.5;clear;X] + tooltip[search;Search] + tooltip[clear;Reset] ]] else list = "label[3,2;" .. minetest.colorize("red", "No connected drives!") .. "]" @@ -51,6 +60,12 @@ local function chest_formspec(pos, start_id, listname, page_max, q) "/" .. page_max .."]" end + if net and not net:powered() then + list = "label[3,2;" .. minetest.colorize("red", "No power!") .. "]" + buttons = "" + page_number = "" + end + return [[ size[9,9.5] ]].. @@ -86,9 +101,9 @@ local term_recipe = nil if minetest.get_modpath("mcl_core") then term_recipe = { { 1, { - {"mcl_core:iron_ingot", "mcl_chests:chest", "mcl_core:iron_ingot"}, - {"mcl_core:iron_ingot", "microexpansion:machine_casing", "mcl_core:iron_ingot"}, - {"mcl_core:iron_ingot", "microexpansion:cable", "mcl_core:iron_ingot"}, + {"mcl_core:iron_ingot", "mcl_chests:chest", "mcl_core:iron_ingot"}, + {"mcl_core:iron_ingot", "microexpansion:machine_casing", "mcl_core:iron_ingot"}, + {"mcl_core:iron_ingot", "microexpansion:cable", "mcl_core:iron_ingot"}, }, } } @@ -97,16 +112,16 @@ else term_recipe = { { 1, { - {"default:steel_ingot", "default:chest", "default:steel_ingot"}, - {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot"}, - {"default:steel_ingot", "microexpansion:cable", "default:steel_ingot"}, + {"default:steel_ingot", "default:chest", "default:steel_ingot"}, + {"default:steel_ingot", "microexpansion:machine_casing", "default:steel_ingot"}, + {"default:steel_ingot", "microexpansion:cable", "default:steel_ingot"}, }, } } end -- [me chest] Register node -microexpansion.register_node("term", { +me.register_node("term", { description = "ME Terminal", usedfor = "Can interact with storage cells in ME networks", tiles = { @@ -154,13 +169,13 @@ microexpansion.register_node("term", { return net:get_access_level(name) >= access_level.modify end, after_destruct = function(pos) - me.send_event(pos,"disconnect") + me.send_event(pos, "disconnect") end, allow_metadata_inventory_put = function(pos, _, _, stack, player) local network = me.get_connected_network(pos) if network then if network:get_access_level(player) < access_level.interact then - return 0 + return 0 end elseif minetest.is_protected(pos, player) then minetest.record_protection_violation(pos, player) @@ -171,13 +186,17 @@ microexpansion.register_node("term", { on_metadata_inventory_put = function(pos, listname, _, stack) local net = me.get_connected_network(pos) local inv = net:get_inventory() - me.insert_item(stack, inv, "main") + local leftovers = me.insert_item(stack, net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(leftovers) + end + net:set_storage_space(true) end, allow_metadata_inventory_take = function(pos,_,_,stack, player) local network = me.get_connected_network(pos) if network then if network:get_access_level(player) < access_level.interact then - return 0 + return 0 end elseif minetest.is_protected(pos, player) then minetest.record_protection_violation(pos, player) @@ -188,13 +207,13 @@ microexpansion.register_node("term", { on_metadata_inventory_take = function(pos, listname, _, stack) local net = me.get_connected_network(pos) local inv = net:get_inventory() - inv:remove_item("main", stack) + me.remove_item(net, inv, "main", stack) end, tube = { can_insert = function(pos, _, stack) --pos, node, stack, direction local net = me.get_connected_network(pos) if not net then - return false + return false end local inv = net:get_inventory() local max_slots = inv:get_size("main") @@ -203,13 +222,13 @@ microexpansion.register_node("term", { local slots, items = 0, 0 -- Get amount of items in drive for i = 1, max_slots do - local dstack = inv:get_stack("main", i) - if dstack:get_name() ~= "" then - slots = slots + 1 - local num = dstack:get_count() - if num == 0 then num = 1 end - items = items + num - end + local dstack = inv:get_stack("main", i) + if dstack:get_name() ~= "" then + slots = slots + 1 + local num = dstack:get_count() + if num == 0 then num = 1 end + items = items + num + end end items = items + stack:get_count() return max_items > items @@ -217,31 +236,30 @@ microexpansion.register_node("term", { insert_object = function(pos, _, stack) local net = me.get_connected_network(pos) if not net then - return stack + return stack end local inv = net:get_inventory() - me.insert_item(stack, inv, "main") + local leftovers = me.insert_item(stack, net, inv, "main") net:set_storage_space(true) - --TODO: leftover - return ItemStack() + return leftovers end, connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, }, after_place_node = pipeworks_enabled and pipeworks.after_place, after_dig_node = pipeworks_enabled and pipeworks.after_dig, - on_receive_fields = function(pos, _, fields, sender) - local net,cp = me.get_connected_network(pos) - if net then - if cp then - microexpansion.log("network and ctrl_pos","info") - else - microexpansion.log("network but no ctrl_pos","warning") + on_receive_fields = function(pos, _, fields, sender) + local net,cp = me.get_connected_network(pos) + if net then + if cp then + me.log("network and ctrl_pos","info") + else + me.log("network but no ctrl_pos","warning") end else if cp then - microexpansion.log("no network but ctrl_pos","warning") + me.log("no network but ctrl_pos","warning") else - microexpansion.log("no network and no ctrl_pos","info") + me.log("no network and no ctrl_pos","info") end end local meta = minetest.get_meta(pos) @@ -252,7 +270,7 @@ microexpansion.register_node("term", { if cp then ctrl_inv = net:get_inventory() else - microexpansion.log("no network connected","warning") + me.log("no network connected","warning") return end local inv @@ -269,34 +287,34 @@ microexpansion.register_node("term", { end if fields.next then if page + 32 > inv:get_size(inv_name) then - return + return end meta:set_int("page", page + 32) meta:set_string("formspec", chest_formspec(pos, page + 32, inv_name, page_max)) elseif fields.prev then if page - 32 < 1 then - return + return end meta:set_int("page", page - 32) meta:set_string("formspec", chest_formspec(pos, page - 32, inv_name, page_max)) elseif fields.search or fields.key_enter_field == "filter" then own_inv:set_size("search", 0) if fields.filter == "" then - meta:set_int("page", 1) - meta:set_string("inv_name", "main") - meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max)) + meta:set_int("page", 1) + meta:set_string("inv_name", "main") + meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max)) else - local tab = {} - for i = 1, ctrl_inv:get_size("main") do - local match = ctrl_inv:get_stack("main", i):get_name():find(fields.filter) - if match then - tab[#tab + 1] = ctrl_inv:get_stack("main", i) - end - end - own_inv:set_list("search", tab) - meta:set_int("page", 1) - meta:set_string("inv_name", "search") - meta:set_string("formspec", chest_formspec(pos, 1, "search", page_max, fields.filter)) + local tab = {} + for i = 1, ctrl_inv:get_size("main") do + local match = ctrl_inv:get_stack("main", i):get_name():find(fields.filter) + if match then + tab[#tab + 1] = ctrl_inv:get_stack("main", i) + end + end + own_inv:set_list("search", tab) + meta:set_int("page", 1) + meta:set_string("inv_name", "search") + meta:set_string("formspec", chest_formspec(pos, 1, "search", page_max, fields.filter)) end elseif fields.clear then own_inv:set_size("search", 0) @@ -305,18 +323,18 @@ microexpansion.register_node("term", { meta:set_string("formspec", chest_formspec(pos, 1, "main", page_max)) elseif fields.tochest then if net:get_access_level(sender) < access_level.interact then - return + return end local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()}) net:set_storage_space(pinv:get_size("main")) local space = net:get_item_capacity() local contents = ctrl_inv:get_list("main") or {} for _,s in pairs(contents) do - if not s:is_empty() then - space = space - s:get_count() - end + if not s:is_empty() then + space = space - s:get_count() + end end - microexpansion.move_inv({ inv=pinv, name="main" }, { inv=ctrl_inv, name="main",huge=true }, space) + me.move_inv(net, { inv=pinv, name="main" }, { inv=ctrl_inv, name="main", huge=true }, space) net:set_storage_space(true) end end, From fac587be33f386af4d79965cc7dd32043032798e Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Thu, 1 Feb 2024 09:40:03 -0800 Subject: [PATCH 02/36] Pull in a few more bits to fix importer and exporter. --- api.lua | 14 +++++++------- modules/item_transfer/exporter.lua | 2 +- modules/item_transfer/importer.lua | 2 +- modules/item_transfer/upgrades.lua | 3 ++- modules/storage/api.lua | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/api.lua b/api.lua index b446201..0772ffc 100644 --- a/api.lua +++ b/api.lua @@ -124,7 +124,7 @@ function microexpansion.register_node(itemstring, def) end end --- get a node, if nessecary load it +-- get a node, if necessary load it function microexpansion.get_node(pos) local node = minetest.get_node_or_nil(pos) if node then return node end @@ -143,7 +143,7 @@ function microexpansion.update_node(pos,event) end -- [function] Move items from inv to inv -function microexpansion.move_inv(inv1, inv2, max, filter) +function microexpansion.move_inv(net, inv1, inv2, max, filter) if max <= 0 then return end local finv, tinv = inv1.inv, inv2.inv local fname, tname = inv1.name, inv2.name @@ -161,18 +161,18 @@ function microexpansion.move_inv(inv1, inv2, max, filter) end if tinv and tinv:room_for_item(tname, v) and (not filter or not filter(v)) then if huge then - microexpansion.insert_item(v, tinv, tname) + microexpansion.insert_item(v, net, tinv, tname) finv:remove_item(fname, v) else --TODO: continue inserting from the same stack if it is bigger than max if v:get_count() > v:get_stack_max() then v = v:peek_item(v:get_stack_max()) end - local leftover = tinv:add_item(tname, v) + local leftovers = tinv:add_item(tname, v) finv:remove_item(fname, v) - if leftover and not(leftover:is_empty()) then - microexpansion.log("leftover items when transferring inventory","warning") - finv:add_item(fname, leftover) + if leftovers and not leftovers:is_empty() then + microexpansion.log("leftover items when transferring inventory", "warning") + finv:add_item(fname, leftovers) end end inserted = inserted + v:get_count() diff --git a/modules/item_transfer/exporter.lua b/modules/item_transfer/exporter.lua index 609887b..3cd7bf5 100644 --- a/modules/item_transfer/exporter.lua +++ b/modules/item_transfer/exporter.lua @@ -22,7 +22,7 @@ local function exporter_timer(pos, elapsed) return not own_inv:contains_item("filter",stack:peek_item()) end local max_count = math.pow(2, upgrades.bulk or 0) - microexpansion.move_inv({inv=net:get_inventory(),name="main",huge=true}, {inv=inv,name=list}, max_count, export_filter) + me.move_inv(net, {inv=net:get_inventory(), name="main", huge=true}, {inv=inv, name=list}, max_count, export_filter) --TODO: perhaps call allow_insert and on_insert callbacks end return true diff --git a/modules/item_transfer/importer.lua b/modules/item_transfer/importer.lua index a563fef..44ae837 100644 --- a/modules/item_transfer/importer.lua +++ b/modules/item_transfer/importer.lua @@ -30,7 +30,7 @@ function importer_timer(pos, elapsed) end return false end - microexpansion.move_inv({inv=inv,name=list}, {inv=net:get_inventory(),name="main",huge=true}, count, import_filter) + me.move_inv(net, {inv=inv, name=list}, {inv=net:get_inventory(), name="main", huge=true}, count, import_filter) net:set_storage_space(true) end return true diff --git a/modules/item_transfer/upgrades.lua b/modules/item_transfer/upgrades.lua index 8631ba3..51390a4 100644 --- a/modules/item_transfer/upgrades.lua +++ b/modules/item_transfer/upgrades.lua @@ -2,7 +2,8 @@ local me = microexpansion --- TODO: regulation upgrade (1: disable in formspec, mesecon control; 2: requests from the me network and perhaps digiline) +-- TODO: regulation upgrade (1: disable in formspec, mesecon control; 2: requests +-- from the me network and perhaps digiline) -- [register item] Upgrade Base me.register_item("upgrade_base", { diff --git a/modules/storage/api.lua b/modules/storage/api.lua index 0184e74..6cb7016 100644 --- a/modules/storage/api.lua +++ b/modules/storage/api.lua @@ -54,7 +54,7 @@ function microexpansion.int_to_pagenum(int) end --[[ [function] Move items from inv to inv -function microexpansion.move_inv(inv1, inv2, max) +function microexpansion.move_inv(net, inv1, inv2, max) if max <= 0 then return end local finv, tinv = inv1.inv, inv2.inv local fname, tname = inv1.name, inv2.name @@ -72,7 +72,7 @@ function microexpansion.move_inv(inv1, inv2, max) end if tinv and tinv:room_for_item(tname, v) then if huge then - microexpansion.insert_item(v, tinv, tname) + microexpansion.insert_item(net, v, tinv, tname) finv:remove_item(fname, v) else local leftover = tinv:add_item(tname, v) From 5bd55033ff44b124717807c7b65f535d254a6e15 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Thu, 1 Feb 2024 09:46:48 -0800 Subject: [PATCH 03/36] Allow importing cells, if empty. --- modules/item_transfer/importer.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/item_transfer/importer.lua b/modules/item_transfer/importer.lua index 44ae837..b5a5588 100644 --- a/modules/item_transfer/importer.lua +++ b/modules/item_transfer/importer.lua @@ -22,7 +22,9 @@ function importer_timer(pos, elapsed) end local import_filter = function(stack) local stack_name = stack:get_name() - if minetest.get_item_group(stack_name, "microexpansion_cell") > 0 then + if minetest.get_item_group(stack_name, "microexpansion_cell") > 0 and + stack:get_meta():get_string("items") ~= "" and + stack:get_meta():get_string("items") ~= "return {}" then return true end if upgrades.filter then From a9b1add21f99b38f5b5e878b9ee0a6f33524b449 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Fri, 2 Feb 2024 09:02:57 -0800 Subject: [PATCH 04/36] Improve imported/exporter UI. --- modules/item_transfer/api.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/modules/item_transfer/api.lua b/modules/item_transfer/api.lua index 2259c1a..151e092 100644 --- a/modules/item_transfer/api.lua +++ b/modules/item_transfer/api.lua @@ -106,22 +106,29 @@ function item_transfer.setup_io_device(title, pos, metadata, inventory) formspec = formspec .. [[ label[0.5,0.75;filter] list[context;filter;0.5,1;5,3] + listring[current_name;upgrades] + listring[current_player;main] + listring[current_name;filter] + listring[current_player;main] ]] else inv:set_size("filter",0) end --TODO: target inventory dropdown inv:set_size("upgrades", 4) - meta:set_string("formspec", - formspec .. - [[ - label[8.5,2.5;upgrades] - list[context;upgrades;8,2.75;2,2] - list[current_player;main;0.5,5.5;8,1;] - list[current_player;main;0.5,7;8,3;8] + formspec = formspec .. [[ + label[8.5,2.5;upgrades] + list[context;upgrades;8,2.75;2,2] + list[current_player;main;0.5,5.5;8,1;] + list[current_player;main;0.5,7;8,3;8] + ]] + if not upgrades.filter then + formspec = formspec .. [[ listring[current_name;upgrades] listring[current_player;main] - ]]) + ]] + end + meta:set_string("formspec", formspec) end local access_level = microexpansion.constants.security.access_levels From a3f9a4000f560dd1ab3eb54e30258e3d4e40dea1 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 3 Feb 2024 10:13:05 -0800 Subject: [PATCH 05/36] When interfaces lose network connectivity, we have to reject input. --- modules/storage/interface.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/storage/interface.lua b/modules/storage/interface.lua index f96d6f9..c51104b 100644 --- a/modules/storage/interface.lua +++ b/modules/storage/interface.lua @@ -332,6 +332,7 @@ me.register_node("interface", { tube = { can_insert = function(pos, _, stack) --pos, node, stack, direction local net = me.get_connected_network(pos) + if not net then return false end local inv = net:get_inventory() local max_slots = inv:get_size("main") From 0a24904fae1ca6dd997883886e39b953d95bc0db Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 3 Feb 2024 15:40:00 -0800 Subject: [PATCH 06/36] Add quantum bridges (links and rings). Add matter condenser. --- README.md | 4 + modules.conf | 1 + modules/network/debugger.lua | 680 +++++++++++++++++++ modules/network/init.lua | 5 + modules/network/network.lua | 30 +- modules/quantum/init.lua | 19 + modules/quantum/matter_condenser.lua | 185 +++++ modules/quantum/quantum_link.lua | 202 ++++++ modules/quantum/quantum_ring.lua | 68 ++ textures/microexpansion_matter_condenser.png | Bin 0 -> 3612 bytes textures/microexpansion_quantum_link.png | Bin 0 -> 3741 bytes textures/microexpansion_quantum_ring.png | Bin 0 -> 4084 bytes textures/microexpansion_singularity.png | Bin 0 -> 749 bytes 13 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 modules/network/debugger.lua create mode 100644 modules/quantum/init.lua create mode 100644 modules/quantum/matter_condenser.lua create mode 100644 modules/quantum/quantum_link.lua create mode 100644 modules/quantum/quantum_ring.lua create mode 100644 textures/microexpansion_matter_condenser.png create mode 100644 textures/microexpansion_quantum_link.png create mode 100644 textures/microexpansion_quantum_ring.png create mode 100644 textures/microexpansion_singularity.png diff --git a/README.md b/README.md index 69fd6db..ad51040 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ MicroExpansion - ME [microexpansion] * **Licence:** Code: MIT (see LICENSE), Media: CC-BY-SA 3.0 +Quantum Textures, from https://github.com/AppliedEnergistics/Applied-Energistics-2.git +(c) 2020, [Ridanisaurus Rid](https://github.com/Ridanisaurus/), (c) 2013 - 2020 AlgorithmX2 et al +CC-BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/) + When you really get into a survival world, you typically end up with a lot of items, like a ton of items. Sometimes literally. But with that huge amount of resources, comes something annoying and typically unwanted: chests. diff --git a/modules.conf b/modules.conf index 5374f54..77457ee 100644 --- a/modules.conf +++ b/modules.conf @@ -1,4 +1,5 @@ network = true +quantum = true power = false storage = true ores = true diff --git a/modules/network/debugger.lua b/modules/network/debugger.lua new file mode 100644 index 0000000..b82c500 --- /dev/null +++ b/modules/network/debugger.lua @@ -0,0 +1,680 @@ +--[[ + Copyright (c) 2023 Scott Lembcke and Howling Moon Software + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + TODO: + * Print short function arguments as part of stack location. + * Properly handle being reentrant due to coroutines. +]] + +local dbg + +-- Use ANSI color codes in the prompt by default. +local COLOR_GRAY = "" +local COLOR_RED = "" +local COLOR_BLUE = "" +local COLOR_YELLOW = "" +local COLOR_RESET = "" +local GREEN_CARET = " => " + +local function pretty(obj, max_depth) + if max_depth == nil then max_depth = dbg.pretty_depth end + + -- Returns true if a table has a __tostring metamethod. + local function coerceable(tbl) + local meta = getmetatable(tbl) + return (meta and meta.__tostring) + end + + local function recurse(obj, depth) + if type(obj) == "string" then + -- Dump the string so that escape sequences are printed. + return string.format("%q", obj) + elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then + local str = "{" + + for k, v in pairs(obj) do + local pair = pretty(k, 0).." = "..recurse(v, depth + 1) + str = str..(str == "{" and pair or ", "..pair) + end + + return str.."}" + else + -- tostring() can fail if there is an error in a __tostring metamethod. + local success, value = pcall(function() return tostring(obj) end) + return (success and value or "") + end + end + + return recurse(obj, 0) +end + +-- The stack level that cmd_* functions use to access locals or info +-- The structure of the code very carefully ensures this. +local CMD_STACK_LEVEL = 6 + +-- Location of the top of the stack outside of the debugger. +-- Adjusted by some debugger entrypoints. +local stack_top = 0 + +-- The current stack frame index. +-- Changed using the up/down commands +local stack_inspect_offset = 0 + +-- LuaJIT has an off by one bug when setting local variables. +local LUA_JIT_SETLOCAL_WORKAROUND = 0 + +-- Default dbg.read function +local function dbg_read(prompt) + dbg.write(prompt) + io.flush() + return io.read() +end + +-- Default dbg.write function +local function dbg_write(str) + io.write(str) +end + +local function dbg_writeln(str, ...) + if select("#", ...) == 0 then + dbg.write((str or "").."\n") + else + dbg.write(string.format(str.."\n", ...)) + end +end + +local function format_loc(file, line) return COLOR_BLUE..file..COLOR_RESET..":"..COLOR_YELLOW..line..COLOR_RESET end +local function format_stack_frame_info(info) + local filename = info.source:match("@(.*)") + local source = filename and dbg.shorten_path(filename) or info.short_src + local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat) + local name = (info.name and "'"..COLOR_BLUE..info.name..COLOR_RESET.."'" or format_loc(source, info.linedefined)) + return format_loc(source, info.currentline).." in "..namewhat.." "..name +end + +local repl + +-- Return false for stack frames without source, +-- which includes C frames, Lua bytecode, and `loadstring` functions +local function frame_has_line(info) return info.currentline >= 0 end + +local function hook_factory(repl_threshold) + return function(offset, reason) + return function(event, _) + -- Skip events that don't have line information. + if not frame_has_line(debug.getinfo(2)) then return end + + -- Tail calls are specifically ignored since they also will have tail returns to balance out. + if event == "call" then + offset = offset + 1 + elseif event == "return" and offset > repl_threshold then + offset = offset - 1 + elseif event == "line" and offset <= repl_threshold then + repl(reason) + end + end + end +end + +local hook_step = hook_factory(1) +local hook_next = hook_factory(0) +local hook_finish = hook_factory(-1) + +-- Create a table of all the locally accessible variables. +-- Globals are not included when running the locals command, but are when running the print command. +local function local_bindings(offset, include_globals) + local level = offset + stack_inspect_offset + CMD_STACK_LEVEL + local func = debug.getinfo(level).func + local bindings = {} + + -- Retrieve the upvalues + do local i = 1; while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + bindings[name] = value + i = i + 1 + end end + + -- Retrieve the locals (overwriting any upvalues) + do local i = 1; while true do + local name, value = debug.getlocal(level, i) + if not name then break end + bindings[name] = value + i = i + 1 + end end + + -- Retrieve the varargs (works in Lua 5.2 and LuaJIT) + local varargs = {} + do local i = 1; while true do + local name, value = debug.getlocal(level, -i) + if not name then break end + varargs[i] = value + i = i + 1 + end end + if #varargs > 0 then bindings["..."] = varargs end + + if include_globals then + -- In Lua 5.2, you have to get the environment table from the function's locals. + local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV) + return setmetatable(bindings, {__index = env or _G}) + else + return bindings + end +end + +-- Used as a __newindex metamethod to modify variables in cmd_eval(). +local function mutate_bindings(_, name, value) + local FUNC_STACK_OFFSET = 3 -- Stack depth of this function. + local level = stack_inspect_offset + FUNC_STACK_OFFSET + CMD_STACK_LEVEL + + -- Set a local. + do local i = 1; repeat + local var = debug.getlocal(level, i) + if name == var then + dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set local variable "..COLOR_BLUE..name..COLOR_RESET) + return debug.setlocal(level + LUA_JIT_SETLOCAL_WORKAROUND, i, value) + end + i = i + 1 + until var == nil end + + -- Set an upvalue. + local func = debug.getinfo(level).func + do local i = 1; repeat + local var = debug.getupvalue(func, i) + if name == var then + dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set upvalue "..COLOR_BLUE..name..COLOR_RESET) + return debug.setupvalue(func, i, value) + end + i = i + 1 + until var == nil end + + -- Set a global. + dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set global variable "..COLOR_BLUE..name..COLOR_RESET) + _G[name] = value +end + +-- Compile an expression with the given variable bindings. +local function compile_chunk(block, env) + local source = "debugger.lua REPL" + local chunk = nil + + if _VERSION <= "Lua 5.1" then + chunk = loadstring(block, source) + if chunk then setfenv(chunk, env) end + else + -- The Lua 5.2 way is a bit cleaner + chunk = load(block, source, "t", env) + end + + if not chunk then dbg_writeln(COLOR_RED.."Error: Could not compile block:\n"..COLOR_RESET..block) end + return chunk +end + +local SOURCE_CACHE = {} + +local function where(info, context_lines) + local source = SOURCE_CACHE[info.source] + if not source then + source = {} + local filename = info.source:match("@(.*)") + if filename then + pcall(function() for line in io.lines(filename) do table.insert(source, line) end end) + elseif info.source then + for line in info.source:gmatch("(.-)\n") do table.insert(source, line) end + end + SOURCE_CACHE[info.source] = source + end + + if source and source[info.currentline] then + for i = info.currentline - context_lines, info.currentline + context_lines do + local tab_or_caret = (i == info.currentline and GREEN_CARET or " ") + local line = source[i] + if line then dbg_writeln(COLOR_GRAY.."% 4d"..tab_or_caret.."%s", i, line) end + end + else + dbg_writeln(COLOR_RED.."Error: Source not available for "..COLOR_BLUE..info.short_src); + end + + return false +end + +-- Wee version differences +local unpack = unpack or table.unpack +local pack = function(...) return {n = select("#", ...), ...} end + +local function cmd_step() + stack_inspect_offset = stack_top + return true, hook_step +end + +local function cmd_next() + stack_inspect_offset = stack_top + return true, hook_next +end + +local function cmd_finish() + local offset = stack_top - stack_inspect_offset + stack_inspect_offset = stack_top + return true, offset < 0 and hook_factory(offset - 1) or hook_finish +end + +local function cmd_print(expr) + local env = local_bindings(1, true) + local chunk = compile_chunk("return "..expr, env) + if chunk == nil then return false end + + -- Call the chunk and collect the results. + local results = pack(pcall(chunk, unpack(rawget(env, "...") or {}))) + + -- The first result is the pcall error. + if not results[1] then + dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." "..results[2]) + else + local output = "" + for i = 2, results.n do + output = output..(i ~= 2 and ", " or "")..dbg.pretty(results[i]) + end + + if output == "" then output = "" end + dbg_writeln(COLOR_BLUE..expr.. GREEN_CARET..output) + end + + return false +end + +local function cmd_eval(code) + local env = local_bindings(1, true) + local mutable_env = setmetatable({}, { + __index = env, + __newindex = mutate_bindings, + }) + + local chunk = compile_chunk(code, mutable_env) + if chunk == nil then return false end + + -- Call the chunk and collect the results. + local success, err = pcall(chunk, unpack(rawget(env, "...") or {})) + if not success then + dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." "..tostring(err)) + end + + return false +end + +local function cmd_down() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset + 1 + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until not info or frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: "..format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln("Already at the bottom of the stack.") + end + + return false +end + +local function cmd_up() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset - 1 + if offset < stack_top then info = nil; break end + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: "..format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln("Already at the top of the stack.") + end + + return false +end + +local function cmd_where(context_lines) + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + return (info and where(info, tonumber(context_lines) or 5)) +end + +local function cmd_trace() + dbg_writeln("Inspecting frame %d", stack_inspect_offset - stack_top) + local i = 0; while true do + local info = debug.getinfo(stack_top + CMD_STACK_LEVEL + i) + if not info then break end + + local is_current_frame = (i + stack_top == stack_inspect_offset) + local tab_or_caret = (is_current_frame and GREEN_CARET or " ") + dbg_writeln(COLOR_GRAY.."% 4d"..COLOR_RESET..tab_or_caret.."%s", i, format_stack_frame_info(info)) + i = i + 1 + end + + return false +end + +local function cmd_locals() + local bindings = local_bindings(1, false) + + -- Get all the variable binding names and sort them + local keys = {} + for k, _ in pairs(bindings) do table.insert(keys, k) end + table.sort(keys) + + for _, k in ipairs(keys) do + local v = bindings[k] + + -- Skip the debugger object itself, "(*internal)" values, and Lua 5.2's _ENV object. + if not rawequal(v, dbg) and k ~= "_ENV" and not k:match("%(.*%)") then + dbg_writeln(" "..COLOR_BLUE..k.. GREEN_CARET..dbg.pretty(v)) + end + end + + return false +end + +local function cmd_help() + dbg.write("" + ..COLOR_BLUE.." "..GREEN_CARET.."re-run last command\n" + ..COLOR_BLUE.." c"..COLOR_YELLOW.."(ontinue)"..GREEN_CARET.."continue execution\n" + ..COLOR_BLUE.." s"..COLOR_YELLOW.."(tep)"..GREEN_CARET.."step forward by one line (into functions)\n" + ..COLOR_BLUE.." n"..COLOR_YELLOW.."(ext)"..GREEN_CARET.."step forward by one line (skipping over functions)\n" + ..COLOR_BLUE.." f"..COLOR_YELLOW.."(inish)"..GREEN_CARET.."step forward until exiting the current function\n" + ..COLOR_BLUE.." u"..COLOR_YELLOW.."(p)"..GREEN_CARET.."move up the stack by one frame\n" + ..COLOR_BLUE.." d"..COLOR_YELLOW.."(own)"..GREEN_CARET.."move down the stack by one frame\n" + ..COLOR_BLUE.." w"..COLOR_YELLOW.."(here) "..COLOR_BLUE.."[line count]"..GREEN_CARET.."print source code around the current line\n" + ..COLOR_BLUE.." e"..COLOR_YELLOW.."(val) "..COLOR_BLUE.."[statement]"..GREEN_CARET.."execute the statement\n" + ..COLOR_BLUE.." p"..COLOR_YELLOW.."(rint) "..COLOR_BLUE.."[expression]"..GREEN_CARET.."execute the expression and print the result\n" + ..COLOR_BLUE.." t"..COLOR_YELLOW.."(race)"..GREEN_CARET.."print the stack trace\n" + ..COLOR_BLUE.." l"..COLOR_YELLOW.."(ocals)"..GREEN_CARET.."print the function arguments, locals and upvalues.\n" + ..COLOR_BLUE.." h"..COLOR_YELLOW.."(elp)"..GREEN_CARET.."print this message\n" + ..COLOR_BLUE.." q"..COLOR_YELLOW.."(uit)"..GREEN_CARET.."halt execution\n" + ) + return false +end + +local last_cmd = false + +local commands = { + ["^c$"] = function() return true end, + ["^s$"] = cmd_step, + ["^n$"] = cmd_next, + ["^f$"] = cmd_finish, + ["^p%s+(.*)$"] = cmd_print, + ["^e%s+(.*)$"] = cmd_eval, + ["^u$"] = cmd_up, + ["^d$"] = cmd_down, + ["^w%s*(%d*)$"] = cmd_where, + ["^t$"] = cmd_trace, + ["^l$"] = cmd_locals, + ["^h$"] = cmd_help, + ["^q$"] = function() dbg.exit(0); return true end, +} + +local function match_command(line) + for pat, func in pairs(commands) do + -- Return the matching command and capture argument. + if line:find(pat) then return func, line:match(pat) end + end +end + +-- Run a command line +-- Returns true if the REPL should exit and the hook function factory +local function run_command(line) + -- GDB/LLDB exit on ctrl-d + if line == nil then dbg.exit(1); return true end + + -- Re-execute the last command if you press return. + if line == "" then line = last_cmd or "h" end + + local command, command_arg = match_command(line) + if command then + last_cmd = line + -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable. + return unpack({command(command_arg)}) + elseif dbg.auto_eval then + return unpack({cmd_eval(line)}) + else + dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." command '%s' not recognized.\nType 'h' and press return for a command list.", line) + return false + end +end + +repl = function(reason) + -- Skip frames without source info. + while not frame_has_line(debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)) do + stack_inspect_offset = stack_inspect_offset + 1 + end + + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3) + reason = reason and (COLOR_YELLOW.."break via "..COLOR_RED..reason..GREEN_CARET) or "" + dbg_writeln(reason..format_stack_frame_info(info)) + + if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end + + repeat + local success, done, hook = pcall(run_command, dbg.read(COLOR_RED.."debugger.lua> "..COLOR_RESET)) + if success then + debug.sethook(hook and hook(0), "crl") + else + local message = COLOR_RED.."INTERNAL DEBUGGER.LUA ERROR. ABORTING\n:"..COLOR_RESET.." "..done + dbg_writeln(message) + error(message) + end + until done +end + +-- Make the debugger object callable like a function. +dbg = setmetatable({}, { + __call = function(_, condition, top_offset, source) + if condition then return end + + top_offset = (top_offset or 0) + stack_inspect_offset = top_offset + stack_top = top_offset + + debug.sethook(hook_next(1, source or "dbg()"), "crl") + return + end, +}) + +-- Expose the debugger's IO functions. +dbg.read = dbg_read +dbg.write = dbg_write +dbg.shorten_path = function (path) return path end +dbg.exit = function(err) os.exit(err) end + +dbg.writeln = dbg_writeln + +dbg.pretty_depth = 3 +dbg.pretty = pretty +dbg.pp = function(value, depth) dbg_writeln(dbg.pretty(value, depth)) end + +dbg.auto_where = false +dbg.auto_eval = false + +local lua_error, lua_assert = error, assert + +-- Works like error(), but invokes the debugger. +function dbg.error(err, level) + level = level or 1 + dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..dbg.pretty(err)) + dbg(false, level, "dbg.error()") + + lua_error(err, level) +end + +-- Works like assert(), but invokes the debugger on a failure. +function dbg.assert(condition, message) + if not condition then + dbg_writeln(COLOR_RED.."ERROR:"..COLOR_RESET..message) + dbg(false, 1, "dbg.assert()") + end + + return lua_assert(condition, message) +end + +-- Works like pcall(), but invokes the debugger on an error. +function dbg.call(f, ...) + return xpcall(f, function(err) + dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..dbg.pretty(err)) + dbg(false, 1, "dbg.call()") + + return err + end, ...) +end + +-- Error message handler that can be used with lua_pcall(). +function dbg.msgh(...) + if debug.getinfo(2) then + dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..dbg.pretty(...)) + dbg(false, 1, "dbg.msgh()") + else + dbg_writeln(COLOR_RED.."debugger.lua: "..COLOR_RESET.."Error did not occur in Lua code. Execution will continue after dbg_pcall().") + end + + return ... +end + +-- Assume stdin/out are TTYs unless we can use LuaJIT's FFI to properly check them. +local stdin_isatty = true +local stdout_isatty = true + +-- Conditionally enable the LuaJIT FFI. +local ffi = (jit and require("ffi")) +if ffi then + ffi.cdef[[ + int isatty(int); // Unix + int _isatty(int); // Windows + void free(void *ptr); + + char *readline(const char *); + int add_history(const char *); + ]] + + local function get_func_or_nil(sym) + local success, func = pcall(function() return ffi.C[sym] end) + return success and func or nil + end + + local isatty = get_func_or_nil("isatty") or get_func_or_nil("_isatty") or (ffi.load("ucrtbase"))["_isatty"] + stdin_isatty = isatty(0) + stdout_isatty = isatty(1) +end + +-- Conditionally enable color support. +local color_maybe_supported = (stdout_isatty and os.getenv("TERM") and os.getenv("TERM") ~= "dumb") +if color_maybe_supported and not os.getenv("DBG_NOCOLOR") then + COLOR_GRAY = string.char(27) .. "[90m" + COLOR_RED = string.char(27) .. "[91m" + COLOR_BLUE = string.char(27) .. "[94m" + COLOR_YELLOW = string.char(27) .. "[33m" + COLOR_RESET = string.char(27) .. "[0m" + GREEN_CARET = string.char(27) .. "[92m => "..COLOR_RESET +end + +if stdin_isatty and not os.getenv("DBG_NOREADLINE") then + pcall(function() + local linenoise = require 'linenoise' + + -- Load command history from ~/.lua_history + local hist_path = os.getenv('HOME') .. '/.lua_history' + linenoise.historyload(hist_path) + linenoise.historysetmaxlen(50) + + local function autocomplete(env, input, matches) + for name, _ in pairs(env) do + if name:match('^' .. input .. '.*') then + linenoise.addcompletion(matches, name) + end + end + end + + -- Auto-completion for locals and globals + linenoise.setcompletion(function(matches, input) + -- First, check the locals and upvalues. + local env = local_bindings(1, true) + autocomplete(env, input, matches) + + -- Then, check the implicit environment. + env = getmetatable(env).__index + autocomplete(env, input, matches) + end) + + dbg.read = function(prompt) + local str = linenoise.linenoise(prompt) + if str and not str:match "^%s*$" then + linenoise.historyadd(str) + linenoise.historysave(hist_path) + end + return str + end + dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Linenoise support enabled.") + end) + + -- Conditionally enable LuaJIT readline support. + pcall(function() + if dbg.read == nil and ffi then + local readline = ffi.load("readline") + dbg.read = function(prompt) + local cstr = readline.readline(prompt) + if cstr ~= nil then + local str = ffi.string(cstr) + if string.match(str, "[^%s]+") then + readline.add_history(cstr) + end + + ffi.C.free(cstr) + return str + else + return nil + end + end + dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Readline support enabled.") + end + end) +end + +-- Detect Lua version. +if jit then -- LuaJIT + LUA_JIT_SETLOCAL_WORKAROUND = -1 + dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Loaded for "..jit.version) +elseif "Lua 5.1" <= _VERSION and _VERSION <= "Lua 5.4" then + dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Loaded for ".._VERSION) +else + dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Not tested against ".._VERSION) + dbg_writeln("Please send me feedback!") +end + +microexpansion.dbg = dbg + +return dbg diff --git a/modules/network/init.lua b/modules/network/init.lua index abcdcd0..5b0488a 100644 --- a/modules/network/init.lua +++ b/modules/network/init.lua @@ -262,3 +262,8 @@ end -- save on server shutdown minetest.register_on_shutdown(me.save) + +if false then + -- This only works if one enables unsafe mods. The debugger uses the unsafe api. + dofile(path.."/debugger.lua") -- Debugger a la python pdb, see https://github.com/slembcke/debugger.lua +end diff --git a/modules/network/network.lua b/modules/network/network.lua index 46bff75..bc8fd13 100644 --- a/modules/network/network.lua +++ b/modules/network/network.lua @@ -73,6 +73,15 @@ function network.adjacent_connected_nodes(pos, include_ctrl) end end end + -- Additionally, linked quantum link nodes are adjacent. + if me.get_node(pos).name == "microexpansion:quantum_link" then + local source = minetest.get_meta(pos):get_string("source") + if source ~= "" then + local apos = vector.from_string(source) + local nn = me.get_node(apos).name + table.insert(nodes,{pos = apos, name = nn}) + end + end return nodes end @@ -274,6 +283,12 @@ function network:get_inventory_name() return "microexpansion_storage_"..cp.x.."_"..cp.y.."_"..cp.z end +function me.leftovers(pos, leftovers) + -- Ick, no room, just drop on the floor. + -- todo: play sound + minetest.add_item({x=pos.x, y=pos.y-2, z=pos.z}, leftovers) +end + function network:get_inventory_space(inv, list) local inv = inv or self:get_inventory() local listname = list or "main" @@ -432,6 +447,19 @@ function network:update_demand() local meta = minetest.get_meta(ipos) local inventories = minetest.deserialize(meta:get_string("connected")) demand = demand + #inventories * 20 + 40 -- interfaces are 40 and 20 for each machine or inventory + elseif name == "microexpansion:quantum_ring" then + demand = demand + 50 -- quantum requires a ton of power even in standby + elseif name == "microexpansion:quantum_link" then + demand = demand + 100 -- quantum requires a ton of power even in standby + local source = minetest.get_meta(pos):get_string("source") + if source ~= "" then + local apos = vector.from_string(source) + -- for a pair, 25 to 1062 eu, rich people pay for distance. + local distance = vector.distance(net.controller_pos, user:get_pos()) + local charge_to_take = math.pow(math.log(distance),2) * 10 + -- When running it take even more + demand = demand + 500 + charge_to_take/2 + end else demand = demand + 20 -- everything else is 20 end @@ -479,7 +507,7 @@ function network:serialize() sert[i] = v end end - return sert + return sert end function network:destruct() diff --git a/modules/quantum/init.lua b/modules/quantum/init.lua new file mode 100644 index 0000000..5ed57b9 --- /dev/null +++ b/modules/quantum/init.lua @@ -0,0 +1,19 @@ +-- quantum/init.lua + +local module_path = microexpansion.get_module_path("quantum") + +microexpansion.require_module("network") + + +-- Iron Ingot Ingredient for MineClone2 +microexpansion.iron_ingot_ingredient = nil +if minetest.get_modpath("mcl_core") then + microexpansion.iron_ingot_ingredient = "mcl_core:iron_ingot" +else + microexpansion.iron_ingot_ingredient = "default:steel_ingot" +end + +-- Load Quantum bits +dofile(module_path.."/matter_condenser.lua") +dofile(module_path.."/quantum_link.lua") +dofile(module_path.."/quantum_ring.lua") diff --git a/modules/quantum/matter_condenser.lua b/modules/quantum/matter_condenser.lua new file mode 100644 index 0000000..b840fcb --- /dev/null +++ b/modules/quantum/matter_condenser.lua @@ -0,0 +1,185 @@ +-- matter condenser +-- machines/matter_condenser.lua + +local me = microexpansion +local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false +local access_level = microexpansion.constants.security.access_levels + +me.register_item("singularity", { + description = "Singularity", + usedfor = "used to link quantum rings", + not_in_creative_inventory = 1, +}) + +function me.create_singularity() + -- todo: this uuid needs to be saved + me.singularity = (me.singularity or 0) + 1 + local singularity = ItemStack("microexpansion:singularity 2") + singularity:get_meta():set_string("id", me.singularity) + return singularity +end + +local fire = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local dstack = inv:get_stack("input", 1) + local timer = minetest.get_node_timer(pos) + -- one at a time please + if timer:is_started() then return end + timer:start(1) +end + +-- [me matter condenser] Get formspec +local function chest_formspec(pos) + local net = me.get_connected_network(pos) + local list + list = [[ + list[context;input;0,0.3;1,1] + tooltip[input;Trash can of items to be destroyed] + list[context;dst;2,0.3;1,1] + list[current_player;main;0,3.5;8,1;] + list[current_player;main;0,4.73;8,3;8] + listring[current_name;dst] + listring[current_player;main] + listring[current_name;input] + listring[current_player;main] + ]] + + local formspec = + "size[9,7.5]".. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + "label[0,-0.23;Matter Condenser]" .. + list + return formspec +end + +local function update_chest(pos,_,ev) + --for now all events matter + local meta = minetest.get_meta(pos) + meta:set_string("formspec", chest_formspec(pos)) +end + +local recipe = nil +if minetest.get_modpath("mcl_core") then +recipe = { + { 1, { + {"", "mcl_chests:chest", ""}, + {"", "microexpansion:machine_casing", ""}, + {"mcl_core:iron_ingot", "microexpansion:cable", "mcl_core:iron_ingot"}, + }, + } +} + +else + +recipe = { + { 1, { + {"", "default:chest", ""}, + {"", "microexpansion:machine_casing", ""}, + {"default:steel_ingot", "microexpansion:cable", "default:steel_ingot"}, + }, + } + } +end + +-- [me chest] Register node +me.register_node("matter_condenser", { + description = "Matter Condenser", + usedfor = "Used to make singularities", + tiles = { + "matter_condenser", + }, + recipe = recipe, + is_ground_content = false, + groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 }, + paramtype = "light", + paramtype2 = "facedir", + me_update = update_chest, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size("dst", 1) + inv:set_size("input", 1) + meta:set_string("formspec", chest_formspec(pos, 1)) + local net,cp = me.get_connected_network(pos) + me.send_event(pos, "connect", {net=net}) + end, + can_dig = function(pos, player) + if not player then + return false + end + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return false + end + local net,cp = me.get_connected_network(pos) + if not net then + return true + end + return net:get_access_level(name) >= access_level.modify + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + allow_metadata_inventory_take = function(pos,_,_,stack, player) + local network = me.get_connected_network(pos) + if network then + if network:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end + return stack:get_count() + end, + on_metadata_inventory_put = function(pos) + fire(pos) + end, + on_timer = function(pos, elapsed) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local dstack = inv:get_stack("input", 1) + if dstack:get_name() == "" then + return + end + inv:set_stack("input", 1, "") + -- todo: a little fast, reduce + inv:set_stack("dst", 1, me.create_singularity()) + end, + tube = { + can_insert = function(pos, _, stack) --pos, node, stack, direction + local net = me.get_connected_network(pos) + if not net then + return false + end + local inv = minetest.get_meta(pos):get_inventory() + local dstack = inv:get_stack("input", 1) + if dstack:get_name() == "" then + return true + end + if dstack:get_name() == stack:get_name() and + dstack:get_wear() == stack:get_wear() then + return true + end + return false + end, + insert_object = function(pos, _, stack) + local inv = minetest.get_meta(pos):get_inventory() + local dstack = inv:get_stack("input", 1) + stack:set_count(stack:get_count() + dstack:get_count()) + inv:set_stack("input", 1, stack) + fire(pos) + return ItemStack() + end, + connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, + }, + after_place_node = pipeworks_enabled and pipeworks.after_place, + after_dig_node = pipeworks_enabled and pipeworks.after_dig, +}) + +if me.uinv_category_enabled then + unified_inventory.add_category_item("storage", "microexpansion:matter_condenser") +end diff --git a/modules/quantum/quantum_link.lua b/modules/quantum/quantum_link.lua new file mode 100644 index 0000000..2cc4072 --- /dev/null +++ b/modules/quantum/quantum_link.lua @@ -0,0 +1,202 @@ +-- network/quantum_link.lua + +local me = microexpansion +local network = me.network +local access_level = microexpansion.constants.security.access_levels + +local recipe = { + { 1, { + {"microexpansion:steel_infused_obsidian_ingot", microexpansion.iron_ingot_ingredient, microexpansion.iron_ingot_ingredient}, + {microexpansion.iron_ingot_ingredient, "microexpansion:machine_casing", microexpansion.iron_ingot_ingredient}, + {microexpansion.iron_ingot_ingredient, "microexpansion:cable", microexpansion.iron_ingot_ingredient}, + }, + } +} + +-- [me matter condenser] Get formspec +local function chest_formspec(pos) + local net = me.get_connected_network(pos) + local list + list = [[ + list[context;input;0,0.3;1,1] + tooltip[input;Place singularity to link two rings] + list[current_player;main;0,3.5;8,1;] + list[current_player;main;0,4.73;8,3;8] + listring[current_name;input] + listring[current_player;main] + ]] + + local formspec = + "size[9,7.5]".. + microexpansion.gui_bg .. + microexpansion.gui_slots .. + "label[0,-0.23;Quantum Link]" .. + list + return formspec +end + +local link_down = function(pos) + local meta = minetest.get_meta(pos) + local source = meta:get_string("source") + if source ~= "" then + local net,idx = me.get_network(pos) + --we want to write all the drives, then cut things + --me.send_event(pos, "writedrives") + me.send_event(pos, "disconnect") + + local rpos = vector.from_string(source) + -- this fails? + local rnode = me.get_node(rpos) + local rmeta = minetest.get_meta(rpos) + + local nn = me.get_node(rpos).name + -- todo: verify the other side is still a quantum link? + -- once taken, the bidirectional link goes away, it was powered + -- by the singularity. + meta:set_string("source", "") + rmeta:set_string("source", "") + me.send_event(pos, "disconnect") + end +end + +-- [register node] Quantum Link +me.register_node("quantum_link", { + description = "Quantum Link", + tiles = { + "quantum_link", + }, + recipe = recipe, + drawtype = "nodebox", + paramtype = "light", + paramtype2 = "facedir", + groups = { cracky = 1, me_connect = 1 }, + connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, + me_update = function(pos,_,ev) + local net = me.get_connected_network(pos) + local meta = minetest.get_meta(pos) + if net == nil then + meta:set_string("infotext", "No Network") + -- swap_nodes to inactive + else + meta:set_string("infotext", "Quantum Link Established") + -- swap_nodes to active + end + end, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size("input", 1) + meta:set_string("formspec", chest_formspec(pos)) + local net,cp = me.get_connected_network(pos) + me.send_event(pos, "connect", {net=net}) + end, + can_dig = function(pos, player) + if not player then + return false + end + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return false + end + local net,cp = me.get_connected_network(pos) + if not net then + return true + end + return net:get_access_level(name) >= access_level.modify + end, + on_destruct = function(pos) + link_down(pos) + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + allow_metadata_inventory_take = function(pos,_,_,stack, player) + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end + + link_down(pos) + + return stack:get_count() + end, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end + if stack:get_name() ~= "microexpansion:singularity" then + return 0 + end + local qid = stack:get_meta():get_int("id") + if stack:get_meta():get_int("id") == 0 then + -- only singularities from the matter condenser, and + -- they can only be used once. + return 0 + end + if not me.qnets then + me.qnets = {} + end + -- note: a link has to be established in one session + if me.qnets[qid] then + local meta = minetest.get_meta(pos) + -- link remote to us. + local rpos = me.qnets[qid] + local rmeta = minetest.get_meta(rpos) + -- links are uniform bidirectional + if rmeta then + local nodes = me.network.adjacent_connected_nodes(pos) + local rpos = vector.zero() + local count = 0 + for x = pos.x-1, pos.x+1 do + for y = pos.y-1, pos.y+1 do + for z = pos.z-1, pos.z+1 do + local npos = vector.new(x, y, z) + local n = me.get_node(npos) + if n.name == "microexpansion:quantum_ring" then + rpos = vector.add(rpos, npos) + count = count + 1 + end + end + end + end + local spos = vector.multiply(pos, count) + -- The structure must be balanced and we need at least 8 quantum rings + if vector.equals(spos, rpos) and count >= 8 then + meta:set_string("source", vector.to_string(rpos)) + rmeta:set_string("source", vector.to_string(pos)) + end + end + me.qnets[qid] = 0 + -- And just like that, we are now up + me.send_event(pos, "connect") + else + -- remember us + me.qnets[qid] = pos + me.send_event(pos, "connect", {net=net}) + end + return 1 + end, + on_metadata_inventory_put = function(pos, listname, index, stack, player) + -- the power required to estabilish the link causes the + -- singularities to decohere + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + stack:get_meta():set_int("id", 0) + inv:set_stack(listname, index, stack) + end, + machine = { + type = "conductor", + }, +}) diff --git a/modules/quantum/quantum_ring.lua b/modules/quantum/quantum_ring.lua new file mode 100644 index 0000000..0097bcb --- /dev/null +++ b/modules/quantum/quantum_ring.lua @@ -0,0 +1,68 @@ +-- network/quantum_ring.lua + +local me = microexpansion +local network = me.network +local access_level = microexpansion.constants.security.access_levels + +local recipe = { + { 1, { + {microexpansion.iron_ingot_ingredient, microexpansion.iron_ingot_ingredient, "microexpansion:steel_infused_obsidian_ingot"}, + {microexpansion.iron_ingot_ingredient, "microexpansion:machine_casing", microexpansion.iron_ingot_ingredient}, + {microexpansion.iron_ingot_ingredient, "microexpansion:cable", microexpansion.iron_ingot_ingredient}, + }, + } +} + +-- [register node] Quantum Ring +me.register_node("quantum_ring", { + description = "Quantum Ring", + tiles = { + "quantum_ring", + }, + recipe = recipe, + drawtype = "nodebox", + paramtype = "light", + paramtype2 = "facedir", + groups = { cracky = 1, me_connect = 1 }, + connect_sides = {left=1, right=1, front=1, back=1, top=1, bottom=1}, + me_update = function(pos,_,ev) + local net = me.get_connected_network(pos) + local meta = minetest.get_meta(pos) + if net == nil then + meta:set_string("infotext", "No Network") + else + meta:set_string("infotext", "Quantum Link Established") + end + end, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local net,cp = me.get_connected_network(pos) + --me.send_event(pos, "connect", {net=net}) + if net == nil then + meta:set_string("infotext", "No Network") + else + meta:set_string("infotext", "Quantum Link Established") + end + end, + can_dig = function(pos, player) + if not player then + return false + end + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return false + end + local net,cp = me.get_connected_network(pos) + if not net then + return true + end + return net:get_access_level(name) >= access_level.modify + end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, + machine = { + type = "conductor", + }, +}) diff --git a/textures/microexpansion_matter_condenser.png b/textures/microexpansion_matter_condenser.png new file mode 100644 index 0000000000000000000000000000000000000000..24f645e1ade86169df478c8914c253f0dd570461 GIT binary patch literal 3612 zcmcgv4R93I9p7SG1#3(M2^Jl;E7KC_-oE{~{b~*jxe$ULB;iUzt;M&yZtnLR~vai)$Moy8n%W4sdy5j38HvjDhc%lQiKUerI)h>;juM`W zv1|^DqZEnc;3JBS$8%`cD{I0QWqlhF?M7RYh^a=d-RS73tO;wpchE4`t47yDGmhmL zo?D!(w>{gfw?_#=ZhBZKJj)zKp+7SWic;1nP=6)|O{%31f}ZU}Z96uYJC%dBk)@^N ztQsp}*v)Q{?{*fB!q7t@%19Tv5eeas1j=eC;^r>B~hXQkEO|? z0BMB}Dl!b}I&YeT(v~4 zg+t6yq@OKnTK`f^T-X3H803T5@k%R&VzprlN6pO`A`YEdK77XKF~2_5g_esmt|iMf zOVP^gTg)QUC8Bffhe9+`fq|iYQ2(+mQ2CXPMq>-i>vcf7n4B}iG&2UZ?fEomY74+#1&q~(o zVWJM<_QO#ByDNZ4qUxA$ceNBHQm&a!3H7Dn2}r=fegmw3daVxq0E$7 zG#+PkJJE!kAxV{l4#X`KIc|tD9Xn3KWHM&y>B?f75=OQ&<%*H?Z1Otwj2bipIo6O2 z7^)>_0{I#08#CX=$Aq8zCNcieAVUGnf3id7JDiE+0)KDPEms|h2gaL&xCg74*2f@v z9w1HPWCK^D5X2or;Au^l5e+0m&~d?PvX*8gUz+{@61|^8x%5&8`$Y-vWIj#0S%#Sp z>vCK(cjWdYaJcLMC(%gZbz0;FM9Y{+XhV~AASt457)EhA;4y0~St8J7gEd)vcGP%U zQgD~kuvl6aL=(!meL)2NTh^y(a6?w7ZOS;v+&B_xx@pUoKc4mGsm+0{w{V5r zz^(BIPo1AQzhG9%}3FjqP_41qH1efcv!>R|nQEDq0f$QQ}N*Q^(Go zJLk-q1E)dLr%LjF0Od3LRY5qr>Cnx@j-%(N*6caB^6t0$etqJDBdK$zySuu& zYPwg<`$hA%YU(c&WV@%hruh5%+E6vXC&#l?`#lbDJz!Q zjz2cD;itR*RsG?cUozW&SiZ0Q;`ikBMfXvYu6=yG_vR}z?s-Ig?*7Xix~94q_|v?#w&iGjwV1Zd-Pn+j6 z%-r4Hp;enmil#)HC={D01u>$*KdQmdZ^T#vX^KVy$zdcmE#V`Ys36gR@9bCalK%M;<8L8b49U`AL0Ki)9>;R@!vInc@Ocw6=7j4 z&SWlMoqqc=4<5KWllky3z4B->S{ib2P|qR~xHQ|Sha8>BY}nWc5gw$2Ji+-Xd&hIGmtP6C^b1H9vM5IkCy>@auF~ z5``ut8PAFRsX<}1G%O4T5fzNA46rOKf@x==p=pMElc30oEXleA6#xxK)*NUG?T^T< zMT9vc`7Lc*{4OU}lO%K`X=-XJJEdlWs3Jkzwk27S6a{buh^PGoHGm&q-(kqp7)M^1 zc!4jZjK~cplAOprZEc|*c4_^%9VQ+ysewWXX61BCO(4NtTsRTcn#Kv1XpPpXpTrys zyI3BjQmG4HkhWg$qT^)qB=@Mjw*}F0c{-%h2#te@2-D4z+~V~e$Kqs!UIQA*gBJ4tFt~I;E6P)()F$CA_3+c-Lr5EXk$~ADNsyg0Zr2^fG8)|2!yWA z7&Z{@u-z7UyueXyE^F!);f$`#sOriFP+f!Z@M#dZ3bFxm6(SpqS*ogu!Uem7K`n?% z0pT%MMIk+FhtqRcQCeL&5w-_M(?qRJv<6K@<{t^BzwP<4CFU>Z9I#{xMYO-dBfLaSNQ=21qsfaquBtjy z7`1_}$UHM$jR4!m2vFB0mJMwc>8!Ky_dBjQn(XLur-DAX z#!Q15z-3$zY(+BxB8CeL=2BHxE!WiO!dqED40_s%0<+3oWoj+zWivSNCuuY%sX`PX z-a=^<=R_6-bpeH8&BM*gk|ur9?o8cc6p}!=AU%r)jnK%OlL2$BbX^eshxV4tx89Vn z70CJd_J2JjDSb3(Nr3e4>d?B+w<5VEJ)36Bbr#ZrUUQK5V28{45<<^4-Zd4A@M`28 z13^s%F19F;O`>65u&(7cRVif){#&6>bEunMPN!e=^G=q^WZYJm0aWQ~%`FSJCs~J3 zhm^M!3n{>an#L>AC4gv(&0MA-tQs4d0k3&WarF{`Ub!>{8C-QuKy4KRL)9p-xWMtA zY+>274NM5>mCKN^MIeK`gS#p)Z4CjJ(*jE~7_xXzbw7?)$ku$OFNg)y*?(C4_YG z%xNO$Jc;vTZw~Rt`}DH=Ucqm=Zzj~|{UGA+9s3`?VNE8}_myJ4zijRO)%JdAu>a}< zW50RpM{k$Il{c<^Xkh&-XU}cCIgVcI7saPGi?e5redn#CubCft{pe#)+Ak9G@JsKo z%DHDpR=umte($lqA@CtD;Z`em{11C}KjH6= zhR@b+IQYq}2lqd3|N8Z3Z#|%0x05Y9ar*Ay&XeQn7JD>PoV_#m`SB;V7Awesb! z?Gsk8dv2fo_{^#73p?&lMt{0??+RQ6u; zt(}vGS~&5gHS61J0uHIw=T-IE7semB@uSft@BSnB=d#0BzVfjnyEc72eSs`)-j;uS IVCU|C0rXqk7XSbN literal 0 HcmV?d00001 diff --git a/textures/microexpansion_quantum_ring.png b/textures/microexpansion_quantum_ring.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9873fdaf9e18dc7b578b39264f96c4940cdd27 GIT binary patch literal 4084 zcmcgv3vd(189rQS43y%4ODLI!Ll9yfE8Y8r<-3682J{=&k06Kv<&y$4@qeEC&CS8TKY2PV;J?1{Ni{Y>^dIbg zdl75b55?etW~kb!4o+1xE9!;1r6O;0G>*{(QTT9k9BT8BL#n9WjQKqux9#_krtbGt z@j)sWFGXQ<()>r=Fih)-Scn}S=g*#f+%tz*&L0;Y^B-nN%AWA*=_cD$Rxy> z=l2x56Oz@z>13&uK&0TMfJRXaDau}2;5dOAPcjrk`6%8;Gk_Kpic@HjOn*FhwuEjd zGXoRTbKy6?C+s+J#pi2kYVtO*UMo@Wqh(q4QH+ma07d}2CFa0p5VJ>iG6ayVCCs>E zS~1dPgsRox_&wOt)Doic3~kI#y9qnY*9_x6+Do}hN&);N8+jqZzdAlr>_D(rfD$ZHHRo$Tt(&RzpHVWsP{ok)4Lw&P;UEHCYVD z@XX+N*m5j8Y~8^LneJO$;h4r9CUkplfud&f4p6rz1C1-C2?QN05wfgEr*@`y+D4X^ zk_A;}Ot+fsG48U{a1?+J@_Sqv0ZIb26k=$Fl@(qZM==UTrJzA9OdUF~D_F+(5KSo@ zr?AW|U`scRmL8$HrWjTt3h}m?QCN?B@mPIwwqQ^xkJ%24X{bEl_h3`Krl~6e#nLiM z8vt=K0}O%Ffvj;NFc?-~MTVm^S?nAiu(SqOQttRvbm^9cIWl{p$dVyx0<8m$7i0iA zP68^+&_FUYjuj|blxVq=ZCb*_0S+TwSzW7i%&1ZZVpU22tST7TeH;x`24Y5)(J8?& zB$nkoml8}Rxmv_6}r*o_t5FaYFDU{hE$`-Q267c-QRS7O%MGy zyJy8r>|(}4x@u99^H<`;g$)pRp5@c+)6HfSsScRfYj(;Ip=(dgtvzlYbK_H)WZGD9 zE$K{CiGGlNikM_FBo(NomY})XM7k&4Y<9E3*|{=B+Z0CFS$9!w3T9h|(*zT!xE_1B zhnVo)$#few-raDP9;=X6E~0wbzGr8xv7q)GsZyviY2U?~O%+3kDKZ8FGeDGcpp zx@M--pllYSS+Pcx=pw2(Oh8;hQNs3njD!^>VLTo&wPa-Z8e@98GG&61bS&~_>baxS zbd)f=D4^>s8579=;e0*nExSzkf8QjZdo)O;0N=l7htzu@<;jWe-Xu$|(p?_NR)e?( zD>$w9kmy+osUjokI2t(~K$>HLs!0e?qRweJU{y&?W+j)+`fn4xtD%g%l+Jolj4PR& zChc^FDS`EwTvOD2d7^ll=4ph>iUb)T(i{g=L)C%KF|wf=9Mo8$FzN7!*%Mbb9>|u9 zW1xVut_YCKYCvG!{D2c2&{avJL|M>uUC)|J5TM4eETC8p0$e9~yfYdN5GBz#32__+ zwQRWrN|SWjpz--wWq~MTLKT|`Bu+4(giAC;RJL3qKB_}pYXJ>qOqe_x(6j)cB=Wq- z35aF5Y`G*#V-1>7@hOGT04;J1_7{UNHLD2}&q=JT-p=(^4i{ZUC-smLm+$T>Od`9S z>am29P57$dK4$JH_$718K`~rg68H{t@3lE^5Jb+8%LB!sf~RX&P6-d5k-zkqzT?Qq zHB+}n=MLXQ2EQjZ4DL7QSG}EC1-U~u-@obpPj|*29P7=m?fb%0$3!{cvUedzV( zU#~hmZ}p|WJTao8b^n6vh5Ogl725Qn3+o@1xgqZ!{pFGQZ`JK{&QXB)?NWY0! z4m7OFD!uYAY@e7{a&=B0%R6v*+w-&5UOIGetzFrn z59)t<$xCImr&o3SIe!m3^72aqx10>E**kCC0|&1ZuE;wublkENA2zn0-1xW3uXVg4 zZ~s%&KfB#5Dj2bN@)_@mWpnDje15C>LP2BOALqXM{y%F8^`qw=p8Or+{OhY9sbgBMzPbvy?%q;SdGVQRf#I9&cl%#F^xgeO#(a9};)9X%OTMW2 c`%d$`__*KZPxY$h+Sw&z+$zzND1t@}YJOBUy literal 0 HcmV?d00001 diff --git a/textures/microexpansion_singularity.png b/textures/microexpansion_singularity.png new file mode 100644 index 0000000000000000000000000000000000000000..41df135fe9f7f1a77e34de315c188b1422a81fcd GIT binary patch literal 749 zcmV7 z0>w;pykk<|1rbs`-X>5I5=wgDmL_l_&7_oK6PVILDy43Z1WoUQNZ5p;q=XwdG~u83 z3Q5)bCOo0|TKeZFTm9&Im5b>0vQ=Jd;gN{>(FLX$lW@{t0)Cc>$BUTqjfd!kM-g=5 zfkzHJa`NyD9zwHFcoaT;Sr0;m2lLVeKX~{r@+c)XOkO6hht(n{e0@ z57iTYLeh6w4&@b>L>a`1xfcDu? zuYbq`FC{u(3OrQC0ok&R2drv;%y=LmOz1ppbCW-AJQCk{#E#H-KnN2L)H!Tshmxn~ zK^0$Ij`FYkaqSn_vToRH2|khQyj|yhk$JvU9(kHCjfccrzrY`tegU6LzZ4#9 Date: Sat, 3 Feb 2024 15:41:14 -0800 Subject: [PATCH 07/36] Fix crafting terminal. Fix controller crash. --- modules/network/ctrl.lua | 4 +++- modules/storage/cterminal.lua | 1 + modules/storage/interface.lua | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/network/ctrl.lua b/modules/network/ctrl.lua index fc88ebe..566cf39 100644 --- a/modules/network/ctrl.lua +++ b/modules/network/ctrl.lua @@ -112,7 +112,9 @@ me.register_node("ctrl", { -- turn ON without mesepower local meta = minetest.get_meta(pos) meta:set_int("enabled", 1) - net:update_demand() + if net then + net:update_demand() + end end }}, on_construct = function(pos) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index dd23de9..5bf8cc3 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -14,6 +14,7 @@ local me = microexpansion local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false +local access_level = microexpansion.constants.security.access_levels -- caches some recipe data to avoid to call the slow function minetest.get_craft_result() every second me.autocrafterCache = {} diff --git a/modules/storage/interface.lua b/modules/storage/interface.lua index c51104b..76ff6dd 100644 --- a/modules/storage/interface.lua +++ b/modules/storage/interface.lua @@ -307,7 +307,7 @@ me.register_node("interface", { after_destruct = function(pos) me.send_event(pos, "disconnect") end, - allow_metadata_inventory_put = function(pos, listname, index, stack) + allow_metadata_inventory_put = function(pos, listname, index, stack, player) return stack:get_count() end, on_metadata_inventory_put = function(pos, _, _, stack) From 60550a8122b0ffd7482e20b711cff928dd7202fc Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 4 Feb 2024 14:25:04 -0800 Subject: [PATCH 08/36] Fix typo. --- modules/quantum/quantum_link.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/quantum/quantum_link.lua b/modules/quantum/quantum_link.lua index 2cc4072..8625824 100644 --- a/modules/quantum/quantum_link.lua +++ b/modules/quantum/quantum_link.lua @@ -157,7 +157,7 @@ me.register_node("quantum_link", { -- links are uniform bidirectional if rmeta then local nodes = me.network.adjacent_connected_nodes(pos) - local rpos = vector.zero() + local spos = vector.zero() local count = 0 for x = pos.x-1, pos.x+1 do for y = pos.y-1, pos.y+1 do @@ -165,15 +165,15 @@ me.register_node("quantum_link", { local npos = vector.new(x, y, z) local n = me.get_node(npos) if n.name == "microexpansion:quantum_ring" then - rpos = vector.add(rpos, npos) + spos = vector.add(spos, npos) count = count + 1 end end end end - local spos = vector.multiply(pos, count) + local mpos = vector.multiply(pos, count) -- The structure must be balanced and we need at least 8 quantum rings - if vector.equals(spos, rpos) and count >= 8 then + if vector.equals(spos, mpos) and count >= 8 then meta:set_string("source", vector.to_string(rpos)) rmeta:set_string("source", vector.to_string(pos)) end From 2cf222436b9b8720a051260724d6f90be9ab338f Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 4 Feb 2024 16:32:39 -0800 Subject: [PATCH 09/36] Add an ability to search the descriptions. #25 --- modules/storage/remote.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index fc0a616..0474e7c 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -16,6 +16,7 @@ local function get_metadata(toolstack) if not m.page then m.page = 1 end if not m.query then m.query = "" end if not m.crafts then m.crafts = "false" end + if not m.desc then m.desc = "false" end if not m.inv_name then m.inv_name = "main" end if not m.query then m.query = "" end return m @@ -61,8 +62,10 @@ local function chest_formspec(s, pos, start_id, listname) listring[current_player;main] ]] buttons = [[ - button[3.56,4.35;1.8,0.9;tochest;To Drive] + button[3.56,4.35;0.9,0.9;tochest;To Drive] tooltip[tochest;Move everything from your inventory to the ME network.] + checkbox[4.46,4.35;desc;desc;]]..s.desc..[[] + tooltip[desc;Search the descriptions] button[5.4,4.35;0.8,0.9;prev;<] button[7.25,4.35;0.8,0.9;next;>] tooltip[prev;Previous] @@ -262,6 +265,9 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) update_search = true did_update = true elseif field == "tochest" then + elseif field == "desc" then + toolmeta.desc = value + update_search = true elseif field == "autocraft" then if tonumber(value) ~= nil then toolmeta.autocraft = value @@ -319,6 +325,10 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) local tab = {} for i = 1, inv:get_size(inv_name) do local match = inv:get_stack(inv_name, i):get_name():find(toolmeta.query) + if toolmeta.desc == "true" then + match = match or inv:get_stack(inv_name, i):get_description():find(toolmeta.query) + match = match or inv:get_stack(inv_name, i):get_short_description():find(toolmeta.query) + end if match then tab[#tab + 1] = inv:get_stack(inv_name, i) end From b0466d4f4f6d9d603966cc0cc25dd63e59b9e458 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 4 Feb 2024 17:01:25 -0800 Subject: [PATCH 10/36] Fix reset on the remote. --- modules/storage/remote.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index 0474e7c..a41bcaf 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -18,7 +18,6 @@ local function get_metadata(toolstack) if not m.crafts then m.crafts = "false" end if not m.desc then m.desc = "false" end if not m.inv_name then m.inv_name = "main" end - if not m.query then m.query = "" end return m end @@ -260,6 +259,7 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) toolmeta.page = page toolmeta.inv_name = "main" toolmeta.query = "" + toolmeta.filter = "" toolmeta.crafts = "false" toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 update_search = true From ed256bb7799b380080ff33115fbac940ed32434e Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Tue, 6 Feb 2024 21:09:09 -0800 Subject: [PATCH 11/36] Fix controller recipe. --- modules/network/init.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/network/init.lua b/modules/network/init.lua index 5b0488a..48cc5e0 100644 --- a/modules/network/init.lua +++ b/modules/network/init.lua @@ -6,6 +6,14 @@ local storage = minetest.get_mod_storage() dofile(path.."/constants.lua") +-- Iron Ingot Ingredient for MineClone2 +microexpansion.iron_ingot_ingredient = nil +if minetest.get_modpath("mcl_core") then + microexpansion.iron_ingot_ingredient = "mcl_core:iron_ingot" +else + microexpansion.iron_ingot_ingredient = "default:steel_ingot" +end + --deprecated: use ItemStack(x) instead --[[ local function split_stack_values(stack) From 9a2e379757e211d8a78b1078637ac4a04bc181e3 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Thu, 8 Feb 2024 16:06:21 -0800 Subject: [PATCH 12/36] Improve technic integration. --- modules/storage/technic-interop.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/storage/technic-interop.lua b/modules/storage/technic-interop.lua index 5facf94..4cafb3f 100644 --- a/modules/storage/technic-interop.lua +++ b/modules/storage/technic-interop.lua @@ -9,6 +9,7 @@ local me = microexpansion -- The speeds of the various machines: me.set_speed("technic:electric_furnace", 2) +me.set_speed("technic:lv_electric_furnace", 2) me.set_speed("technic:mv_electric_furnace", 4) me.set_speed("technic:hv_electric_furnace", 12) me.set_speed("technic:lv_alloy_furnace", 1) @@ -91,6 +92,7 @@ me.register_max("default:ice", 99*4) -- The type of machines all the machines are: We have to list these -- before me.register_inventory. me.register_typename("technic:electric_furnace", "cooking") +me.register_typename("technic:lv_electric_furnace", "cooking") me.register_typename("technic:mv_electric_furnace", "cooking") me.register_typename("technic:hv_electric_furnace", "cooking") me.register_typename("technic:lv_grinder", "grinding") @@ -110,6 +112,7 @@ me.register_typename("technic:mv_freezer", "freezing") -- We need active nodes defined as well, as the recipe system doesn't otherwise have -- recipes for them. me.register_machine_alias("technic:electric_furnace_active", "technic:electric_furnace") +me.register_machine_alias("technic:lv_electric_furnace_active", "technic:lv_electric_furnace") me.register_machine_alias("technic:mv_electric_furnace_active", "technic:mv_electric_furnace") me.register_machine_alias("technic:hv_electric_furnace_active", "technic:hv_electric_furnace") me.register_machine_alias("technic:lv_grinder_active", "technic:lv_grinder") From c0d5476afb62f7f5ff8457ad65158e5cc05ce64f Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Fri, 9 Feb 2024 11:47:23 -0800 Subject: [PATCH 13/36] Also set the speed for technic:coal_alloy_furnace. --- modules/storage/technic-interop.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/storage/technic-interop.lua b/modules/storage/technic-interop.lua index 4cafb3f..d61e385 100644 --- a/modules/storage/technic-interop.lua +++ b/modules/storage/technic-interop.lua @@ -12,6 +12,7 @@ me.set_speed("technic:electric_furnace", 2) me.set_speed("technic:lv_electric_furnace", 2) me.set_speed("technic:mv_electric_furnace", 4) me.set_speed("technic:hv_electric_furnace", 12) +me.set_speed("technic:coal_alloy_furnace", 1) me.set_speed("technic:lv_alloy_furnace", 1) me.set_speed("technic:mv_alloy_furnace", 1.5) me.set_speed("technic:lv_compressor", 1) From 80b3a72b2078c9ec7308db97cc33f04c6ea291e2 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Fri, 9 Feb 2024 11:53:34 -0800 Subject: [PATCH 14/36] Default speed to 1. Remove debugging infomation. --- modules/network/autocraft.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index e4e7f32..2072900 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -157,7 +157,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) end dat[1].ostack = ItemStack(dat[1].recip.output) -- me.log("SEE: "..machine_name.." "..minetest.serialize(technic.recipes)) - local speed = me.speed[machine_name] + local speed = me.speed[machine_name] or 1 local craft_count = dat[1].ostack:get_count() local total = math.ceil(count/craft_count) -- Remove the extra machines. In theory we could remove the busy machines. @@ -496,7 +496,7 @@ function me.later(net, cpos, action, time) net.pending.index = 1 end if i == 1 then - me.log("TIMER: starting timer to fire at "..time.." seconds", "error") + --me.log("TIMER: starting timer to fire at "..time.." seconds", "error") me.start_crafting(cpos, time+0.1) else -- me.log("TIMER: did not start timer for later, index "..i.." at time "..time, "error") From f8172f5c95ac2d9b8f28ffbb7c4811e527ff0d7c Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 10 Feb 2024 10:32:51 -0800 Subject: [PATCH 15/36] Improve query clearing on the remote. --- modules/storage/remote.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index a41bcaf..adbc062 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -260,6 +260,8 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) toolmeta.inv_name = "main" toolmeta.query = "" toolmeta.filter = "" + toolstack:get_meta():set_string("query", "") + toolstack:get_meta():set_string("filter", "") toolmeta.crafts = "false" toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 update_search = true From 0cb853fbab1ff0a293fffafd918232b801f7609b Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 10 Feb 2024 10:34:42 -0800 Subject: [PATCH 16/36] Separate autocrafting input and output by a little to ensure they don't collide. --- modules/network/autocraft.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index 2072900..533536e 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -68,7 +68,8 @@ function me.reserve(net, pos, original_start, length) local free_time = net.pending.busy[pos] or 0 local start = math.max(free_time, original_start) local ending = start + length - net.pending.busy[pos] = ending + -- inputs and outputs can collide if we run them too close + net.pending.busy[pos] = ending + 1 return start end From 02c228b1412cb920b9dd337f6b085f8c52554e7a Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 10 Feb 2024 11:37:25 -0800 Subject: [PATCH 17/36] FIx remote GUI clearing. --- modules/storage/remote.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index adbc062..4f486da 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -27,6 +27,8 @@ local function chest_formspec(s, pos, start_id, listname) local buttons = "" local net,cpos = me.get_connected_network(pos) + -- luajit seems to need this to ensure "clear" works, weird, why? + local dummy = s.query if net then local inv = net:get_inventory() if listname and (inv:get_size(listname) > 0 or net:get_item_capacity() > 0) then @@ -259,9 +261,6 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) toolmeta.page = page toolmeta.inv_name = "main" toolmeta.query = "" - toolmeta.filter = "" - toolstack:get_meta():set_string("query", "") - toolstack:get_meta():set_string("filter", "") toolmeta.crafts = "false" toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 update_search = true From 33d4bacae7503b0a15db749f3f2a2a4afc4c2770 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 10 Feb 2024 17:30:45 -0800 Subject: [PATCH 18/36] Add description searching to cterminal. Incorporate recent updates into cterminal. --- modules/storage/cterminal.lua | 225 +++++++++++++++++++++------------- modules/storage/remote.lua | 4 +- modules/storage/terminal.lua | 46 +++---- 3 files changed, 166 insertions(+), 109 deletions(-) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 5bf8cc3..8086023 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -1,5 +1,5 @@ -- crafting terminal --- microexpansion/cterminal.lua +-- microexpansion/modules/storage/cterminal.lua -- TODO: Bugs, can't craft sticks, oil extract by using the -- output. Does work when updating the recipe. Groups are hanky. We @@ -21,12 +21,13 @@ me.autocrafterCache = {} local autocrafterCache = me.autocrafterCache -- [me chest] Get formspec -local function chest_formspec(pos, start_id, listname, page_max, q, c) +local function chest_formspec(pos, start_id, listname, page_max, q, c, d) local list local page_number = "" local buttons = "" local query = q or "" local crafts = (c and "true") or "false" + local desc = (d and "true") or "false" local net,cpos = me.get_connected_network(pos) if cpos then @@ -62,11 +63,12 @@ local function chest_formspec(pos, start_id, listname, page_max, q, c) listring[context;output] listring[current_player;main] listring[context;search] - listring[current_player;main] ]] buttons = [[ - button[3.56,4.35;1.8,0.9;tochest;To Drive] + button[3.56,4.35;0.9,0.9;tochest;To Drive] tooltip[tochest;Move everything from your inventory to the ME network.] + checkbox[4.46,4.35;desc;desc;]]..desc..[[] + tooltip[desc;Search the descriptions] button[5.4,4.35;0.8,0.9;prev;<] button[7.25,4.35;0.8,0.9;next;>] tooltip[prev;Previous] @@ -120,7 +122,8 @@ local function update_chest(pos,_,ev) local net = me.get_connected_network(pos) local meta = minetest.get_meta(pos) if net == nil then - meta:set_int("page", 1) + page = 1 + meta:set_int("page", page) meta:set_string("formspec", chest_formspec(pos, 1)) return end @@ -484,6 +487,25 @@ local function on_output_change(pos, linv, stack) return 0 end +local cterm_recipe = nil +if minetest.get_modpath("mcl_core") then +cterm_recipe = { + { 1, { + {"microexpansion:term", "mcl_chests:chest"}, + }, + } +} + +else + +cterm_recipe = { + { 1, { + {"microexpansion:term", "default:chest"}, + }, + } + } +end + -- [me cterminal] Register node me.register_node("cterminal", { description = "ME Crafting Terminal", @@ -496,12 +518,7 @@ me.register_node("cterminal", { "chest_side", "chest_front", }, - recipe = { - { 1, { - {"microexpansion:term", "default:chest"}, - }, - } - }, + recipe = cterm_recipe, is_ground_content = false, groups = { cracky = 1, me_connect = 1, tubedevice = 1, tubedevice_receiver = 1 }, paramtype = "light", @@ -510,7 +527,8 @@ me.register_node("cterminal", { on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_string("inv_name", "none") - meta:set_int("page", 1) + page = 1 + meta:set_int("page", page) local own_inv = meta:get_inventory() own_inv:set_size("src", 1) @@ -521,9 +539,6 @@ me.register_node("cterminal", { me.send_event(pos, "connect", {net=net}) update_chest(pos) end, - after_destruct = function(pos) - me.send_event(pos, "disconnect") - end, can_dig = function(pos, player) if not player then return false @@ -538,28 +553,39 @@ me.register_node("cterminal", { if not inv:is_empty("recipe") then return false end - local net,cp = me.get_connected_network(pos) + local net,cpos = me.get_connected_network(pos) if not net then return true end return net:get_access_level(name) >= access_level.modify end, + after_destruct = function(pos) + me.send_event(pos, "disconnect") + end, allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end --me.log("Allow a move from "..from_list.." to "..to_list, "error") local meta = minetest.get_meta(pos) if to_list == "recipe" and from_list == "search" then local net = me.get_connected_network(pos) - local linv = minetest.get_meta(pos):get_inventory() + local linv = meta:get_inventory() local inv = net:get_inventory() local stack = linv:get_stack(from_list, from_index) - local meta = minetest.get_meta(pos) count = math.min(count, stack:get_stack_max()) stack:set_count(count) me.remove_item(net, inv, "main", stack) return count end if to_list == "output" then - local linv = minetest.get_meta(pos):get_inventory() + local linv = meta:get_inventory() local stack = linv:get_stack(from_list, from_index) return on_output_change(pos, linv, stack) end @@ -568,11 +594,10 @@ me.register_node("cterminal", { end if to_list == "search" then local net = me.get_connected_network(pos) - local linv = minetest.get_meta(pos):get_inventory() + local linv = meta:get_inventory() local inv = net:get_inventory() local stack = linv:get_stack(from_list, from_index) stack:set_count(count) - -- local meta = minetest.get_meta(pos) -- meta:set_string("infotext", "allow moving: "..stack:get_name()) -- TODO: Check capacity? Test. local leftovers = me.insert_item(stack, net, inv, "main") @@ -581,6 +606,15 @@ me.register_node("cterminal", { return count end, allow_metadata_inventory_take = function(pos, listname, index, stack, player) + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end -- This is used for removing items from "search", "recipe" and "output". --me.log("Allow a take from "..listname, "error") local count = stack:get_count() @@ -592,7 +626,6 @@ me.register_node("cterminal", { end --[[if listname == "main" then -- This should be unused, we don't have a local inventory called main. - local net = me.get_connected_network(pos) local inv = net:get_inventory() local ret = me.remove_item(net, inv, "main", stack) --me.log("REMOVE: after remove count is "..ret:get_count(), "error") @@ -602,7 +635,15 @@ me.register_node("cterminal", { return count end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) - -- me.dbg() + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, player) then + minetest.record_protection_violation(pos, player) + return 0 + end if listname == "output" then local linv = minetest.get_meta(pos):get_inventory() return on_output_change(pos, linv, stack) @@ -732,6 +773,9 @@ me.register_node("cterminal", { end, insert_object = function(pos, _, stack) local net = me.get_connected_network(pos) + if not net then + return stack + end local inv = net:get_inventory() local leftovers = me.insert_item(stack, net, inv, "main") net:set_storage_space(true) @@ -758,8 +802,12 @@ me.register_node("cterminal", { end local meta = minetest.get_meta(pos) local page = meta:get_int("page") + local did_update = false + local update_search = false local inv_name = meta:get_string("inv_name") + local query = meta:get_string("query") local crafts = meta:get_string("crafts") == "true" + local desc = meta:get_string("desc") == "true" local own_inv = meta:get_inventory() local ctrl_inv if cpos then @@ -784,22 +832,28 @@ me.register_node("cterminal", { if page + 32 > inv:get_size(inv_name) then return end - meta:set_int("page", page + 32) - meta:set_string("formspec", chest_formspec(pos, page + 32, inv_name, page_max, fields.filter, crafts)) + page = page + 32 + meta:set_int("page", page) + did_update = true elseif fields.prev then if page - 32 < 1 then return end - meta:set_int("page", page - 32) - meta:set_string("formspec", chest_formspec(pos, page - 32, inv_name, page_max, fields.filter, crafts)) + page = page - 32 + meta:set_int("page", page) + did_update = true + elseif fields.desc then + meta:set_string("desc", fields.desc) + desc = fields.desc == "true" + page = 1 + meta:set_int("page", page) + update_search = true elseif fields.crafts then - meta:set_int("page", 1) - --me.log("CRAFT: craftables: "..dump(net and net.process), "error") - --me.log("CRAFT: got fields: "..dump(fields), "error") - inv_name = "main" - if fields.crafts == "true" then - crafts = true - meta:set_string("crafts", "true") + crafts = fields.crafts == "true" + meta:set_string("crafts", fields.crafts) + page = 1 + meta:set_int("page", page) + if crafts then inv_name = "crafts" local tab = {} if net then @@ -818,74 +872,41 @@ me.register_node("cterminal", { own_inv:set_list(inv_name, tab) meta:set_string("inv_name", inv_name) page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) else - crafts = false - meta:set_string("crafts", "false") - if fields.filter == "" then - own_inv:set_size("crafts", 0) + inv_name = "main" + own_inv:set_size("crafts", 0) + if query == "" then meta:set_string("inv_name", inv_name) page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.fields, crafts)) end end - if fields.filter ~= "" then - inv = own_inv - if inv_name == "main" then - inv = ctrl_inv - end - local tab = {} - for i = 1, inv:get_size(inv_name) do - local match = inv:get_stack(inv_name, i):get_name():find(fields.filter) - if match then - tab[#tab + 1] = inv:get_stack(inv_name, i) - end - end - inv_name = "search" - own_inv:set_size(inv_name, #tab) - own_inv:set_list(inv_name, tab) - meta:set_string("inv_name", inv_name) - page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) - end + update_search = true elseif fields.search or fields.key_enter_field == "filter" then own_inv:set_size("search", 0) --me.log("CRAFT: got fields: "..dump(fields), "error") - meta:set_int("page", 1) - inv_name = "main" - inv = ctrl_inv - if crafts then - inv_name = "crafts" - inv = own_inv - end - if fields.filter == "" then - meta:set_string("inv_name", inv_name) - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) - else - local tab = {} - for i = 1, inv:get_size(inv_name) do - local match = inv:get_stack(inv_name, i):get_name():find(fields.filter) - if match then - tab[#tab + 1] = inv:get_stack(inv_name, i) - end - end - inv_name = "search" - own_inv:set_list(inv_name, tab) - meta:set_string("inv_name", inv_name) - page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max, fields.filter, crafts)) - end + page = 1 + meta:set_int("page", page) + query = fields.filter + meta:set_string("query", query) + update_search = true elseif fields.clear then --me.log("CRAFT: got fields: "..dump(fields), "error") own_inv:set_size("search", 0) own_inv:set_size("crafts", 0) - meta:set_int("page", 1) + page = 1 + meta:set_int("page", page) inv_name = "main" meta:set_string("inv_name", inv_name) + query = "" + meta:set_string("query", query) + crafts = false meta:set_string("crafts", "false") page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 - meta:set_string("formspec", chest_formspec(pos, 1, inv_name, page_max)) + did_update = true elseif fields.tochest then + if net:get_access_level(sender) < access_level.interact then + return + end local pinv = minetest.get_inventory({type="player", name=sender:get_player_name()}) -- TODO: test and fix, net:set_storage_space(pinv:get_size("main")) local space = net:get_item_capacity() @@ -906,5 +927,39 @@ me.register_node("cterminal", { end end end + + if update_search then + inv_name = "main" + inv = ctrl_inv + if crafts then + inv_name = "crafts" + inv = own_inv + end + if query == "" then + meta:set_string("inv_name", inv_name) + else + local tab = {} + for i = 1, inv:get_size(inv_name) do + local match = inv:get_stack(inv_name, i):get_name():find(query) + if desc then + match = match or inv:get_stack(inv_name, i):get_description():find(query) + match = match or inv:get_stack(inv_name, i):get_short_description():find(query) + end + if match then + tab[#tab + 1] = inv:get_stack(inv_name, i) + end + end + inv_name = "search" + own_inv:set_size(inv_name, #tab) + own_inv:set_list(inv_name, tab) + meta:set_string("inv_name", inv_name) + page_max = math.floor(own_inv:get_size(inv_name) / 32) + 1 + end + did_update = true + end + + if did_update then + meta:set_string("formspec", chest_formspec(pos, page, inv_name, page_max, query, crafts, desc)) + end end, }) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index 4f486da..2378aad 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -268,6 +268,8 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) elseif field == "tochest" then elseif field == "desc" then toolmeta.desc = value + page = 1 + toolmeta.page = page update_search = true elseif field == "autocraft" then if tonumber(value) ~= nil then @@ -287,7 +289,6 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) end if update_search then - inv_name = "main" if toolmeta.crafts == "true" then inv_name = "me_crafts" local tab = {} @@ -310,6 +311,7 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) toolmeta.page_max = page_max did_update = true else + inv_name = "main" if toolmeta.query == "" then own_inv:set_size("me_crafts", 0) toolmeta.inv_name = inv_name diff --git a/modules/storage/terminal.lua b/modules/storage/terminal.lua index db8ce0c..3186bab 100644 --- a/modules/storage/terminal.lua +++ b/modules/storage/terminal.lua @@ -1,5 +1,5 @@ -- terminal --- microexpansion/machines.lua +-- microexpansion/modules/storage/terminal.lua local me = microexpansion local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false @@ -11,16 +11,16 @@ local function chest_formspec(pos, start_id, listname, page_max, q) local page_number = "" local buttons = "" local query = q or "" - local net,cp = me.get_connected_network(pos) + local net,cpos = me.get_connected_network(pos) - if cp then + if cpos then if listname and net:get_item_capacity() > 0 then local ctrlinvname = net:get_inventory_name() if listname == "main" then - list = "list[detached:"..ctrlinvname..";" - .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + list = "list[detached:"..ctrlinvname..";" + .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" else - list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" + list = "list[context;" .. listname .. ";0,0.3;8,4;" .. (start_id - 1) .. "]" end if minetest.get_modpath("i3") then list = list .. [[ @@ -82,15 +82,15 @@ end local function update_chest(pos,_,ev) --for now all events matter - - local network = me.get_connected_network(pos) + + local net = me.get_connected_network(pos) local meta = minetest.get_meta(pos) - if network == nil then + if net == nil then meta:set_int("page", 1) meta:set_string("formspec", chest_formspec(pos, 1)) return end - local size = network:get_item_capacity() + local size = net:get_item_capacity() local page_max = me.int_to_pagenum(size) + 1 meta:set_string("inv_name", "main") @@ -120,7 +120,7 @@ term_recipe = { } end --- [me chest] Register node +-- [me terminal] Register node me.register_node("term", { description = "ME Terminal", usedfor = "Can interact with storage cells in ME networks", @@ -148,7 +148,7 @@ me.register_node("term", { own_inv:set_size("src", 1) local net = me.get_connected_network(pos) - me.send_event(pos,"connect",{net=net}) + me.send_event(pos, "connect", {net=net}) if net then update_chest(pos) end @@ -162,7 +162,7 @@ me.register_node("term", { minetest.record_protection_violation(pos, name) return false end - local net,cp = me.get_connected_network(pos) + local net,cpos = me.get_connected_network(pos) if not net then return true end @@ -172,9 +172,9 @@ me.register_node("term", { me.send_event(pos, "disconnect") end, allow_metadata_inventory_put = function(pos, _, _, stack, player) - local network = me.get_connected_network(pos) - if network then - if network:get_access_level(player) < access_level.interact then + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then return 0 end elseif minetest.is_protected(pos, player) then @@ -193,9 +193,9 @@ me.register_node("term", { net:set_storage_space(true) end, allow_metadata_inventory_take = function(pos,_,_,stack, player) - local network = me.get_connected_network(pos) - if network then - if network:get_access_level(player) < access_level.interact then + local net = me.get_connected_network(pos) + if net then + if net:get_access_level(player) < access_level.interact then return 0 end elseif minetest.is_protected(pos, player) then @@ -248,15 +248,15 @@ me.register_node("term", { after_place_node = pipeworks_enabled and pipeworks.after_place, after_dig_node = pipeworks_enabled and pipeworks.after_dig, on_receive_fields = function(pos, _, fields, sender) - local net,cp = me.get_connected_network(pos) + local net,cpos = me.get_connected_network(pos) if net then - if cp then + if cpos then me.log("network and ctrl_pos","info") else me.log("network but no ctrl_pos","warning") end else - if cp then + if cpos then me.log("no network but ctrl_pos","warning") else me.log("no network and no ctrl_pos","info") @@ -267,7 +267,7 @@ me.register_node("term", { local inv_name = meta:get_string("inv_name") local own_inv = meta:get_inventory() local ctrl_inv - if cp then + if cpos then ctrl_inv = net:get_inventory() else me.log("no network connected","warning") From 6be657da4a1f7422c27dfa7d832fb9d9f01d1465 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sat, 10 Feb 2024 17:51:23 -0800 Subject: [PATCH 19/36] Reduce the odds of creating a singularity to something kinda rare. --- modules/quantum/matter_condenser.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/quantum/matter_condenser.lua b/modules/quantum/matter_condenser.lua index b840fcb..4413526 100644 --- a/modules/quantum/matter_condenser.lua +++ b/modules/quantum/matter_condenser.lua @@ -1,5 +1,5 @@ -- matter condenser --- machines/matter_condenser.lua +-- microexpansion/modules/quantum/matter_condenser.lua local me = microexpansion local pipeworks_enabled = minetest.get_modpath("pipeworks") and true or false @@ -146,8 +146,9 @@ me.register_node("matter_condenser", { return end inv:set_stack("input", 1, "") - -- todo: a little fast, reduce - inv:set_stack("dst", 1, me.create_singularity()) + if math.random() < dstack:get_count()/256000 then + inv:set_stack("dst", 1, me.create_singularity()) + end end, tube = { can_insert = function(pos, _, stack) --pos, node, stack, direction From 8a3b409e5dd540c93de0e0f0d8a9de98ff6bf616 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 11 Feb 2024 09:39:45 -0800 Subject: [PATCH 20/36] Fix remote GUI clearing. --- modules/storage/remote.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index 2378aad..f6270d8 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -261,6 +261,7 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) toolmeta.page = page toolmeta.inv_name = "main" toolmeta.query = "" + toolstack:get_meta():set_string("query", "") toolmeta.crafts = "false" toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 update_search = true From 689812ee29656bc9871b34632856afb5cadca6f7 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 11 Feb 2024 10:28:02 -0800 Subject: [PATCH 21/36] Singularities that are not entangled are not useful to have. --- modules/quantum/matter_condenser.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/quantum/matter_condenser.lua b/modules/quantum/matter_condenser.lua index 4413526..e185bb1 100644 --- a/modules/quantum/matter_condenser.lua +++ b/modules/quantum/matter_condenser.lua @@ -8,7 +8,7 @@ local access_level = microexpansion.constants.security.access_levels me.register_item("singularity", { description = "Singularity", usedfor = "used to link quantum rings", - not_in_creative_inventory = 1, + groups = {not_in_creative_inventory = 1}, }) function me.create_singularity() From 4de901702cd80dce7b3dd45ffa2268fb3e01315a Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 11 Feb 2024 12:40:12 -0800 Subject: [PATCH 22/36] Don't ignore remove_item. --- modules/storage/cterminal.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 8086023..665bda7 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -581,8 +581,7 @@ me.register_node("cterminal", { local stack = linv:get_stack(from_list, from_index) count = math.min(count, stack:get_stack_max()) stack:set_count(count) - me.remove_item(net, inv, "main", stack) - return count + return me.remove_item(net, inv, "main", stack):get_count() end if to_list == "output" then local linv = meta:get_inventory() From 008e9041b6c16e16a737ae1194ed8533a7cef891 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Mon, 12 Feb 2024 16:03:38 -0800 Subject: [PATCH 23/36] Also return the slot. This allows higher performance api. --- modules/network/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/network/init.lua b/modules/network/init.lua index 48cc5e0..efd5583 100644 --- a/modules/network/init.lua +++ b/modules/network/init.lua @@ -46,6 +46,7 @@ function me.insert_item(stack, net, inv, listname) return inv:add_item(listname, stack) end local to_insert = type(stack) == "userdata" and stack or ItemStack(stack) + local slot local found = false for i = 0, inv:get_size(listname) do local inside = inv:get_stack(listname, i) @@ -59,6 +60,7 @@ function me.insert_item(stack, net, inv, listname) print("stack is now " .. inside:to_string()) end inv:set_stack(listname, i, inside) + slot = i found = true break; end From 9541750d08b1ad77d7362e7ed1bf02079ef1ae2a Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Mon, 12 Feb 2024 16:09:02 -0800 Subject: [PATCH 24/36] Harden the crafting terminal and the remote. --- modules/storage/cterminal.lua | 177 ++++++++++++++++++---------------- modules/storage/remote.lua | 177 +++++++++++++++++++++++++++++++--- 2 files changed, 258 insertions(+), 96 deletions(-) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 665bda7..6947c99 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -165,10 +165,9 @@ function me.get_craft(pos, inventory, hash) end -- From pipeworks/autocrafter.lua --- note, that this function assumes allready being updated to virtual items +-- note, that this function assumes already being updated to virtual items -- and doesn't handle recipes with stacksizes > 1 -local function after_recipe_change(pos, inventory) - local meta = minetest.get_meta(pos) +function me.after_recipe_change(pos, inventory) -- if we emptied the grid, there's no point in keeping it running or cached if inventory:is_empty("recipe") then autocrafterCache[minetest.hash_node_position(pos)] = nil @@ -375,10 +374,10 @@ end -- net.autocrafters should not be allowed as the output in that case -- is virtual. It can be removed if rinv:"output" has the item iff -- that item is removed from the autocrafter's output. -local function on_output_change(pos, linv, stack) +function me.network:on_output_change(pos, linv, stack) local name = stack:get_name() -- me.log("PROCESS: "..name.." was found0", "error") - local net = me.get_connected_network(pos) + local net = self local inv = net:get_inventory() local has_enough = true local function clear_recipe() @@ -389,6 +388,7 @@ local function on_output_change(pos, linv, stack) -- full, no room to remove has_enough = false elseif prev and prev:get_name() ~= "" and me.insert_item(prev, net, inv, "main"):get_count() > 0 then + -- These all can fault, see minetest.after in network.lua net:set_storage_space(true) -- Don't have to worry about this happening until minetest is fully multithreaded has_enough = false @@ -483,10 +483,61 @@ local function on_output_change(pos, linv, stack) width_idx = (width_idx < 3) and (width_idx + 1) or 1 end -- we'll set the output slot in after_recipe_change to the actual result of the new recipe - after_recipe_change(pos, linv) + me.after_recipe_change(pos, linv) return 0 end +function me.network:take_output(pos, linv, inv) + local replace = true + -- This assumes that all inputs are only just 1 item, always true? + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and inp:get_name() ~= "" then + local consume = ItemStack(inp:get_name()) + replace = replace and (inp:get_count() > 1 or inv:contains_item("main", consume)) + end + end + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and inp:get_name() ~= "" then + if inp:get_count() == 1 then + if inv:contains_item("main", inp) then + local r = me.remove_item(net, inv, "main", inp) + if r:get_count() ~= 1 then + linv:set_stack("recipe", i, ItemStack("")) + replace = false + end + else + linv:set_stack("recipe", i, ItemStack("")) + replace = false + end + else + local stack_copy = ItemStack(inp) + stack_copy:set_count(inp:get_count()-1) + linv:set_stack("recipe", i, stack_copy) + end + end + end + -- deal with replacements + local hash = minetest.hash_node_position(pos) + local craft = autocrafterCache[hash] or me.get_craft(pos, linv, hash) + for i = 1, 9 do + if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count() + or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name()) + and not craft.decremented_input.items[i]:is_empty() then + local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main") + if not leftovers:is_empty() then + me.leftovers(pos, leftovers) + end + end + if replace then + linv:set_stack("output", 1, craft.output.item) + else + linv:set_list("output", {}) + end + end +end + local cterm_recipe = nil if minetest.get_modpath("mcl_core") then cterm_recipe = { @@ -575,7 +626,6 @@ me.register_node("cterminal", { --me.log("Allow a move from "..from_list.." to "..to_list, "error") local meta = minetest.get_meta(pos) if to_list == "recipe" and from_list == "search" then - local net = me.get_connected_network(pos) local linv = meta:get_inventory() local inv = net:get_inventory() local stack = linv:get_stack(from_list, from_index) @@ -583,16 +633,25 @@ me.register_node("cterminal", { stack:set_count(count) return me.remove_item(net, inv, "main", stack):get_count() end + if from_list == "output" then + local linv = minetest.get_meta(pos):get_inventory() + -- an output with no recipe is a virtual item and can't be taken, + -- but if there is a recipe, then it can be taken. + local was_empty = true + for i = 1, 9 do + was_empty = was_empty and linv:get_stack("recipe", i):is_empty() + end + if was_empty then return 0 end + end if to_list == "output" then local linv = meta:get_inventory() local stack = linv:get_stack(from_list, from_index) - return on_output_change(pos, linv, stack) + return net:on_output_change(pos, linv, stack) end - if from_list == "crafts" then + if from_list == "crafts" or from_list == "search" then return 0 end if to_list == "search" then - local net = me.get_connected_network(pos) local linv = meta:get_inventory() local inv = net:get_inventory() local stack = linv:get_stack(from_list, from_index) @@ -619,10 +678,19 @@ me.register_node("cterminal", { local count = stack:get_count() if listname == "search" or listname == "recipe" then count = math.min(count, stack:get_stack_max()) - end - if listname == "crafts" then + elseif listname == "crafts" then return 0 end + if listname == "output" then + local linv = minetest.get_meta(pos):get_inventory() + -- an output with no recipe is a virtual item and can't be taken, + -- but if there is a recipe, then it can be taken. + local was_empty = true + for i = 1, 9 do + was_empty = was_empty and linv:get_stack("recipe", i):is_empty() + end + if was_empty then return 0 end + end --[[if listname == "main" then -- This should be unused, we don't have a local inventory called main. local inv = net:get_inventory() @@ -645,9 +713,8 @@ me.register_node("cterminal", { end if listname == "output" then local linv = minetest.get_meta(pos):get_inventory() - return on_output_change(pos, linv, stack) - elseif listname == "search" or listname == "main" then - local net = me.get_connected_network(pos) + return net:on_output_change(pos, linv, stack) + elseif listname == "search" or listname == "crafts" then local inv = net:get_inventory() -- TODO: Check full inv, should be fixed now, confirm. local leftovers = me.insert_item(stack, net, inv, "main") @@ -659,19 +726,7 @@ me.register_node("cterminal", { -- me.dbg() if listname == "recipe" then local linv = minetest.get_meta(pos):get_inventory() - after_recipe_change(pos, linv) - elseif listname == "search" or listname == "main" then - -- done above in allow, nothing left to do here - elseif listname == output then - -- done above - else - local net = me.get_connected_network(pos) - local inv = net:get_inventory() - local leftovers = me.insert_item(stack, net, inv, "main") - if not leftovers:is_empty() then - me.leftovers(net.controller_pos, leftovers) - end - net:set_storage_space(true) + me.after_recipe_change(pos, linv) end end, on_metadata_inventory_take = function(pos, listname, index, stack) @@ -680,62 +735,14 @@ me.register_node("cterminal", { local linv = minetest.get_meta(pos):get_inventory() local num_left = linv:get_stack("output", 1):get_count() -- We only need to consume the recipe if there are no more items - -- if num_left ~= 0 then return end - if num_left > 1 then return end + if num_left > 0 then return end local net = me.get_connected_network(pos) local inv = net:get_inventory() - local replace = true - -- This assumes that all inputs are only just 1 item, always true? - for i = 1, 9 do - local inp = linv:get_stack("recipe", i) - if inp and inp:get_name() ~= "" then - local consume = ItemStack(inp:get_name()) - replace = replace and (inp:get_count() > 1 or inv:contains_item("main", consume)) - end - end - for i = 1, 9 do - local inp = linv:get_stack("recipe", i) - if inp and inp:get_name() ~= "" then - if inp:get_count() == 1 then - if inv:contains_item("main", inp) then - local r = me.remove_item(net, inv, "main", inp) - if r:get_count() ~= 1 then - linv:set_stack("recipe", i, ItemStack("")) - replace = false - end - else - linv:set_stack("recipe", i, ItemStack("")) - replace = false - end - else - local stack_copy = ItemStack(inp) - stack_copy:set_count(inp:get_count()-1) - linv:set_stack("recipe", i, stack_copy) - end - end - end - -- deal with replacements - local hash = minetest.hash_node_position(pos) - local craft = autocrafterCache[hash] or me.get_craft(pos, linv, hash) - for i = 1, 9 do - if (craft.decremented_input.items[i]:get_count() ~= linv:get_stack("recipe", i):get_count() - or craft.decremented_input.items[i]:get_name() ~= linv:get_stack("recipe", i):get_name()) - and not craft.decremented_input.items[i]:is_empty() then - local leftovers = me.insert_item(craft.decremented_input.items[i], net, inv, "main") - if not leftovers:is_empty() then - me.leftovers(pos, leftovers) - end - end - if replace then - linv:set_stack("output", 1, craft.output.item) - else - linv:set_list("output", {}) - end - end + net:take_output(pos, linv, inv) elseif listname == "recipe" then local linv = minetest.get_meta(pos):get_inventory() - after_recipe_change(pos, linv) - elseif listname ~= "main" then + me.after_recipe_change(pos, linv) + elseif listname == "crafts" or listname == "search" then local net = me.get_connected_network(pos) local inv = net:get_inventory() me.remove_item(net, inv, "main", stack) @@ -745,7 +752,15 @@ me.register_node("cterminal", { --me.log("A move from "..from_list.." to "..to_list, "error") if to_list == "recipe" or from_list == "recipe" then local inv = minetest.get_meta(pos):get_inventory() - after_recipe_change(pos, inv) + me.after_recipe_change(pos, inv) + elseif from_listname == "output" then + local linv = minetest.get_meta(pos):get_inventory() + local num_left = linv:get_stack("output", 1):get_count() + -- We only need to consume the recipe if there are no more items + if num_left > 0 then return end + local net = me.get_connected_network(pos) + local inv = net:get_inventory() + net:take_output(pos, linv, inv) end end, tube = { diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index f6270d8..c5fd60a 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -1,4 +1,5 @@ local me = microexpansion +local access_level = microexpansion.constants.security.access_levels --technic = rawget(_G, "technic") or {} @@ -61,6 +62,7 @@ local function chest_formspec(s, pos, start_id, listname) listring[current_player;main] listring[current_player;output] listring[current_player;main] + listring[current_player;me_search] ]] buttons = [[ button[3.56,4.35;0.9,0.9;tochest;To Drive] @@ -120,6 +122,14 @@ minetest.register_tool("microexpansion:remote", { local pos = pointed_thing.under pos.z = pos.z - 1 local net,cpos = me.get_connected_network(pos) + if net then + if net:get_access_level(user) < access_level.interact then + return 0 + end + elseif minetest.is_protected(pos, user) then + minetest.record_protection_violation(pos, user) + return 0 + end if net then if not net:powered(user:get_player_name()) then return end minetest.chat_send_player(user:get_player_name(), "Connected to ME network, right-click to use.") @@ -139,8 +149,9 @@ minetest.register_tool("microexpansion:remote", { if not user or not user:is_player() or user.is_fake_player then return end local toolmeta = get_metadata(toolstack) local pos = toolmeta.terminal + local playername = user:get_player_name() if not pos then - minetest.chat_send_player(user:get_player_name(), "Left-click on ME block to connect to ME network.") + minetest.chat_send_player(playername, "Left-click on ME block to connect to ME network.") return end local net,cpos = me.get_connected_network(pos) @@ -153,7 +164,7 @@ minetest.register_tool("microexpansion:remote", { end if toolmeta.charge < charge_to_take then - minetest.chat_send_player(user:get_player_name(), "No power left, recharge in technic battery.") + minetest.chat_send_player(playername, "No power left, recharge in technic battery.") return end @@ -163,7 +174,7 @@ minetest.register_tool("microexpansion:remote", { technic.set_RE_wear(toolstack, toolmeta.charge, technic.power_tools[toolstack:get_name()]) end - if net and not net:powered(user:get_player_name()) then return end + if net and not net:powered(playername) then return end local page = toolmeta.page local inv_name = toolmeta.inv_name @@ -182,11 +193,12 @@ minetest.register_tool("microexpansion:remote", { inv = own_inv end if net then + user:get_meta():set_string("controller_pos", minetest.pos_to_string(net.controller_pos)) toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 toolstack:set_metadata(minetest.serialize(toolmeta)) end - minetest.show_formspec(user:get_player_name(), "microexpansion:remote_control", + minetest.show_formspec(playername, "microexpansion:remote_control", chest_formspec(toolmeta, pos, page, inv_name)) return toolstack @@ -224,6 +236,7 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) local page = toolmeta.page local did_update = false local update_search = false + local to_clear = false local do_autocraft = false for field, value in pairs(fields) do --me.log("REMOTE: form "..field.." value "..value, "error") @@ -255,17 +268,7 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) update_search = true end elseif field == "clear" then - own_inv:set_size("me_search", 0) - own_inv:set_size("me_crafts", 0) - page = 1 - toolmeta.page = page - toolmeta.inv_name = "main" - toolmeta.query = "" - toolstack:get_meta():set_string("query", "") - toolmeta.crafts = "false" - toolmeta.page_max = math.floor(inv:get_size(inv_name) / 32) + 1 - update_search = true - did_update = true + to_clear = true elseif field == "tochest" then elseif field == "desc" then toolmeta.desc = value @@ -284,6 +287,20 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) end end + if to_clear then + own_inv:set_size("me_search", 0) + own_inv:set_size("me_crafts", 0) + page = 1 + toolmeta.page = page + toolmeta.inv_name = "main" + toolmeta.query = "" + toolstack:get_meta():set_string("query", "") + toolmeta.crafts = "false" + toolmeta.page_max = math.floor(ctrl_inv:get_size(inv_name) / 32) + 1 + update_search = true + did_update = true + end + if do_autocraft then local count = tonumber(toolmeta.autocraft) me.autocraft(me.autocrafterCache, pos, net, own_inv, ctrl_inv, count) @@ -365,6 +382,136 @@ minetest.register_craft({ } }) +minetest.register_allow_player_inventory_action( + function(player, action, linv, info) + -- linv:get_list(info.from_list)[info.from_index] + local from_list = info.from_list + local to_list = info.to_list + if action == "move" then + if (to_list == "recipe" or to_list == "main") and from_list == "me_search" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + local inv = net:get_inventory() + local stack = linv:get_stack(from_list, info.from_index) + local count = math.min(info.count, stack:get_stack_max()) + stack:set_count(count) + return me.remove_item(net, inv, "main", stack):get_count() + end + if to_list == "output" then + --local stack = linv:get_list(info.from_list)[info.from_index] + local stack = linv:get_stack(from_list, info.from_index) + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + return net:on_output_change(cpos, linv, stack) + end + if from_list == "me_crafts" or from_list == "me_search" then + return 0 + end + if from_list == "output" then + -- an output with no recipe is a virtual item and can't be taken, + -- but if there is a recipe, then it can be taken. + local was_empty = true + for i = 1, 9 do + was_empty = was_empty and linv:get_stack("recipe", i):is_empty() + end + if was_empty then return 0 end + end + if to_list == "me_search" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + local inv = net:get_inventory() + --local stack = linv:get_stack(from_list, info.from_index) + local stack = linv:get_list(info.from_list)[info.from_index] + stack:set_count(info.count) + -- meta:set_string("infotext", "allow moving: "..stack:get_name()) + -- TODO: Check capacity? Test. + local leftovers = me.insert_item(stack, net, inv, "main") + return info.count - leftovers:get_count() + end + elseif action == "take" then + local stack = info.stack + local count = stack:get_count() + local listname = info.listname + if listname == "me_search" or listname == "recipe" then + count = math.min(count, stack:get_stack_max()) + elseif listname == "output" then + -- an output with no recipe is a virtual item and can't be taken, + -- but if there is a recipe, then it can be taken. + local was_empty = true + for i = 1, 9 do + was_empty = was_empty and linv:get_stack("recipe", i):is_empty() + end + if was_empty then return 0 end + end + if listname == "me_crafts" then + return 0 + end + return count + elseif action == "put" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + local listname = info.listname + if listname == "output" then + net:on_output_change(cpos, linv, info.stack) + elseif listname == "me_search" or listname == "me_crafts" then + local inv = net:get_inventory() + -- TODO: Check full inv, should be fixed now, confirm. + local leftovers = me.insert_item(stack, net, inv, "main") + return stack:get_count() - leftovers:get_count() + end + end + if info.stack then + return info.stack:get_count() + end + return info.count + end) + +minetest.register_on_player_inventory_action( + function(player, action, linv, info) + -- linv:get_list(info.from_list)[info.from_index] + local from_list = info.from_list + local to_list = info.to_list + if action == "move" then + if from_list == "output" then + local num_left = linv:get_stack("output", 1):get_count() + -- We only need to consume the recipe if there are no more items + if num_left > 0 then return end + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + local inv = net:get_inventory() + net:take_output(cpos, linv, inv) + end + if to_list == "recipe" or from_list == "recipe" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local net = me.get_network(cpos) + me.after_recipe_change(cpos, linv) + end + elseif action == "take" then + local stack = info.stack + local count = stack:get_count() + local listname = info.listname + if listname == "output" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + local num_left = linv:get_stack("output", 1):get_count() + -- We only need to consume the recipe if there are no more items + if num_left > 0 then return end + local net = me.get_network(cpos) + local inv = net:get_inventory() + net:take_output(cpos, linv, inv) + elseif listname == "recipe" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + me.after_recipe_change(cpos, linv) + elseif listname == "me_crafts" or listname == "me_search" then + end + elseif action == "put" then + local listname = info.listname + if listname == "recipe" then + local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) + me.after_recipe_change(cpos, linv) + end + end + end) + if technic then technic.register_power_tool("microexpansion:remote", 450000) end From e1bc6d975d0437fc04da7ddb8936f799d214ea2d Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Tue, 13 Feb 2024 14:24:24 -0800 Subject: [PATCH 25/36] Harden autocrafting. --- modules/network/autocraft.lua | 51 ++++++++++++---------- modules/storage/cterminal.lua | 21 ++++----- modules/storage/interface.lua | 61 ++++++++++----------------- modules/storage/pipeworks-interop.lua | 10 +++-- modules/storage/remote.lua | 4 +- 5 files changed, 69 insertions(+), 78 deletions(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index 533536e..6d5ffac 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -57,7 +57,7 @@ end -- time. todo: this solves the end and the outputs, but not the -- inputs. They can be overloaded and we might have to delay putting -- things into the machine. -function me.reserve(net, pos, original_start, length) +function me.reserve(net, hash, original_start, length) if not net.pending then net.pending = {} net.pending.time = {} @@ -65,11 +65,11 @@ function me.reserve(net, pos, original_start, length) if not net.pending.busy then net.pending.busy = {} end - local free_time = net.pending.busy[pos] or 0 + local free_time = net.pending.busy[hash] or 0 local start = math.max(free_time, original_start) local ending = start + length -- inputs and outputs can collide if we run them too close - net.pending.busy[pos] = ending + 1 + net.pending.busy[hash] = ending + 0.1 return start end @@ -88,13 +88,14 @@ local function build(net, cpos, inv, name, count, stack, sink, time) -- of things, help them out. local max = me.maximums[name] if not max then - -- If no explicit max, assume this is a pipeworks autocrafter and - -- it only has 12 outputs. - max = stack:get_stack_max()*12 + -- If no explicit max, assume this is a machine with 4 stacks of outputs. + max = stack:get_stack_max()*4 end if net.process and net.process[name] then max = max * length(net.process[name]) elseif net.autocrafters[name] then + -- autocrafters from pipeworks have a 12 slot output + max = stack:get_stack_max()*12 max = max * length(net.autocrafters[name]) end --me.log("AC: max is "..max , "error") @@ -117,14 +118,16 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local step_time = next_time - time return built, step_time end - --me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error") + me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error") local dat = {} local second_output = nil - local main_action_time = count * pipeworks_craft_time + 1 + local main_action_time = count * pipeworks_craft_time + 1.1 if net.process and net.process[name] then local machines = net.process[name] for k, v in pairs(machines) do - local mname = minetest.get_node(k).name + local apos = minetest.get_position_from_hash(k) + local ipos = minetest.get_position_from_hash(v) + local mname = minetest.get_node(apos).name if not me.block_to_typename_map[mname] then -- There is no way this can be. Prune. -- Would be nice if we had a way to notice blocks going away. @@ -134,8 +137,10 @@ local function build(net, cpos, inv, name, count, stack, sink, time) end local i = #dat + 1 dat[i] = {} - dat[i].apos = k - dat[i].ipos = v + dat[i].ahash = k + dat[i].ihash = v + dat[i].apos = apos + dat[i].ipos = ipos dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory() -- todo: figure out if we should use total. if i == count then @@ -187,8 +192,10 @@ local function build(net, cpos, inv, name, count, stack, sink, time) for k, v in pairs(machines) do local i = #dat + 1 dat[i] = {} - dat[i].apos = k - dat[i].ipos = v + dat[i].ahash = k + dat[i].ihash = v + dat[i].apos = minetest.get_position_from_hash(k) + dat[i].ipos = minetest.get_position_from_hash(v) dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory() -- TODO: If we set up pipeworks ac, then we remove interface for it and craft -- it goes to ac, and dies here. Flush net.autocrafters for all the @@ -200,17 +207,13 @@ local function build(net, cpos, inv, name, count, stack, sink, time) dat[i].ostack = dat[i].rinv:get_stack("output", 1) if dat[i].ostack:get_name() ~= name then -- invalidate it - net.autocrafters[name][dat[i].apos] = nil + net.autocrafters[name][dat[i].ahash] = nil --me.log("invalidating autocrafter for "..name, "error") table.remove(dat) if #dat == 0 then return end end - -- todo: figure out if we should use total. Test with crafting planks. - if i == total then - break - end end -- Consider looking up the recipe and finding the replacements that way. if name == "technic:copper_coil" or name == "technic:control_logic_unit" @@ -219,8 +222,11 @@ local function build(net, cpos, inv, name, count, stack, sink, time) end local craft_count = dat[1].ostack:get_count() local total = math.ceil(count/craft_count) + while #dat > total do + table.remove(dat) + end local subtotal = math.floor((total+#dat-1)/#dat) - main_action_time = subtotal * pipeworks_craft_time + 1 + main_action_time = subtotal * pipeworks_craft_time + 1.1 else me.log("can't craft a "..name, "error") return @@ -260,7 +266,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local replace = true local next_time = {} for i = 1, #dat do - next_time[i] = me.reserve(net, dat[i].apos, time, main_action_time) + next_time[i] = me.reserve(net, dat[i].ahash, time, main_action_time) end --me.log("RESERVE: "..name.." stime "..time.." step "..main_action_time.." reserve "..next_time[1], "error") --me.log("PREP: pre count is "..count, "error") @@ -299,6 +305,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat)) leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count() end + stack = ItemStack(stack) stack:set_count(leftovers) --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") inv:set_stack("ac", slot, stack) @@ -321,7 +328,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) -- I think this works, but it is slightly wasteful, but in a good way as -- 10 on 10 machines will each craft 1 on craft_count 2 item yielding 10 extra. local subcount = math.floor((count+i-1)/#dat) - local inner_istack = istack + local inner_istack = ItemStack(istack) inner_istack:set_count(subcount) local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, dat[i].isink, time) if built then @@ -399,7 +406,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local action_time_step = main_action_time local action = function(net) --me.log("ACTION: post craft for "..stack:get_name(), "error") - local inner_stack = stack + local inner_stack = ItemStack(stack) -- todo: prove the below is correct. -- See extra below for how I think it fails. inner_stack:set_count(craft_count*math.floor((total+i-1)/#dat)) diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 6947c99..5bcfae1 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -406,7 +406,8 @@ function me.network:on_output_change(pos, linv, stack) if net and net.process[name] then -- me.log("PROCESS: "..name.." was found1", "error") has_enough = clear_recipe() - local pos,ipos = next(net.process[name]) + local hash,ihash = next(net.process[name]) + local pos = minetest.get_position_from_hash(hash) if has_enough and pos then -- me.log("PROCESS: "..name.." was found2", "error") local inputs = me.find_by_output(name) @@ -434,15 +435,11 @@ function me.network:on_output_change(pos, linv, stack) elseif net and net.autocrafters[name] then has_enough = clear_recipe() if has_enough then - local pos,ipos = next(net.autocrafters[name]) - if pos then - local rinv = minetest.get_meta(pos):get_inventory() - stack = ItemStack(rinv:get_stack("output", 1)) - linv:set_stack("output", 1, stack) - else - -- me.log("pos in autocrafters was missing", "error") - linv:set_stack("output", 1, ItemStack()) - end + local hash,ihash = next(net.autocrafters[name]) + local pos = minetest.get_position_from_hash(hash) + local rinv = minetest.get_meta(pos):get_inventory() + stack = ItemStack(rinv:get_stack("output", 1)) + linv:set_stack("output", 1, stack) else linv:set_stack("output", 1, ItemStack()) end @@ -874,11 +871,11 @@ me.register_node("cterminal", { if not net.process then net:reload_network() end - for name,pos in pairs(net.autocrafters) do + for name,hash in pairs(net.autocrafters) do tab[#tab + 1] = ItemStack(name) end tab[#tab + 1] = ItemStack("") - for name,pos in pairs(net.process) do + for name,hash in pairs(net.process) do tab[#tab + 1] = ItemStack(name) end end diff --git a/modules/storage/interface.lua b/modules/storage/interface.lua index 76ff6dd..9a56f89 100644 --- a/modules/storage/interface.lua +++ b/modules/storage/interface.lua @@ -20,12 +20,12 @@ end function me.walk_connected(pos) local nodes = {} local visited = {} - visited[pos.x] = {} - visited[pos.x][pos.y] = {} - visited[pos.x][pos.y][pos.z] = true - local to_visit = {pos} + local hash = minetest.hash_node_position(pos) + visited[hash] = true + local to_visit = {hash} while #to_visit > 0 do - local pos = table.remove(to_visit) + local hash = table.remove(to_visit) + local pos = minetest.get_position_from_hash(hash) local adjacent = { {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}, @@ -35,19 +35,15 @@ function me.walk_connected(pos) {x=pos.x, y=pos.y, z=pos.z-1}, } for _,apos in pairs(adjacent) do - if not visited[apos.x] then - visited[apos.x] = {} - end - if not visited[apos.x][apos.y] then - visited[apos.x][apos.y] = {} - end - if visited[apos.x][apos.y][apos.z] ~= true then - visited[apos.x][apos.y][apos.z] = true + local ahash = minetest.hash_node_position(apos) + if visited[ahash] ~= true then + visited[ahash] = true + local apos = minetest.get_position_from_hash(ahash) local napos = minetest.get_node(apos) local nn = napos.name if can_connect(nn) then table.insert(nodes, {pos=apos, name=nn}) - table.insert(to_visit, apos) + table.insert(to_visit, ahash) end end end @@ -167,7 +163,9 @@ function me.reload_interface(net, pos, doinventories) net.process[name] = {} end -- me.log("INT: registering "..name.." for the "..node.name, "error") - net.process[name][n.pos] = pos + local nhash = minetest.hash_node_position(n.pos) + local hash = minetest.hash_node_position(pos) + net.process[name][nhash] = hash end else me.reload_inventory(name, net, ctrl_inv, int_meta, n, pos, doinventories) @@ -267,37 +265,24 @@ me.register_node("interface", { end net:update_counts() - -- pos is a table and does not have value semantics. if net.autocrafters_by_pos then - for k, v in pairs(net.autocrafters_by_pos) do - if k.x == pos.x and k.y == pos.y and k.z == pos.z then - pos = k - break - end - end - if net.autocrafters_by_pos[pos] then - for name, apos in pairs(net.autocrafters_by_pos[pos]) do + local hash = minetest.hash_node_position(pos) + if net.autocrafters_by_pos[hash] then + for name, ahash in pairs(net.autocrafters_by_pos[hash]) do -- deindex these upon removal of the interface controlling them - net.autocrafters_by_pos[pos][name] = nil - net.autocrafters[name][apos] = nil + net.autocrafters_by_pos[hash][name] = nil + net.autocrafters[name][ahash] = nil end end end if net.process then - -- todo: This is a little slow, speed it up? Interface removal is infrequent. - for _, v in pairs(net.process) do - for k, ipos in pairs(v) do - if ipos.x == pos.x and ipos.y == pos.y and ipos.z == pos.z then - pos = ipos - break - end - end - end + local hash = minetest.hash_node_position(pos) for name, v in pairs(net.process) do - for apos, ipos in pairs(v) do - if ipos == pos then + -- todo: This is a little slow, we could have the inverse map and not walk + for ahash, ihash in pairs(v) do + if ihash == hash then --me.log("INTERFACE: killing a mchine for "..name, "error") - net.process[name][apos] = nil + net.process[name][ahash] = nil end end end diff --git a/modules/storage/pipeworks-interop.lua b/modules/storage/pipeworks-interop.lua index c982097..abd89e0 100644 --- a/modules/storage/pipeworks-interop.lua +++ b/modules/storage/pipeworks-interop.lua @@ -13,13 +13,15 @@ me.register_inventory("pipeworks:autocrafter", function(net, ctrl_inv, int_meta, -- register the crafted items so the autocrafter can use them local craft = rinv:get_stack("output", 1) if not craft:is_empty() then - if not net.autocrafters_by_pos[pos] then - net.autocrafters_by_pos[pos] = {} + local hash = minetest.hash_node_position(pos) + if not net.autocrafters_by_pos[hash] then + net.autocrafters_by_pos[hash] = {} end - net.autocrafters_by_pos[pos][craft:get_name()] = n.pos + local nhash = minetest.hash_node_position(n.pos) + net.autocrafters_by_pos[hash][craft:get_name()] = nhash if not net.autocrafters[craft:get_name()] then net.autocrafters[craft:get_name()] = {} end - net.autocrafters[craft:get_name()][n.pos] = pos + net.autocrafters[craft:get_name()][nhash] = hash end end) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index c5fd60a..38c46ff 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -314,11 +314,11 @@ minetest.register_on_player_receive_fields(function(user, formname, fields) if not net.process then net:reload_network() end - for name,pos in pairs(net.autocrafters) do + for name,hash in pairs(net.autocrafters) do tab[#tab + 1] = ItemStack(name) end tab[#tab + 1] = ItemStack("") - for name,pos in pairs(net.process) do + for name,hash in pairs(net.process) do tab[#tab + 1] = ItemStack(name) end end From c0b82b445b71ea77b6715096a5b13982a01842e2 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Wed, 14 Feb 2024 11:32:01 -0800 Subject: [PATCH 26/36] Don't network search on non-me stuff. --- modules/storage/remote.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index 38c46ff..2bf7ba9 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -449,11 +449,12 @@ minetest.register_allow_player_inventory_action( return count elseif action == "put" then local cpos = minetest.string_to_pos(player:get_meta():get_string("controller_pos")) - local net = me.get_network(cpos) local listname = info.listname if listname == "output" then + local net = me.get_network(cpos) net:on_output_change(cpos, linv, info.stack) elseif listname == "me_search" or listname == "me_crafts" then + local net = me.get_network(cpos) local inv = net:get_inventory() -- TODO: Check full inv, should be fixed now, confirm. local leftovers = me.insert_item(stack, net, inv, "main") From 81e9478f9b51a02963d83c4b6b72c20f834bd1cd Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 18 Feb 2024 09:55:49 -0800 Subject: [PATCH 27/36] Add annihilation plane. --- modules/item_transfer/annihilation.lua | 109 +++++++++++++++++++++++++ modules/item_transfer/init.lua | 1 + 2 files changed, 110 insertions(+) create mode 100644 modules/item_transfer/annihilation.lua diff --git a/modules/item_transfer/annihilation.lua b/modules/item_transfer/annihilation.lua new file mode 100644 index 0000000..7df3ae2 --- /dev/null +++ b/modules/item_transfer/annihilation.lua @@ -0,0 +1,109 @@ +-- microexpansion/modules/item_transfer/annihilation.lua + +local me = microexpansion +local item_transfer = me.item_transfer +local access_level = microexpansion.constants.security.access_levels + +local importer_timer = function(pos, elapsed) + local net, cp = me.get_connected_network(pos) + if not net then + return false + end + local node = minetest.get_node(pos) + local target = vector.add(pos, microexpansion.facedir_to_right_dir(node.param2)) + --TODO: allow setting list with upgrade + local n = minetest.get_node(target) + if n and n.name ~= "air" and n.name ~= "default:water_flowing" and n.name ~= "default:lava_flowing" + and n.name ~= "default:river_water_flowing" then + minetest.remove_node(target) + local own_inv = minetest.get_meta(pos):get_inventory() + local upgrades = me.count_upgrades(own_inv) + local count = math.min(net:get_inventory_space(),math.pow(2, upgrades.bulk or 0)) + local list = "main" + local inv = own_inv + if count <= 0 then + return true + end + if n.name ~= "default:stone" then + count = 1 + end + local stack = ItemStack(n.name) + stack:set_count(count) + inv:add_item(list, stack) + local import_filter = function(stack) + local stack_name = stack:get_name() + if upgrades.filter then + return not own_inv:contains_item("filter",stack:peek_item()) + end + return false + end + me.move_inv(net, {inv=inv, name=list}, {inv=net:get_inventory(), name="main", huge=true}, count, import_filter) + net:set_storage_space(true) + end + return true +end + +-- [MicroExpansion Annihilation] Register node +item_transfer.register_io_device("annihilation", { + description = "ME Annihilation Plane", + usedfor = "Annihilates items and imports them into ME Networks", + tiles = { + "importer", + "importer", + "interface", + "cable", + "microexpansion_importer.png^[transform4", + "importer", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5, -0.25, -0.25, 0.25, 0.25, 0.25}, + {0.25, -0.375, -0.375, 0.5, 0.375, 0.375}, + }, + }, + connect_sides = { "left" }, + recipe = { + { 1, { + {"", "basic_materials:ic", microexpansion.iron_ingot_ingredient }, + {"", "microexpansion:cable", "group:hoe" }, + {"", "", microexpansion.iron_ingot_ingredient }, + }, + }, + { 1, { + {"", "microexpansion:logic_chip", microexpansion.iron_ingot_ingredient }, + {"", "microexpansion:cable", "group:hoe" }, + {"", "", microexpansion.iron_ingot_ingredient }, + }, + } + }, + is_ground_content = false, + groups = { crumbly = 1 }, + on_timer = importer_timer, + on_construct = function(pos) + local own_inv = minetest.get_meta(pos):get_inventory() + own_inv:set_size("main", 1) + item_transfer.setup_io_device("ME Annihilation Plane",pos) + me.send_event(pos,"connect") + item_transfer.update_timer_based(pos) + end, + after_destruct = function(pos) + minetest.get_node_timer(pos):stop() + me.send_event(pos,"disconnect") + end, + on_metadata_inventory_put = function(pos, listname, _, stack, player) + if listname == "upgrades" then + item_transfer.setup_io_device("ME Annihilation Plane",pos) + end + end, + on_metadata_inventory_take = function(pos, listname, _, stack, player) + if listname == "upgrades" then + item_transfer.setup_io_device("ME Annihilation Plane",pos) + end + end +}) + +if me.uinv_category_enabled then + unified_inventory.add_category_item("storage", "microexpansion:annihilation") +end diff --git a/modules/item_transfer/init.lua b/modules/item_transfer/init.lua index 086c5cc..78c7087 100644 --- a/modules/item_transfer/init.lua +++ b/modules/item_transfer/init.lua @@ -22,4 +22,5 @@ dofile(module_path.."/upgrades.lua") -- Load ports dofile(module_path.."/importer.lua") dofile(module_path.."/exporter.lua") +dofile(module_path.."/annihilation.lua") --dofile(module_path.."/interface.lua") From b59cab7978cc8e43a924e9815bb6bf8800b09541 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 18 Feb 2024 11:43:11 -0800 Subject: [PATCH 28/36] Remove debugging output. --- modules/network/autocraft.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index 6d5ffac..b1433be 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -118,7 +118,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local step_time = next_time - time return built, step_time end - me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error") + --me.log("BUILD: count is "..count.." and stack size is "..stack:get_count(), "error") local dat = {} local second_output = nil local main_action_time = count * pipeworks_craft_time + 1.1 From 7c84c015782b078db2d88f24ed9fb71c2d97e5ad Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Mon, 19 Feb 2024 09:04:23 -0800 Subject: [PATCH 29/36] Ensure we don't loose any items when back pressured. --- modules/network/autocraft.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index b1433be..fecce7d 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -305,9 +305,12 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat)) leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count() end - stack = ItemStack(stack) + stack = ItemStack(name) stack:set_count(leftovers) --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") + if leftovers > 0 then + net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers.." "..name..".\n" + end inv:set_stack("ac", slot, stack) end] = name -- and then something moves the size of ac back to before we started From 5b70b5dc4bdc9f94f21dd699fddad314b7115149 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 25 Feb 2024 11:19:29 -0800 Subject: [PATCH 30/36] Make the remote of the quantum link a hair more expensive. --- modules/network/network.lua | 6 +++--- modules/storage/remote.lua | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/network/network.lua b/modules/network/network.lua index bc8fd13..fe12154 100644 --- a/modules/network/network.lua +++ b/modules/network/network.lua @@ -454,10 +454,10 @@ function network:update_demand() local source = minetest.get_meta(pos):get_string("source") if source ~= "" then local apos = vector.from_string(source) - -- for a pair, 25 to 1062 eu, rich people pay for distance. + -- for a pair, 125 to 1162 eu, rich people pay for distance. local distance = vector.distance(net.controller_pos, user:get_pos()) - local charge_to_take = math.pow(math.log(distance),2) * 10 - -- When running it take even more + local charge_to_take = math.pow(math.log(distance),2) * 10 + 100 + -- When running it takes even more demand = demand + 500 + charge_to_take/2 end else diff --git a/modules/storage/remote.lua b/modules/storage/remote.lua index 2bf7ba9..2cc8072 100644 --- a/modules/storage/remote.lua +++ b/modules/storage/remote.lua @@ -158,9 +158,9 @@ minetest.register_tool("microexpansion:remote", { local charge_to_take = 100 if net then - -- 25 to 1062 eu per operation, rich people pay for distance. + -- 150 to 1187 eu per operation, rich people pay for distance. local distance = vector.distance(net.controller_pos, user:get_pos()) - charge_to_take = math.pow(math.log(distance),2) * 10 + charge_to_take = math.pow(math.log(distance),2) * 10 + 125 end if toolmeta.charge < charge_to_take then From 83639c62f4968883c658021c3f08b905b6b2f98f Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Tue, 27 Feb 2024 15:01:08 -0800 Subject: [PATCH 31/36] Fixup autocrafting a little. --- modules/network/autocraft.lua | 135 ++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 39 deletions(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index fecce7d..3a4a704 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -81,9 +81,39 @@ local length = function(a) return count end +local defer_move = function(net, name, count, total, time, dat, inv, prepworkbits, grabbed) + local slot = inv:get_size("ac")+1 + --me.log("SLOT: inv now has "..slot.." slots.", "error") + --net.ac_status = net.ac_status .. time.." Did save "..grabbed:get_count().." "..grabbed:get_name().." into slot "..slot..".\n" + inv:set_size("ac", slot) + inv:set_stack("ac", slot, grabbed) + -- and later we do this: + local func = function(time) + --me.log("PREP: about to move "..name, "error") + local stack = inv:get_stack("ac", slot) + --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") + --net.ac_status = net.ac_status .. time.." Loading "..stack:get_count().." "..name.." from slot "..slot..".\n" + local leftovers = 0 + for i = 1, #dat do + -- todo: prove the below is correct. + -- This spreads across evenly when craft_count is > 0 (stainless, carbon steel for example). + local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat)) + leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count() + end + stack = ItemStack(name) + stack:set_count(leftovers) + --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") + if leftovers > 0 then + net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers.." "..name..".\n" + end + inv:set_stack("ac", slot, stack) + end + prepworkbits[func] = name +end + -- Testing: HV solar is realiable, big loans are screwy. -- HV batteries are realiable. -local function build(net, cpos, inv, name, count, stack, sink, time) +local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, time) -- The autocrafters nor the machines can take really large amounts -- of things, help them out. local max = me.maximums[name] @@ -107,11 +137,11 @@ local function build(net, cpos, inv, name, count, stack, sink, time) max = math.min(max, count) substack:set_count(max) local step_time - built, step_time = build(net, cpos, inv, name, max, substack, sink, time) + built, step_time = build(net, cpos, inv, name, max, substack, iprepworkbits, sink, next_time) if not built then -- we are done, can't craft, so stop else - next_time = math.max(next_time, time + step_time) + next_time = math.max(next_time, time + step_time) end count = count - max end @@ -179,7 +209,8 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local subtotal = math.floor((total+#dat-1)/#dat) --main_action_time = subtotal*1.025*dat[1].recip.time/speed + 2 -- ok --main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1 -- too fast? - main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? + --main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? + main_action_time = math.ceil((subtotal+1)*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? if second_output then second_output = ItemStack(second_output) second_output:set_count(second_output:get_count()*total) @@ -231,12 +262,58 @@ local function build(net, cpos, inv, name, count, stack, sink, time) me.log("can't craft a "..name, "error") return end + local prepworkbits = {} + local funcs = {} for i = 1, #dat do - dat[i].isink = function(sstack) + dat[i].isink = function(sstack, time) + if dat[i].slots == true then + -- if we have already run the final move, we can move in place directly. + --net.ac_status = net.ac_status .. time.." Simple sink, about to move "..sstack:get_count().." "..sstack:get_name()..".\n" + local leftovers = dat[i].rinv:add_item("src", sstack) + --net.ac_status = net.ac_status .. time.." Leftovers "..leftovers:get_count().." "..leftovers:get_name()..".\n" + return leftovers + end + local slot = inv:get_size("ac")+1 + --me.log("SLOT: inv now has "..slot.." slots.", "error") + --net.ac_status = net.ac_status .. time.." For "..count.." "..name.." for machine "..i.." created slot "..slot..".\n" + inv:set_size("ac", slot) + if not dat[i].slots then + dat[i].slots = {} + end + dat[i].slots[slot] = true --me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error") - return dat[i].rinv:add_item("src", sstack) + --net.ac_status = net.ac_status .. time.." Sinking "..sstack:get_count().." "..sstack:get_name().." for "..name.." into slot "..slot..".\n" + inv:set_stack("ac", slot, sstack) + return ItemStack() + end + funcs[i] = function(i, time) + if not dat[i].slots then + dat[i].slots = true + return + end + for slot,_ in pairs(dat[i].slots) do + local stack = inv:get_stack("ac", slot) + --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") + --net.ac_status = net.ac_status .. time.." About to move sunk "..stack:get_count().." "..stack:get_name()..", for "..count.." "..name.." from slot "..slot..".\n" + local leftovers = dat[i].rinv:add_item("src", stack) + --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") + --net.ac_status = net.ac_status .. time.." Leftovers "..leftovers:get_count().." "..leftovers:get_name()..".\n" + if not leftovers:is_empty() then + net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers:get_count().." "..name..".\n" + end + inv:set_stack("ac", slot, leftovers) + --slot = 0 + end + dat[i].slots = true end end + prepworkbits[function(time) + for i = 1, #dat do + --net.ac_status = net.ac_status .. time.." PREP2ing before "..name..".\n" + funcs[i](i, time) + --net.ac_status = net.ac_status .. time.." PREP2ing after "..name..".\n" + end + end] = name local craft_count = dat[1].ostack:get_count() -- These will be returned to the me system local extra = ItemStack(name) @@ -252,6 +329,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) for i = 1, #dat[1].recip.input do local inp = dat[1].recip.input[i] --me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error") + --net.ac_status = net.ac_status .. time.." Consuming "..inp:get_count().." "..inp:get_name()..".\n" consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() end else @@ -272,7 +350,6 @@ local function build(net, cpos, inv, name, count, stack, sink, time) --me.log("PREP: pre count is "..count, "error") -- prepwork --me.log("PREP: count is "..count, "error") - local prepworkbits = {} local previous_ac_size = inv:get_size("ac") --me.log("PREP: ac size at top is "..previous_ac_size, "error") for name, count in pairs(consume) do @@ -290,29 +367,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) if grabbed and grabbed:get_count() == count then --me.log("ac grabbed "..name, "error") net.ac_status = net.ac_status .. time.." Grabbed "..count.." "..name..".\n" - local slot = inv:get_size("ac")+1 - inv:set_size("ac", slot) - inv:set_stack("ac", slot, grabbed) - -- and later we do this: - prepworkbits[function() - --me.log("PREP: about to move "..name, "error") - local stack = inv:get_stack("ac", slot) - --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") - local leftovers = 0 - for i = 1, #dat do - -- todo: prove the below is correct. - -- This spreads across evenly when craft_count is > 0 (stainless, carbon steel for example). - local inner_stack = stack:take_item(count/total*math.floor((total+i-1)/#dat)) - leftovers = leftovers + dat[i].rinv:add_item("src", inner_stack):get_count() - end - stack = ItemStack(name) - stack:set_count(leftovers) - --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") - if leftovers > 0 then - net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers.." "..name..".\n" - end - inv:set_stack("ac", slot, stack) - end] = name + defer_move(net, name, count, total, time, dat, inv, prepworkbits, grabbed) -- and then something moves the size of ac back to before we started else --me.log("ac could not grab "..count.." "..name, "error") @@ -333,7 +388,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) local subcount = math.floor((count+i-1)/#dat) local inner_istack = ItemStack(istack) inner_istack:set_count(subcount) - local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, dat[i].isink, time) + local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, prepworkbits, dat[i].isink, time) if built then next_time[i] = math.max(next_time[i], time + step_time) final_step_time = math.max(final_step_time, step_time) @@ -345,17 +400,19 @@ local function build(net, cpos, inv, name, count, stack, sink, time) net.ac_status = net.ac_status .. time.." Craft "..count.." "..name.." in "..final_step_time.." seconds.\n" else me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error") - net.ac_status = net.ac_status .. time.." Can't craft "..count.." "..name..".\n" + net.ac_status = net.ac_status .. (time+final_step_time).." Can't craft "..count.." "..name..".\n" end end replace = replace and hasit end - local prepwork = function () + local prepwork = function (time) -- Do all the little bits of prepwork for func, name in pairs(prepworkbits) do --me.log("PREPing: before "..name, "error") - func() + --net.ac_status = net.ac_status .. time.." PREPing before "..name..".\n" + func(time) --me.log("PREPing: done "..name, "error") + --net.ac_status = net.ac_status .. time.." PREPing after "..name..".\n" end end -- end of prepwork @@ -373,7 +430,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) end local main_action = function() --me.log("ACTION: prep for "..stack:get_name(), "error") - prepwork() + prepwork(next_time) -- and once we are done with all the postponed work, we can reduce "ac" -- lifetimes are more complex than you can imagine. -- We use a simple rule. When all done, there is nothing left. At that point, @@ -427,7 +484,7 @@ local function build(net, cpos, inv, name, count, stack, sink, time) end if not dst_stack:is_empty() then --me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error") - local leftovers = sink(dst_stack) + local leftovers = sink(dst_stack, ctime) if leftovers and not leftovers:is_empty() then --me.log("autocrafter overflow, backpressuring", "error") net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" @@ -562,18 +619,18 @@ function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) end local start_time = me.autocraft_next_start(net) or 0 net.ac_status = net.ac_status .. start_time.." using pipeworks autocrafter\n" - local sink = function(stack) + local sink = function(stack, time) local leftovers = me.insert_item(stack, net, inv, "main") net:set_storage_space(true) return leftovers end - local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, sink, start_time) + local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, nil, sink, start_time) if built then --me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") net.ac_status = net.ac_status .. start_time.." Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds.\n" else --me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") - net.ac_status = net.ac_status .. start_time.." Can't craft "..(count*craft_count).." "..name..".\n" + net.ac_status = net.ac_status .. (start_time).." Can't craft "..(count*craft_count).." "..name..".\n" end return end From d16e11d43490cf7a9ad0b287fed6558effd038ae Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Mon, 4 Mar 2024 11:35:42 -0800 Subject: [PATCH 32/36] Add glass builtin. Add nonsensical_skyblock (aka glcraft) support. --- modules/ores/init.lua | 16 ++++++++++++++++ modules/storage/cterminal.lua | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/ores/init.lua b/modules/ores/init.lua index 29ca3ee..4f36263 100644 --- a/modules/ores/init.lua +++ b/modules/ores/init.lua @@ -84,3 +84,19 @@ me.register_item("quartz_crystal", { me.register_node("quartz", quartz_nodedef) + +local glcraft_modpath = minetest.get_modpath("glcraft") +if glcraft_modpath then + local glass_ingredient = "default:glass" + if mcl_core_modpath then glass_ingredient = "mcl_core:glass" end + -- glcraft is part of the nonsensical_skyblock game. It doesn't have a world, + -- so we need to add a crafting recipe for the ores. + minetest.register_craft({ + output = "microexpansion:quartz", + recipe = { + {"bonemeal:fertiliser", glass_ingredient, "bonemeal:fertiliser"}, + {"bonemeal:fertiliser", stone_ingrediant, "bonemeal:fertiliser"}, + {"bonemeal:fertiliser", glass_ingredient, "bonemeal:fertiliser"}, + } + }) +end diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 5bcfae1..70a6500 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -256,7 +256,8 @@ end me.output_by_typename = { -- aka me.register_output_by_typename("cooking", "default:stone") -- shared by technic and techage - ["cooking"] = { "default:stone", "default:copper_ingot", "default:gold_ingot", "default:tin_ingot" } + ["cooking"] = { "default:stone", "default:glass", "default:copper_ingot", + "default:gold_ingot", "default:tin_ingot" } } -- Used to register what machine types (typename) produce which outputs. @@ -285,6 +286,7 @@ end me.map_output_to_inputs = { -- furnace ("cooking") ["default:stone"] = { ItemStack("default:cobble") }, + ["default:glass"] = { ItemStack("default:sand") }, } From f275b612e9ab2af949100635150ee1e976dc8c39 Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Tue, 12 Mar 2024 16:09:30 -0700 Subject: [PATCH 33/36] Refine autocrafting to be more robust. Add clay cooking. --- modules/network/autocraft.lua | 130 ++++++++++++++++++++++------------ modules/network/network.lua | 4 ++ modules/storage/cterminal.lua | 7 +- 3 files changed, 92 insertions(+), 49 deletions(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index 3a4a704..8b3e197 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -70,6 +70,7 @@ function me.reserve(net, hash, original_start, length) local ending = start + length -- inputs and outputs can collide if we run them too close net.pending.busy[hash] = ending + 0.1 + --net:aclog(original_start, "Machine "..hash.." in use: "..free_time.." and needed for "..length.." finishing at "..(ending+0.0)) return start end @@ -84,7 +85,7 @@ end local defer_move = function(net, name, count, total, time, dat, inv, prepworkbits, grabbed) local slot = inv:get_size("ac")+1 --me.log("SLOT: inv now has "..slot.." slots.", "error") - --net.ac_status = net.ac_status .. time.." Did save "..grabbed:get_count().." "..grabbed:get_name().." into slot "..slot..".\n" + --net:aclog(time, " Did save "..grabbed:get_count().." "..grabbed:get_name().." into slot "..slot) inv:set_size("ac", slot) inv:set_stack("ac", slot, grabbed) -- and later we do this: @@ -92,7 +93,7 @@ local defer_move = function(net, name, count, total, time, dat, inv, prepworkbit --me.log("PREP: about to move "..name, "error") local stack = inv:get_stack("ac", slot) --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") - --net.ac_status = net.ac_status .. time.." Loading "..stack:get_count().." "..name.." from slot "..slot..".\n" + --net:aclog(time, " Loading "..stack:get_count().." "..name.." from slot "..slot) local leftovers = 0 for i = 1, #dat do -- todo: prove the below is correct. @@ -104,13 +105,45 @@ local defer_move = function(net, name, count, total, time, dat, inv, prepworkbit stack:set_count(leftovers) --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") if leftovers > 0 then - net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers.." "..name..".\n" + net:aclog(time, "Machine was in use, backpressuring "..leftovers.." "..name) end inv:set_stack("ac", slot, stack) end prepworkbits[func] = name end +local function trim(net, dat, total) + local recip = dat[1].recip + local ostack = dat[1].ostack + dat[1].recip = nil + dat[1].ostack = nil + -- don't split stupidly small counts across machines, too much + -- wasted time and too inefficient. + --if total < 5 then total = 1 end + + -- we sort all available machines so we can use the least busy machines first + if not net.pending then + net.pending = {} + net.pending.time = {} + end + if not net.pending.busy then + net.pending.busy = {} + end + local key = function(r) + return net.pending.busy[r.ahash] or 0 + end + local cmp = function(a, b) + return key(a) < key(b) + end + table.sort(dat, cmp) + -- Remove the extra machines. + while #dat > total do + table.remove(dat) + end + dat[1].recip = recip + dat[1].ostack = ostack +end + -- Testing: HV solar is realiable, big loans are screwy. -- HV batteries are realiable. local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, time) @@ -141,7 +174,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti if not built then -- we are done, can't craft, so stop else - next_time = math.max(next_time, time + step_time) + next_time = next_time + step_time end count = count - max end @@ -172,10 +205,6 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti dat[i].apos = apos dat[i].ipos = ipos dat[i].rinv = minetest.get_meta(dat[i].apos):get_inventory() - -- todo: figure out if we should use total. - if i == count then - break - end ::continue:: end --me.log("INT: looking up output "..name, "error") @@ -196,10 +225,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local speed = me.speed[machine_name] or 1 local craft_count = dat[1].ostack:get_count() local total = math.ceil(count/craft_count) - -- Remove the extra machines. In theory we could remove the busy machines. - while #dat > total do - table.remove(dat) - end + trim(net, dat, total) -- crafting 4 carbon plates misses taking 1 carbon plate on output, make this bigger -- we'll try 1 for now, figure out right formula. 1 looks perfect. 128 glue is short by 2 -- 1 + 1 is a second too slow on the doped for 81., 2 +0 doesn't work, a second shy @@ -253,9 +279,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti end local craft_count = dat[1].ostack:get_count() local total = math.ceil(count/craft_count) - while #dat > total do - table.remove(dat) - end + trim(net, dat, total) local subtotal = math.floor((total+#dat-1)/#dat) main_action_time = subtotal * pipeworks_craft_time + 1.1 else @@ -268,21 +292,21 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti dat[i].isink = function(sstack, time) if dat[i].slots == true then -- if we have already run the final move, we can move in place directly. - --net.ac_status = net.ac_status .. time.." Simple sink, about to move "..sstack:get_count().." "..sstack:get_name()..".\n" + --net:aclog(time, " Simple sink, about to move "..sstack:get_count().." "..sstack:get_name()) local leftovers = dat[i].rinv:add_item("src", sstack) - --net.ac_status = net.ac_status .. time.." Leftovers "..leftovers:get_count().." "..leftovers:get_name()..".\n" + --net:aclog(time, " Leftovers "..leftovers:get_count().." "..leftovers:get_name()) return leftovers end local slot = inv:get_size("ac")+1 --me.log("SLOT: inv now has "..slot.." slots.", "error") - --net.ac_status = net.ac_status .. time.." For "..count.." "..name.." for machine "..i.." created slot "..slot..".\n" + --net:aclog(time, " For "..count.." "..name.." for machine "..i.." created slot "..slot) inv:set_size("ac", slot) if not dat[i].slots then dat[i].slots = {} end dat[i].slots[slot] = true --me.log("TIMER: prep inputs, moving "..sstack:get_count().." "..sstack:get_name(), "error") - --net.ac_status = net.ac_status .. time.." Sinking "..sstack:get_count().." "..sstack:get_name().." for "..name.." into slot "..slot..".\n" + --net:aclog(time, " Sinking "..sstack:get_count().." "..sstack:get_name().." for "..name.." into slot "..slot) inv:set_stack("ac", slot, sstack) return ItemStack() end @@ -294,12 +318,12 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti for slot,_ in pairs(dat[i].slots) do local stack = inv:get_stack("ac", slot) --me.log("PREP: before move actual content of slot "..slot.." is "..stack:get_count().." "..stack:get_name(), "error") - --net.ac_status = net.ac_status .. time.." About to move sunk "..stack:get_count().." "..stack:get_name()..", for "..count.." "..name.." from slot "..slot..".\n" + --net:aclog((time, " About to move sunk "..stack:get_count().." "..stack:get_name()..", for "..count.." "..name.." from slot "..slot) local leftovers = dat[i].rinv:add_item("src", stack) --me.log("PREP: post move into real inventory "..stack:get_count().." "..name.." leftovers", "error") - --net.ac_status = net.ac_status .. time.." Leftovers "..leftovers:get_count().." "..leftovers:get_name()..".\n" + --net:aclog(time, " Leftovers "..leftovers:get_count().." "..leftovers:get_name()) if not leftovers:is_empty() then - net.ac_status = net.ac_status .. time.." Machine was in use, backpressuring "..leftovers:get_count().." "..name..".\n" + net:aclog(time, "Machine was in use, backpressuring "..leftovers:get_count().." "..name) end inv:set_stack("ac", slot, leftovers) --slot = 0 @@ -309,9 +333,9 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti end prepworkbits[function(time) for i = 1, #dat do - --net.ac_status = net.ac_status .. time.." PREP2ing before "..name..".\n" + --net:aclog(time, "PREP2ing before "..name) funcs[i](i, time) - --net.ac_status = net.ac_status .. time.." PREP2ing after "..name..".\n" + --net:aclog(time, "PREP2ing after "..name) end end] = name local craft_count = dat[1].ostack:get_count() @@ -329,7 +353,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti for i = 1, #dat[1].recip.input do local inp = dat[1].recip.input[i] --me.log("MID: consuming "..inp:get_name().." count: "..count.." inp:getcount: "..inp:get_count(), "error") - --net.ac_status = net.ac_status .. time.." Consuming "..inp:get_count().." "..inp:get_name()..".\n" + --net:aclog(time, " Consuming "..inp:get_count().." "..inp:get_name()) consume[inp:get_name()] = (consume[inp:get_name()] or 0) + count*inp:get_count() end else @@ -344,7 +368,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local replace = true local next_time = {} for i = 1, #dat do - next_time[i] = me.reserve(net, dat[i].ahash, time, main_action_time) + next_time[i] = time end --me.log("RESERVE: "..name.." stime "..time.." step "..main_action_time.." reserve "..next_time[1], "error") --me.log("PREP: pre count is "..count, "error") @@ -366,18 +390,18 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local grabbed = me.remove_item(net, inv, "main", istack) if grabbed and grabbed:get_count() == count then --me.log("ac grabbed "..name, "error") - net.ac_status = net.ac_status .. time.." Grabbed "..count.." "..name..".\n" + net:aclog(time, " Grabbed "..count.." "..name) defer_move(net, name, count, total, time, dat, inv, prepworkbits, grabbed) -- and then something moves the size of ac back to before we started else --me.log("ac could not grab "..count.." "..name, "error") - net.ac_status = net.ac_status .. time.." Could not grab "..count.." "..name..".\n" + net:aclog(time, "Could not grab "..count.." "..name) hasit = false end else -- Try and autocraft it --me.log("AC: recursive crafting "..count.." "..istack:get_count(), "error") - net.ac_status = net.ac_status .. time.." Need to craft "..count.." "..name..".\n" + net:aclog(time, " Need to craft "..count.." "..name) hasit = true local final_step_time = 0 for i = 1, #dat do @@ -391,16 +415,17 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local built, step_time = build(net, cpos, inv, name, subcount, inner_istack, prepworkbits, dat[i].isink, time) if built then next_time[i] = math.max(next_time[i], time + step_time) + --net:aclog(next_time[i], "recursive time for "..i.." is "..next_time[i]) final_step_time = math.max(final_step_time, step_time) else hasit = false end end if hasit then - net.ac_status = net.ac_status .. time.." Craft "..count.." "..name.." in "..final_step_time.." seconds.\n" + net:aclog(time, "Craft "..count.." "..name.." in "..final_step_time.." seconds") else me.log("can't craft "..istack:get_count().." "..istack:get_name(), "error") - net.ac_status = net.ac_status .. (time+final_step_time).." Can't craft "..count.." "..name..".\n" + net:aclog(time+final_step_time, "Can't craft "..count.." "..name) end end replace = replace and hasit @@ -409,10 +434,10 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti -- Do all the little bits of prepwork for func, name in pairs(prepworkbits) do --me.log("PREPing: before "..name, "error") - --net.ac_status = net.ac_status .. time.." PREPing before "..name..".\n" + --net:aclog(time, "PREPing before "..name) func(time) --me.log("PREPing: done "..name, "error") - --net.ac_status = net.ac_status .. time.." PREPing after "..name..".\n" + --net:aclog(time, "PREPing after "..name) end end -- end of prepwork @@ -426,10 +451,21 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local tmp_next_time = next_time next_time = 0 for i = 1, #dat do - next_time = math.max(next_time, tmp_next_time[i]) + -- We need to bump next time up to account for all machine reservations, use 0 + -- to merely find when the machine is free next. When all are free, that's when + -- we start. + next_time = math.max(next_time, me.reserve(net, dat[i].ahash, tmp_next_time[i], 0)) + end + local actual_time = next_time + --net:aclog(actual_time, " Actual time was "..actual_time) + -- and reserve all of them til the end of the last one + for i = 1, #dat do + actual_time = math.max(actual_time, me.reserve(net, dat[i].ahash, next_time, main_action_time) + main_action_time) end + --net:aclog(actual_time, " Actual endtime is "..actual_time) local main_action = function() --me.log("ACTION: prep for "..stack:get_name(), "error") + net:aclog(next_time, "Now crafting "..name) prepwork(next_time) -- and once we are done with all the postponed work, we can reduce "ac" -- lifetimes are more complex than you can imagine. @@ -477,17 +513,18 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti -- deal with output and replacements local dst_stack = dat[i].rinv:remove_item("dst", inner_stack) local ctime = next_time+action_time_step + net:aclog(ctime, " Now done with "..name) if dst_stack:get_count() ~= inner_stack:get_count() then --me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") -- me.log("saw "..dst_stack:get_count().." items instead of "..inner_stack:get_count().." items", "error") - net.ac_status = net.ac_status .. ctime.." Missing "..(inner_stack:get_count()-dst_stack:get_count()).." "..name..", only made "..dst_stack:get_count()..".\n" + net:aclog(ctime, "Missing "..(inner_stack:get_count()-dst_stack:get_count()).." "..name..", only made "..dst_stack:get_count()) end if not dst_stack:is_empty() then --me.log("TIMER: inserting "..dst_stack:get_count().." "..dst_stack:get_name(), "error") local leftovers = sink(dst_stack, ctime) if leftovers and not leftovers:is_empty() then --me.log("autocrafter overflow, backpressuring", "error") - net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + net:aclog(ctime, "Backpressure of "..name) -- If any don't fit, back pressure on the crafter, we don't -- mean to do this, and want to chunk the crafting items smaller dat[i].rinv:add_item("dst", leftovers) @@ -503,14 +540,14 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti if dst_stack:get_count() ~= extra:get_count() then --me.log("wow, missing items that should have been crafted "..stack:get_name(), "error") --me.log("saw "..dst_stack:get_count().." items instead of "..extra:get_count().." items", "error") - net.ac_status = net.ac_status .. ctime.." Missing "..(extra:get_count() - dst_stack:get_count()).." extra "..name..".\n" + net:aclog(ctime, "Missing "..(extra:get_count() - dst_stack:get_count()).." extra "..name) end if not dst_stack:is_empty() then local leftovers = me.insert_item(dst_stack, net, inv, "main") net:set_storage_space(true) if leftovers and not leftovers:is_empty() then --me.log("autocrafter overflow, backpressuring", "error") - net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + net:aclog(ctime, "Backpressure of "..name) -- If any don't fit, back pressure on the crafter, we don't -- mean to do this, and want to chunk the crafting items smaller dat[i].rinv:add_item("dst", leftovers) @@ -523,7 +560,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti local leftovers = me.insert_item(second, net, inv, "main") if leftovers and not leftovers:is_empty() then --me.log("autocrafter overflow, backpressuring", "error") - net.ac_status = net.ac_status .. ctime.." Backpressure of "..name..".\n" + net:aclog(ctime, "Backpressure of "..name) -- If any don't fit, back pressure on the crafter, we don't -- mean to do this, and want to chunk the crafting items smaller dat[i].rinv:add_item("dst", leftovers) @@ -544,8 +581,7 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti --me.log("LATER: main action for "..stack:get_name().." in "..next_time.." seconds", "error") me.later(net, cpos, main_action, next_time) - -- The step time is the prep time and the main_action_time - local step_time = next_time - time + main_action_time + local step_time = actual_time - time return true, step_time end @@ -565,7 +601,7 @@ function me.later(net, cpos, action, time) end if i == 1 then --me.log("TIMER: starting timer to fire at "..time.." seconds", "error") - me.start_crafting(cpos, time+0.1) + me.start_crafting(cpos, time+0.0) else -- me.log("TIMER: did not start timer for later, index "..i.." at time "..time, "error") -- bubble sort the entry back to the right spot @@ -581,7 +617,7 @@ function me.later(net, cpos, action, time) net.pending[i-1] = net.pending[i] net.pending[i] = t if i == 2 then - me.start_crafting(cpos, net.pending.time[1]+0.1) + me.start_crafting(cpos, net.pending.time[1]+0.0) end else break @@ -613,12 +649,12 @@ function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) net:reload_network() end if net.autocrafters[name] or net.process[name] then - --me.log("using pipeworks autocrafter", "error") + --me.log("Using pipeworks autocrafter", "error") if not net.pending or not net.ac_status then net.ac_status = "" end local start_time = me.autocraft_next_start(net) or 0 - net.ac_status = net.ac_status .. start_time.." using pipeworks autocrafter\n" + net:aclog(start_time, "\nUsing pipeworks autocrafter") local sink = function(stack, time) local leftovers = me.insert_item(stack, net, inv, "main") net:set_storage_space(true) @@ -627,10 +663,10 @@ function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, nil, sink, start_time) if built then --me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") - net.ac_status = net.ac_status .. start_time.." Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds.\n" + net:aclog(start_time, "Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds") else --me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") - net.ac_status = net.ac_status .. (start_time).." Can't craft "..(count*craft_count).." "..name..".\n" + net:aclog(start_time, "Can't craft "..(count*craft_count).." "..name) end return end diff --git a/modules/network/network.lua b/modules/network/network.lua index fe12154..8ea4b27 100644 --- a/modules/network/network.lua +++ b/modules/network/network.lua @@ -518,3 +518,7 @@ end function network:update_counts() end + +function network:aclog(time, msg) + self.ac_status = self.ac_status ..time.." "..msg..".\n" +end diff --git a/modules/storage/cterminal.lua b/modules/storage/cterminal.lua index 70a6500..60a9a6c 100644 --- a/modules/storage/cterminal.lua +++ b/modules/storage/cterminal.lua @@ -256,8 +256,8 @@ end me.output_by_typename = { -- aka me.register_output_by_typename("cooking", "default:stone") -- shared by technic and techage - ["cooking"] = { "default:stone", "default:glass", "default:copper_ingot", - "default:gold_ingot", "default:tin_ingot" } + ["cooking"] = { "default:stone", "default:glass", "default:clay_brick", + "default:copper_ingot", "default:gold_ingot", "default:tin_ingot" } } -- Used to register what machine types (typename) produce which outputs. @@ -287,6 +287,7 @@ me.map_output_to_inputs = { -- furnace ("cooking") ["default:stone"] = { ItemStack("default:cobble") }, ["default:glass"] = { ItemStack("default:sand") }, + ["default:clay_brick"] = { ItemStack("default:clay_lump") }, } @@ -326,6 +327,8 @@ me.register_max("default:gold_ingot", 99*4) me.register_max("default:steel_ingot", 99*4) me.register_max("default:tin_ingot", 99*4) me.register_max("default:stone", 99*4) +me.register_max("default:glass", 99*4) +me.register_max("default:clay_brick", 99*4) me.register_max("mesecons_materials:glue", 99*4) me.register_max("mesecons_materials:fiber", 99*4) me.register_max("basic_materials:plastic_sheet", 99*4) From e2cdad3c2f1305ce7de1857c368c0db016cd75ac Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Wed, 20 Mar 2024 15:59:14 -0700 Subject: [PATCH 34/36] Fix recipe for annihilation plane. --- modules/item_transfer/annihilation.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/item_transfer/annihilation.lua b/modules/item_transfer/annihilation.lua index 7df3ae2..0c3d589 100644 --- a/modules/item_transfer/annihilation.lua +++ b/modules/item_transfer/annihilation.lua @@ -66,15 +66,15 @@ item_transfer.register_io_device("annihilation", { connect_sides = { "left" }, recipe = { { 1, { - {"", "basic_materials:ic", microexpansion.iron_ingot_ingredient }, - {"", "microexpansion:cable", "group:hoe" }, {"", "", microexpansion.iron_ingot_ingredient }, + {"", "microexpansion:cable", "group:hoe" }, + {"", "basic_materials:ic", microexpansion.iron_ingot_ingredient }, }, }, { 1, { - {"", "microexpansion:logic_chip", microexpansion.iron_ingot_ingredient }, - {"", "microexpansion:cable", "group:hoe" }, {"", "", microexpansion.iron_ingot_ingredient }, + {"", "microexpansion:cable", "group:hoe" }, + {"", "microexpansion:logic_chip", microexpansion.iron_ingot_ingredient }, }, } }, From 89a11017686a0852d54df44e9e1cfb95f7cc08dd Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Thu, 21 Mar 2024 13:38:37 -0700 Subject: [PATCH 35/36] Allow technic uranium to be exported sanely to centrifuge. --- modules/item_transfer/exporter.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/item_transfer/exporter.lua b/modules/item_transfer/exporter.lua index 3cd7bf5..8b81cbf 100644 --- a/modules/item_transfer/exporter.lua +++ b/modules/item_transfer/exporter.lua @@ -18,10 +18,16 @@ local function exporter_timer(pos, elapsed) --TODO: move more with upgrades local own_inv = minetest.get_meta(pos):get_inventory() local upgrades = me.count_upgrades(own_inv) + local max_count = math.pow(2, upgrades.bulk or 0) local export_filter = upgrades.filter and function(stack) + local s = stack:peek_item(max_count) + -- Ensure bulk exports are full sized, this allows uranium to work + -- in centrifuges. + if s:get_count() < max_count then + return not false + end return not own_inv:contains_item("filter",stack:peek_item()) end - local max_count = math.pow(2, upgrades.bulk or 0) me.move_inv(net, {inv=net:get_inventory(), name="main", huge=true}, {inv=inv, name=list}, max_count, export_filter) --TODO: perhaps call allow_insert and on_insert callbacks end From cee1c1d43e4e027423001a7cee9f429554ec2dee Mon Sep 17 00:00:00 2001 From: Mike Stump Date: Sun, 24 Mar 2024 10:11:20 -0700 Subject: [PATCH 36/36] Prefer me crafting. Bump technic crafting time a little. --- modules/network/autocraft.lua | 101 ++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/modules/network/autocraft.lua b/modules/network/autocraft.lua index 8b3e197..8dd4406 100644 --- a/modules/network/autocraft.lua +++ b/modules/network/autocraft.lua @@ -236,7 +236,8 @@ local function build(net, cpos, inv, name, count, stack, iprepworkbits, sink, ti --main_action_time = subtotal*1.025*dat[1].recip.time/speed + 2 -- ok --main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1 -- too fast? --main_action_time = math.ceil(subtotal*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? - main_action_time = math.ceil((subtotal+1)*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? + --main_action_time = math.ceil((subtotal+1)*1.02*dat[1].recip.time/speed) + 1.2 -- too fast? + main_action_time = math.ceil((subtotal+1)*1.04*dat[1].recip.time/speed) + 1.2 -- too fast? if second_output then second_output = ItemStack(second_output) second_output:set_count(second_output:get_count()*total) @@ -627,50 +628,17 @@ function me.later(net, cpos, action, time) end end -function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) - local ostack = linv:get_stack("output", 1) - local name = ostack:get_name() - --me.log("crafting "..name.." "..tostring(count), "error") - - local stack = ItemStack(name) - local craft_count = ostack:get_count() - --me.log("auto: craft_count "..craft_count.." count "..count, "error") - -- we craft a minimum of count, to the multiple of the crafting count - count = math.ceil(count/craft_count) - --me.log("auto: count is now "..count, "error") - stack:set_count(count*craft_count) - --me.log("auto: stack size is now "..stack:get_count(), "error") - --me.log("auto: and build count is "..(count*craft_count), "error") - - -- me.log("autocrafters: "..minetest.serialize(net.autocrafters), "error") - - if not net.process then - -- rewalk the interfaces on the network to rebuild the machines. - net:reload_network() - end - if net.autocrafters[name] or net.process[name] then - --me.log("Using pipeworks autocrafter", "error") - if not net.pending or not net.ac_status then - net.ac_status = "" - end - local start_time = me.autocraft_next_start(net) or 0 - net:aclog(start_time, "\nUsing pipeworks autocrafter") - local sink = function(stack, time) - local leftovers = me.insert_item(stack, net, inv, "main") - net:set_storage_space(true) - return leftovers - end - local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, nil, sink, start_time) - if built then - --me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") - net:aclog(start_time, "Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds") - else - --me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") - net:aclog(start_time, "Can't craft "..(count*craft_count).." "..name) +local recipe_is_empty = function(linv) + for i = 1, 9 do + local inp = linv:get_stack("recipe", i) + if inp and not inp:is_empty() then + return false end - return end + return true +end +local lautocraft = function(autocrafterCache, cpos, net, linv, inv, count, stack) --me.log("using microexpansion autocrafter", "error") local consume = {} for i = 1, 9 do @@ -729,3 +697,52 @@ function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) end end end + +function me.autocraft(autocrafterCache, cpos, net, linv, inv, count) + local ostack = linv:get_stack("output", 1) + local name = ostack:get_name() + --me.log("crafting "..name.." "..tostring(count), "error") + + local stack = ItemStack(name) + local craft_count = ostack:get_count() + --me.log("auto: craft_count "..craft_count.." count "..count, "error") + -- we craft a minimum of count, to the multiple of the crafting count + count = math.ceil(count/craft_count) + --me.log("auto: count is now "..count, "error") + stack:set_count(count*craft_count) + --me.log("auto: stack size is now "..stack:get_count(), "error") + --me.log("auto: and build count is "..(count*craft_count), "error") + + -- me.log("autocrafters: "..minetest.serialize(net.autocrafters), "error") + + if not recipe_is_empty(linv) then + lautocraft(autocrafterCache, cpos, net, linv, inv, count, stack) + end + + if not net.process then + -- rewalk the interfaces on the network to rebuild the machines. + net:reload_network() + end + if net.autocrafters[name] or net.process[name] then + --me.log("Using pipeworks autocrafter", "error") + if not net.pending or not net.ac_status then + net.ac_status = "" + end + local start_time = me.autocraft_next_start(net) or 0 + net:aclog(start_time, "\nUsing pipeworks autocrafter") + local sink = function(stack, time) + local leftovers = me.insert_item(stack, net, inv, "main") + net:set_storage_space(true) + return leftovers + end + local built, step_time = build(net, cpos, inv, name, count*craft_count, stack, nil, sink, start_time) + if built then + --me.log("crafting "..stack:get_count().." "..stack:get_name().." in "..step_time.." seconds", "error") + net:aclog(start_time, "Crafting "..(count*craft_count).." "..name.." in "..step_time.." seconds") + else + --me.log("can't craft "..stack:get_count().." "..stack:get_name(), "error") + net:aclog(start_time, "Can't craft "..(count*craft_count).." "..name) + end + return + end +end