From 1349e3c2d540b2d44b03786ab4aec9ce2e63c99a Mon Sep 17 00:00:00 2001 From: Joachim Stolberg Date: Mon, 10 Jun 2019 21:31:58 +0200 Subject: [PATCH] furnace added --- basis/command.lua | 2 +- basis/firebox_lib.lua | 49 +- basis/lib.lua | 7 + coal_power_station/firebox.lua | 14 +- furnace/booster.lua | 208 ++++++ furnace/cooking.lua | 118 ++++ furnace/firebox.lua | 212 ++++++ furnace/furnace__.lua | 667 ++++++++++++++++++ furnace/furnace_top.lua | 259 +++++++ furnace/recipes.lua | 56 ++ init.lua | 7 + lamps/lib.lua | 2 +- lamps/simplelamp.lua | 3 +- oil/oilpump.lua | 31 - sounds/techage_booster.ogg | Bin 0 -> 17790 bytes steam_engine/firebox.lua | 61 +- textures/techage_appl_furnace.png | Bin 0 -> 296 bytes ...age_fermenter.png => techage_concrete.png} | Bin textures/techage_concrete4.png | Bin 0 -> 597 bytes textures/techage_frame4_ta3.png | Bin 1115 -> 1142 bytes textures/techage_frame4_ta3_top.png | Bin 1115 -> 1142 bytes textures/techage_frame_ta3.png | Bin 985 -> 398 bytes textures/techage_frame_ta3_top.png | Bin 985 -> 398 bytes 23 files changed, 1595 insertions(+), 101 deletions(-) create mode 100644 furnace/booster.lua create mode 100644 furnace/cooking.lua create mode 100644 furnace/firebox.lua create mode 100644 furnace/furnace__.lua create mode 100644 furnace/furnace_top.lua create mode 100644 furnace/recipes.lua delete mode 100644 oil/oilpump.lua create mode 100644 sounds/techage_booster.ogg create mode 100644 textures/techage_appl_furnace.png rename textures/{techage_fermenter.png => techage_concrete.png} (100%) create mode 100644 textures/techage_concrete4.png diff --git a/basis/command.lua b/basis/command.lua index a8fa6b0..92ed345 100644 --- a/basis/command.lua +++ b/basis/command.lua @@ -324,7 +324,7 @@ function techage.transfer(pos, outdir, topic, payload, network, nodenames) if network then dpos, indir = network:get_connected_node_pos(pos, outdir) else - dpos, indir = tubelib2.get_pos(pos, outdir) + dpos, indir = tubelib2.get_pos(pos, outdir), outdir end -- check node name local name = techage.get_node_lvm(dpos).name diff --git a/basis/firebox_lib.lua b/basis/firebox_lib.lua index a0f20f3..6319b9f 100644 --- a/basis/firebox_lib.lua +++ b/basis/firebox_lib.lua @@ -42,21 +42,35 @@ function techage.firebox.formspec(mem) if mem.running then fuel_percent = ((mem.burn_cycles or 1) * 100) / (mem.burn_cycles_total or 1) end - local power_level = mem.power_level or 4 - return "size[8,6.5]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "list[current_name;fuel;1,0.5;1,1;]".. - "image[2,0.5;1,1;default_furnace_fire_bg.png^[lowpart:".. - fuel_percent..":default_furnace_fire_fg.png]".. - "label[4.5,0.1;"..I("Power")..":]".. - "dropdown[6,0;1.8;power_level;25%,50%,75%,100%;"..power_level.."]".. - "button[1,1.5;1.8,1;update;"..I("Update").."]".. - "list[current_player;main;0,2.8;8,4;]".. - "listring[current_name;fuel]".. - "listring[current_player;main]".. - default.get_hotbar_bg(0, 2.8) + if mem.power_level then + return "size[8,6.5]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[current_name;fuel;1,0.5;1,1;]".. + "image[2,0.5;1,1;default_furnace_fire_bg.png^[lowpart:".. + fuel_percent..":default_furnace_fire_fg.png]".. + "label[4.5,0.1;"..I("Power")..":]".. + "dropdown[6,0;1.8;power_level;25%,50%,75%,100%;"..power_level.."]".. + "button[1,1.5;1.8,1;update;"..I("Update").."]".. + "list[current_player;main;0,2.8;8,4;]".. + "listring[current_name;fuel]".. + "listring[current_player;main]".. + default.get_hotbar_bg(0, 2.8) + else + return "size[8,6]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[current_name;fuel;1,0.5;1,1;]".. + "image[3,0.5;1,1;default_furnace_fire_bg.png^[lowpart:".. + fuel_percent..":default_furnace_fire_fg.png]".. + "button[5,0.5;1.8,1;update;"..I("Update").."]".. + "list[current_player;main;0,2;8,4;]".. + "listring[current_name;fuel]".. + "listring[current_player;main]".. + default.get_hotbar_bg(0, 2) + end end function techage.firebox.can_dig(pos, player) @@ -129,3 +143,8 @@ function techage.firebox.get_fuel(pos) end end +function techage.firebox.has_fuel(pos) + local inv = M(pos):get_inventory() + local items = inv:get_stack("fuel", 1) + return items:get_count() > 0 +end diff --git a/basis/lib.lua b/basis/lib.lua index c6466d4..925a3b0 100644 --- a/basis/lib.lua +++ b/basis/lib.lua @@ -35,6 +35,13 @@ function techage.one_of(val, selection) return selection[1] end +function techage.index(list, x) + for idx, v in pairs(list) do + if v == x then return idx end + end + return nil +end + function techage.get_node_lvm(pos) local node = minetest.get_node_or_nil(pos) if node then diff --git a/coal_power_station/firebox.lua b/coal_power_station/firebox.lua index 19c3fd1..593aac1 100644 --- a/coal_power_station/firebox.lua +++ b/coal_power_station/firebox.lua @@ -103,6 +103,7 @@ minetest.register_node("techage:coalfirebox", { local mem = tubelib2.init_mem(pos) mem.running = false mem.burn_cycles = 0 + mem.power_level = 4 local meta = M(pos) meta:set_string("formspec", firebox.formspec(mem)) local inv = meta:get_inventory() @@ -189,11 +190,14 @@ techage.register_node({"techage:coalfirebox"}, { return techage.get_items(inv, "fuel", num) end, on_push_item = function(pos, in_dir, stack) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local mem = tubelib2.get_mem(pos) - start_firebox(pos, mem) - return techage.put_items(inv, "fuel", stack) + if firebox.Burntime[stack:get_name()] then + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local mem = tubelib2.get_mem(pos) + start_firebox(pos, mem) + return techage.put_items(inv, "fuel", stack) + end + return false end, on_unpull_item = function(pos, in_dir, stack) local meta = minetest.get_meta(pos) diff --git a/furnace/booster.lua b/furnace/booster.lua new file mode 100644 index 0000000..478adf1 --- /dev/null +++ b/furnace/booster.lua @@ -0,0 +1,208 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA3 Booster + +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P = minetest.string_to_pos +local M = minetest.get_meta + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local I,_ = dofile(MP.."/intllib.lua") + +local POWER_CONSUMPTION = 3 + +local Power = techage.ElectricCable + +local function infotext(pos, state) + M(pos):set_string("infotext", I("TA3 Booster")..": "..state) +end + +local function swap_node(pos, name) + local node = minetest.get_node(pos) + if node.name == name then + return + end + node.name = name + minetest.swap_node(pos, node) +end + +local function play_sound(pos) + local mem = tubelib2.get_mem(pos) + if mem.running then + mem.handle = minetest.sound_play("techage_booster", { + pos = pos, + gain = 1, + max_hear_distance = 7}) + minetest.after(2, play_sound, pos) + end +end + +local function stop_sound(pos) + local mem = tubelib2.get_mem(pos) + if mem.handle then + minetest.sound_stop(mem.handle) + mem.handle = nil + end +end + +local function on_power_pass1(pos, mem) + --print("on_power_pass1") + if mem.running then + mem.correction = POWER_CONSUMPTION + else + mem.correction = 0 + end + return mem.correction +end + +local function on_power_pass2(pos, mem, sum) + if sum > 0 then + mem.has_power = true + return 0 + else + mem.has_power = false + return -mem.correction + end +end + +minetest.register_node("techage:ta3_booster", { + description = I("TA3 Booster"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta3.png^techage_appl_arrow.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_hole_biogas.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_hole_biogas.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_compressor.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_compressor.png^[transformFX^techage_frame_ta3.png", + }, + + on_construct = tubelib2.init_mem, + + after_place_node = function(pos, placer) + local node = minetest.get_node(pos) + local indir = techage.side_to_indir("R", node.param2) + M(pos):set_int("indir", indir) + infotext(pos, "stopped") + end, + + paramtype2 = "facedir", + groups = {cracky=2, crumbly=2, choppy=2}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + + +minetest.register_node("techage:ta3_booster_on", { + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta3.png^techage_appl_arrow.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_hole_biogas.png^techage_frame_ta3.png", + "techage_filling_ta3.png^techage_appl_hole_biogas.png^techage_frame_ta3.png", + { + image = "techage_filling4_ta3.png^techage_appl_compressor4.png^techage_frame4_ta3.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.2, + }, + }, + { + image = "techage_filling4_ta3.png^techage_appl_compressor4.png^[transformFX]^techage_frame4_ta3.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.2, + }, + }, + }, + + paramtype2 = "facedir", + groups = {not_in_creative_inventory = 1}, + diggable = false, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + +techage.power.register_node({"techage:ta3_booster", "techage:ta3_booster_on"}, { + on_power_pass1 = on_power_pass1, + on_power_pass2 = on_power_pass2, + power_network = Power, + conn_sides = {"F", "B", "U", "D"}, +}) + +-- for intra machine communication +techage.register_node({"techage:ta3_booster", "techage:ta3_booster_on"}, { + on_transfer = function(pos, in_dir, topic, payload) + --print("ta3_booster", topic, payload, in_dir) + if M(pos):get_int("indir") == in_dir then + local mem = tubelib2.get_mem(pos) + if topic == "power" then + return mem.has_power + elseif topic == "start" then + if mem.has_power then + mem.running = true + play_sound(pos) + swap_node(pos, "techage:ta3_booster_on") + infotext(pos, "running") + techage.power.power_distribution(pos) + else + infotext(pos, "no power") + end + elseif topic == "stop" then + mem.running = false + stop_sound(pos) + swap_node(pos, "techage:ta3_booster") + if mem.has_power then + infotext(pos, "stopped") + else + infotext(pos, "no power") + end + techage.power.power_distribution(pos) + end + end + end +}) + +minetest.register_craft({ + output = "techage:ta3_booster", + recipe = { + {"basic_materials:steel_bar", "default:wood", "basic_materials:steel_bar"}, + {"techage:steam_pipeS", "basic_materials:gear_steel", "techage:steam_pipeS"}, + {"basic_materials:steel_bar", "default:wood", "basic_materials:steel_bar"}, + }, +}) + +techage.register_help_page(I("TA3 Booster"), +I([[Part of the TA3 Furnace and further machines. +Used to increase the air/gas pressure.]]), "techage:ta3_booster") + + +minetest.register_lbm({ + label = "[techage] Booster sound", + name = "techage:booster", + nodenames = {"techage:ta3_booster_on"}, + run_at_every_load = true, + action = function(pos, node) + play_sound(pos) + end +}) diff --git a/furnace/cooking.lua b/furnace/cooking.lua new file mode 100644 index 0000000..ee82e0c --- /dev/null +++ b/furnace/cooking.lua @@ -0,0 +1,118 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Cooking routines for furnace + +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P = minetest.string_to_pos +local M = minetest.get_meta + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local I,_ = dofile(MP.."/intllib.lua") + +local Recipes = {} -- registered recipes +local KeyList = {} -- index to Recipes key translation +local NumRecipes = 0 + +techage.furnace = {} + +-- move recipe src items to output inventory +local function process(inv, recipe) + local res + -- check if all ingredients are available + for _,item in ipairs(recipe.input) do + if not inv:contains_item("src", item) then + return false + end + end + -- remove items + for _,item in ipairs(recipe.input) do + inv:remove_item("src", item) + end + -- add to dst + local stack = ItemStack(recipe.output) + stack:set_count(recipe.number) + inv:add_item("dst", stack) + return true +end + +function techage.furnace.smelting(pos, mem, elapsed) + local inv = M(pos):get_inventory() + local state = techage.STANDBY + if inv and not inv:is_empty("src") then + local key = KeyList[mem.recipe_idx or 1] or KeyList[1] + local recipe = Recipes[key] + -- check dst inv + local item = ItemStack(recipe.output) + if not inv:room_for_item("dst", item) then + return techage.BLOCKED + end + + elapsed = elapsed + (mem.leftover or 0) + while elapsed >= recipe.time do + if process(inv, recipe) == false then + mem.leftover = 0 + return techage.STANDBY + else + state = techage.RUNNING + end + elapsed = elapsed - recipe.time + end + mem.leftover = elapsed + return state + end + return techage.STANDBY +end + +function techage.furnace.get_output(idx) + local key = KeyList[idx] or KeyList[1] + return Recipes[key].output +end + +function techage.furnace.get_num_recipes() + return NumRecipes +end + +function techage.furnace.reset_cooking(mem) + mem.leftover = 0 +end + + +if minetest.global_exists("unified_inventory") then + unified_inventory.register_craft_type("ta3_melting", { + description = I("TA3 Melting"), + icon = "techage_concrete.png^techage_appl_furnace.png^techage_frame_ta3.png", + width = 2, + height = 2, + }) +end + +function techage.furnace.register_recipe(recipe) + local output = string.split(recipe.output, " ") + local number = tonumber(output[2] or 1) + table.insert(KeyList, output) + Recipes[output] = { + input = recipe.recipe, + output = output[1], + number = number, + time = math.max((recipe.time or 3) * number, 2), + } + NumRecipes = NumRecipes + 1 + + if minetest.global_exists("unified_inventory") then + recipe.items = recipe.recipe + recipe.type = "ta3_melting" + unified_inventory.register_craft(recipe) + end +end \ No newline at end of file diff --git a/furnace/firebox.lua b/furnace/firebox.lua new file mode 100644 index 0000000..4e6acf9 --- /dev/null +++ b/furnace/firebox.lua @@ -0,0 +1,212 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA3 Industrial Furnace Firebox + +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P = minetest.string_to_pos +local M = minetest.get_meta + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local I,_ = dofile(MP.."/intllib.lua") + +local firebox = techage.firebox + +local CYCLE_TIME = 2 + +local function has_fuel(pos, mem) + return mem.burn_cycles > 0 or firebox.has_fuel(pos) +end + +local function stop_firebox(pos, mem) + mem.running = false + firebox.swap_node(pos, "techage:furnace_firebox") + minetest.get_node_timer(pos):stop() + M(pos):set_string("formspec", firebox.formspec(mem)) +end + +local function node_timer(pos, elapsed) + local mem = tubelib2.get_mem(pos) + if mem.running then + mem.burn_cycles = (mem.burn_cycles or 0) - 1 + if mem.burn_cycles <= 0 then + local taken = firebox.get_fuel(pos) + if taken then + mem.burn_cycles = firebox.Burntime[taken:get_name()] / CYCLE_TIME + mem.burn_cycles_total = mem.burn_cycles + else + stop_firebox(pos, mem) + return false + end + end + return true + end +end + +local function start_firebox(pos, mem) + if not mem.running then + mem.running = true + node_timer(pos, 0) + firebox.swap_node(pos, "techage:furnace_firebox_on") + minetest.get_node_timer(pos):start(CYCLE_TIME) + M(pos):set_string("formspec", firebox.formspec(mem)) + end +end + +local function booster_cmnd(pos, cmnd) + return techage.transfer( + pos, + "L", -- outdir + cmnd, -- topic + nil, -- payload + nil, -- network + {"techage:ta3_booster", "techage:ta3_booster_on"}) +end + +minetest.register_node("techage:furnace_firebox", { + description = I("TA3 Furnace Firebox"), + tiles = { + -- up, down, right, left, back, front + "techage_concrete.png^techage_appl_open.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_appl_hole_biogas.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_appl_firehole.png^techage_frame_ta3.png", + }, + paramtype2 = "facedir", + on_rotate = screwdriver.disallow, + groups = {cracky=2}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), + + on_timer = node_timer, + can_dig = firebox.can_dig, + allow_metadata_inventory_put = firebox.allow_metadata_inventory, + allow_metadata_inventory_take = firebox.allow_metadata_inventory, + on_receive_fields = firebox.on_receive_fields, + on_rightclick = firebox.on_rightclick, + + on_construct = function(pos) + local mem = tubelib2.init_mem(pos) + mem.running = false + mem.burn_cycles = 0 + local meta = M(pos) + meta:set_string("formspec", firebox.formspec(mem)) + local inv = meta:get_inventory() + inv:set_size('fuel', 1) + end, +}) + +minetest.register_node("techage:furnace_firebox_on", { + description = I("TA3 Furnace Firebox"), + tiles = { + -- up, down, right, left, back, front + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + "techage_concrete.png^techage_frame_ta3.png", + { + image = "techage_concrete4.png^techage_appl_firehole4.png^techage_frame4_ta3.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + }, + paramtype2 = "facedir", + light_source = 8, + on_rotate = screwdriver.disallow, + diggable = false, + groups = {not_in_creative_inventory = 1}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), + drop = "techage:furnace_firebox", + + on_timer = node_timer, + can_dig = firebox.can_dig, + allow_metadata_inventory_put = firebox.allow_metadata_inventory, + allow_metadata_inventory_take = firebox.allow_metadata_inventory, + on_receive_fields = firebox.on_receive_fields, + on_rightclick = firebox.on_rightclick, +}) + +minetest.register_craft({ + output = "techage:furnace_firebox", + recipe = { + {'techage:basalt_stone', 'techage:basalt_stone', 'techage:basalt_stone'}, + {'default:steel_ingot', '', 'default:steel_ingot'}, + {'techage:basalt_stone', 'techage:basalt_stone', 'techage:basalt_stone'}, + }, +}) + +techage.register_node({"techage:furnace_firebox", "techage:furnace_firebox_on"}, { + on_pull_item = function(pos, in_dir, num) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.get_items(inv, "fuel", num) + end, + on_push_item = function(pos, in_dir, stack) + if firebox.Burntime[stack:get_name()] then + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local mem = tubelib2.get_mem(pos) + return techage.put_items(inv, "fuel", stack) + end + return false + end, + on_unpull_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.put_items(inv, "fuel", stack) + end, + on_recv_message = function(pos, topic, payload) + if topic == "state" then + local meta = minetest.get_meta(pos) + return techage.get_inv_state(meta, "fuel") + else + return "unsupported" + end + end, + -- called from furnace_top + on_transfer = function(pos, in_dir, topic, payload) + --print("on_transfer", topic, payload) + if topic == "fuel" then + local mem = tubelib2.get_mem(pos) + return has_fuel(pos, mem) and booster_cmnd(pos, "power") + elseif topic == "start" then + local mem = tubelib2.get_mem(pos) + start_firebox(pos, mem) + booster_cmnd(pos, "start") + elseif topic == "stop" then + local mem = tubelib2.get_mem(pos) + stop_firebox(pos, mem) + booster_cmnd(pos, "stop") + end + end +}) + +minetest.register_lbm({ + label = "[techage] Furnace firebox", + name = "techage:furnace", + nodenames = {"techage:furnace_firebox_on"}, + run_at_every_load = true, + action = function(pos, node) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end +}) diff --git a/furnace/furnace__.lua b/furnace/furnace__.lua new file mode 100644 index 0000000..759b857 --- /dev/null +++ b/furnace/furnace__.lua @@ -0,0 +1,667 @@ + +--[[ + + ======================================================================= + Tubelib Biogas Machines Mod + by Micu (c) 2018, 2019 + + Copyright (C) 2018, 2019 Michal Cieslakiewicz + + Biogas Jet Furnace is an upgraded version of standard Gas Furnace. + It works 2 times faster in both item cooking speed and Biogas + consumption. One unit of Biogas lasts for 20 seconds but items + are cooked twice as fast. However, this puts more stress on internal + parts so machine needs to be serviced more frequently. + Please see gasfurnace.lua for differences between Biogas and Coal + furnaces as this device is functionally identical to Gas Furnace. + The only extra upgrade is support for pulling whole stacks from + output tray (like HighPerf Chest), so its output can be paired with + HighPerf Pusher. + + Note about implementation: to maintain timer at 1 sec while halving + cooking time a random factor has been introduced for all cooking + durations that are odd numbers. Such items will cook either longer or + quicker with 50/50 probability. Example: from 10 iron lumps that have + standard cooking time 3 sec each, ~5 items should cook for 2 sec and + ~5 for 1 sec giving average cooking time 1.5 sec and using proper + amount of Biogas. + + Tubelib v2 implementation info: + * device updates itself every tick, so cycle_time must be set to 1 + even though production takes longer (start method sets timer to + this value) + * keep_running function is called every time item is produced + (not every processing tick - function does not accept neither 0 + nor fractional values for num_items parameter) + * desired_state metadata allows to properly change non-running target + state during transition; when new state differs from old one, timer + is reset so it is guaranteed that each countdown starts from + COUNTDOWN_TICKS + * num_items in keep_running method is set to 1 (default value); + machine aging is controlled by aging_factor solely; tubelib item + counter is used to count production iterations not actual items + + License: LGPLv2.1+ + ======================================================================= + +]]-- + +--[[ + --------- + Variables + --------- +]]-- + +-- Jet Furnace uses Biogas twice as fast so 1 Biogas unit burns for 20 sec +local BIOGAS_TICKS = 20 +-- timing +local TIMER_TICK_SEC = 1 -- Node timer tick +local STANDBY_TICKS = 4 -- Standby mode timer frequency factor +local COUNTDOWN_TICKS = 4 -- Ticks to standby + +-- machine inventory +local INV_H = 3 -- Inventory height +local INV_IN_W = 3 -- Input inventory width +local INV_OUT_W = (6 - INV_IN_W) -- Output inventory width + +--[[ + -------- + Formspec + -------- +]]-- +-- static data for formspec +local fmxy = { + inv_h = tostring(INV_H), + inv_in_w = tostring(INV_IN_W), + mid_x = tostring(INV_IN_W + 1), + inv_out_w = tostring(INV_OUT_W), + inv_out_x = tostring(INV_IN_W + 2), + biogas_time = tostring(BIOGAS_TICKS * TIMER_TICK_SEC) +} + +-- formspec +local function formspec(self, pos, meta) + local state = meta:get_int("tubelib_state") + local fuel_pct = tostring(100 * meta:get_int("fuel_ticks") / BIOGAS_TICKS) + local item_pct = tostring(100 * (1 - meta:get_int("item_ticks") / meta:get_int("item_total"))) + return "size[8,8.25]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "list[context;src;0,0;" .. fmxy.inv_in_w .. "," .. fmxy.inv_h .. ";]" .. + "list[context;cur;" .. fmxy.inv_in_w .. ",0;1,1;]" .. + "image[" .. fmxy.inv_in_w .. + ",1;1,1;biogasmachines_gasfurnace_inv_bg.png^[lowpart:" .. + fuel_pct .. ":biogasmachines_gasfurnace_inv_fg.png]" .. + "image[" .. fmxy.mid_x .. ",1;1,1;gui_furnace_arrow_bg.png^[lowpart:" .. + item_pct .. ":gui_furnace_arrow_fg.png^[transformR270]" .. + "list[context;fuel;" .. fmxy.inv_in_w .. ",2;1,1;]" .. + "item_image[" .. fmxy.inv_in_w .. ",2;1,1;tubelib_addons1:biogas]" .. + "image_button[" .. fmxy.mid_x .. ",2;1,1;" .. + self:get_state_button_image(meta) .. ";state_button;]" .. + "item_image[2,3.25;0.5,0.5;biogasmachines:jetfurnace]" .. + "label[2.5,3.25;=]" .. + "item_image[2.75,3.25;0.5,0.5;default:furnace]" .. + "label[3.25,3.25;x 2]" .. + "item_image[4.5,3.25;0.5,0.5;tubelib_addons1:biogas]" .. + "tooltip[4.5,3.25;0.5,0.5;Biogas]" .. + "label[5,3.25;= " .. fmxy.biogas_time .. " sec]" .. + "list[context;dst;" .. fmxy.inv_out_x .. ",0;" .. fmxy.inv_out_w .. + "," .. fmxy.inv_h .. ";]" .. + "list[current_player;main;0,4;8,1;]" .. + "list[current_player;main;0,5.25;8,3;8]" .. + "listring[context;dst]" .. + "listring[current_player;main]" .. + "listring[context;src]" .. + "listring[current_player;main]" .. + "listring[context;fuel]" .. + "listring[current_player;main]" .. + (state == tubelib.RUNNING and + "box[" .. fmxy.inv_in_w .. ",0;0.82,0.88;#BF5F2F]" or + "listring[context;cur]listring[current_player;main]") .. + default.get_hotbar_bg(0, 4) +end + +--[[ + ------- + Helpers + ------- +]]-- + +-- reset processing data +local function state_meta_reset(pos, meta) + meta:set_int("item_ticks", -1) + meta:set_int("item_total", -1) +end + +-- Wrapper for 'cooking' get_craft_result() function for specified ItemStack +-- Return values are as follows: +-- time - cooking time or 0 if not cookable +-- input - input itemstack (take it from source stack to get decremented input) +-- output - output itemstack array (all extra leftover products are also here) +-- decr_input - decremented input (without leftover products) +local function get_cooking_items(stack) + if stack:is_empty() then + return 0, nil, nil, nil + end + local cookout, decinp = minetest.get_craft_result({ method = "cooking", + width = 1, items = { stack } }) + if cookout.time <= 0 or cookout.item:is_empty() then + return 0, nil, nil, nil + end + local inp = stack + local outp = { cookout.item } + local decp = decinp and decinp.items and decinp.items[1] or nil + if decp and not decp:is_empty() then + if decp:get_name() ~= stack:get_name() then + outp[#outp + 1] = decp + decp = ItemStack({}) + else + inp = ItemStack(stack:get_name() .. " " .. + tostring(stack:get_count() - decp:get_count())) + end + end + return cookout.time, inp, outp, decp +end + +-- calculates fast (jet) cooking time, adding randomly 1 (with 50/50 chance) +-- to odd durations to compensate halves +local function get_fast_cooking_time(time) + local otime = math.max(math.floor(time + 0.5), 2) -- rounding + local ntime = math.floor(otime / 2) + if otime % 2 == 0 then + return ntime + else + return math.random(ntime, ntime + 1) + end +end + +--[[ + ------------- + State machine + ------------- +]]-- + +local machine = tubelib.NodeStates:new({ + node_name_passive = "biogasmachines:jetfurnace", + node_name_active = "biogasmachines:jetfurnace_active", + node_name_defect = "biogasmachines:jetfurnace_defect", + infotext_name = "Biogas Jet Furnace", + cycle_time = TIMER_TICK_SEC, + standby_ticks = STANDBY_TICKS, + has_item_meter = true, + aging_factor = 20, + on_start = function(pos, meta, oldstate) + meta:set_int("desired_state", tubelib.RUNNING) + state_meta_reset(pos, meta) + end, + on_stop = function(pos, meta, oldstate) + meta:set_int("desired_state", tubelib.STOPPED) + state_meta_reset(pos, meta) + end, + formspec_func = formspec, +}) + +-- fault function for convenience as there is no on_fault method (yet) +local function machine_fault(pos, meta) + meta:set_int("desired_state", tubelib.FAULT) + state_meta_reset(pos, meta) + machine:fault(pos, meta) +end + +-- customized version of NodeStates:idle() +local function countdown_to_halt(pos, meta, target_state) + if target_state ~= tubelib.STANDBY and + target_state ~= tubelib.BLOCKED and + target_state ~= tubelib.STOPPED and + target_state ~= tubelib.FAULT then + return true + end + if machine:get_state(meta) == tubelib.RUNNING and + meta:get_int("desired_state") ~= target_state then + meta:set_int("tubelib_countdown", COUNTDOWN_TICKS) + meta:set_int("desired_state", target_state) + end + local countdown = meta:get_int("tubelib_countdown") - 1 + if countdown >= -1 then + -- we don't need anything less than -1 + meta:set_int("tubelib_countdown", countdown) + end + if countdown < 0 then + if machine:get_state(meta) == target_state then + return true + end + meta:set_int("desired_state", target_state) + -- workaround for switching between non-running states + meta:set_int("tubelib_state", tubelib.RUNNING) + if target_state == tubelib.FAULT then + machine_fault(pos, meta) + elseif target_state == tubelib.STOPPED then + machine:stop(pos, meta) + elseif target_state == tubelib.BLOCKED then + machine:blocked(pos, meta) + else + machine:standby(pos, meta) + end + return false + end + return true +end + +-- countdown to one of two states depending on fuel availability +local function fuel_countdown_to_halt(pos, meta, target_state_fuel, target_state_empty) + local inv = meta:get_inventory() + if meta:get_int("fuel_ticks") == 0 and inv:is_empty("fuel") then + return countdown_to_halt(pos, meta, target_state_empty) + else + return countdown_to_halt(pos, meta, target_state_fuel) + end +end + +--[[ + --------- + Callbacks + --------- +]]-- + +-- do not allow to dig protected or non-empty machine +local function can_dig(pos, player) + if minetest.is_protected(pos, player:get_player_name()) then + return false + end + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + return inv:is_empty("src") and inv:is_empty("dst") + and inv:is_empty("fuel") +end + +-- cleanup after digging +local function after_dig_node(pos, oldnode, oldmetadata, digger) + tubelib.remove_node(pos) +end + +-- init machine after placement +local function after_place_node(pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('src', INV_H * INV_IN_W) + inv:set_size('cur', 1) + inv:set_size('fuel', 1) + inv:set_size('dst', INV_H * INV_OUT_W) + meta:set_string("owner", placer:get_player_name()) + meta:set_int("fuel_ticks", 0) + state_meta_reset(pos, meta) + local number = tubelib.add_node(pos, "biogasmachines:jetfurnace") + machine:node_init(pos, number) +end + +-- validate incoming items +local function allow_metadata_inventory_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + if listname == "src" then + if stack:get_name() == "tubelib_addons1:biogas" then + return 0 + else + return stack:get_count() + end + elseif listname == "cur" or listname == "dst" then + return 0 + elseif listname == "fuel" then + if stack:get_name() == "tubelib_addons1:biogas" then + return stack:get_count() + else + return 0 + end + end + return 0 +end + +-- validate items move +local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) + local meta = minetest.get_meta(pos) + if to_list == "cur" or + (from_list == "cur" and machine:get_state(meta) == tubelib.RUNNING) then + return 0 + end + local inv = meta:get_inventory() + local stack = inv:get_stack(from_list, from_index) + return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) +end + +-- validate items retrieval +local function allow_metadata_inventory_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + if listname == "cur" then + local meta = minetest.get_meta(pos) + if machine:get_state(meta) == tubelib.RUNNING then + return 0 + end + end + return stack:get_count() +end + +-- formspec callback +local function on_receive_fields(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + machine:state_button_event(pos, fields) +end + +-- tick-based item production +local function on_timer(pos, elapsed) + local node = minetest.get_node(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local fuel = meta:get_int("fuel_ticks") + local recipe = {} + local inp + if inv:is_empty("cur") then + -- idle and ready, check for something to work with + if inv:is_empty("src") then + return fuel_countdown_to_halt(pos, meta, + tubelib.STANDBY, tubelib.STOPPED) + end + -- find item to cook/smelt that fits output tray + -- (parse list items as first choice is not always the best one) + local idx = -2 + for i = 1, inv:get_size("src") do + inp = inv:get_stack("src", i) + if not inp:is_empty() then + recipe.time, recipe.input, recipe.output, + recipe.decremented_input = get_cooking_items(inp) + if recipe.time > 0 then + idx = -1 + inv:set_list("dst_copy", inv:get_list("dst")) + local is_dst_ok = true + for _, stack in ipairs(recipe.output) do + local outp = inv:add_item("dst_copy", stack) + if not outp:is_empty() then + is_dst_ok = false + break + end + end + inv:set_size("dst_copy", 0) + if is_dst_ok then + idx = i + break + end + end + end + end + -- (idx == -2 - nothing cookable found in src) + -- (idx == -1 - cookable item in src but no space in dst) + if idx == -2 then + return fuel_countdown_to_halt(pos, meta, + tubelib.STANDBY, tubelib.STOPPED) + elseif idx == -1 then + if machine:get_state(meta) == tubelib.STANDBY then + -- adapt behaviour to other biogas machines + -- (standby->blocked should go through running) + machine:start(pos, meta, true) + return false + else + return fuel_countdown_to_halt(pos, meta, + tubelib.BLOCKED, tubelib.FAULT) + end + end + if machine:get_state(meta) == tubelib.STANDBY or + machine:get_state(meta) == tubelib.BLOCKED then + -- something to do, wake up and re-entry + machine:start(pos, meta, true) + return false + end + if fuel == 0 and inv:is_empty("fuel") then + return countdown_to_halt(pos, meta, tubelib.FAULT) + end + inv:set_stack("src", idx, recipe.decremented_input) + inv:set_stack("cur", 1, recipe.input) + recipe.time = get_fast_cooking_time(recipe.time) + meta:set_int("item_ticks", recipe.time) + meta:set_int("item_total", recipe.time) + else + -- production + inp = inv:get_stack("cur", 1) + if machine:get_state(meta) ~= tubelib.RUNNING or + inp:is_empty() then + -- exception, should not happen - oops + machine_fault(pos, meta) + return false + end + -- production tick + if fuel == 0 and inv:is_empty("fuel") then + return countdown_to_halt(pos, meta, tubelib.FAULT) + end + local itemcnt = meta:get_int("item_ticks") + local zzz -- dummy + if itemcnt < 0 then + -- interrupted - cook again + recipe.time, zzz, recipe.output = get_cooking_items(inp) + recipe.time = get_fast_cooking_time(recipe.time) + meta:set_int("item_total", recipe.time) + itemcnt = recipe.time + else + recipe.output = nil + end + itemcnt = itemcnt - 1 + if itemcnt == 0 then + if not recipe.output then + zzz, zzz, recipe.output = get_cooking_items(inp) + end + for _, i in ipairs(recipe.output) do + inv:add_item("dst", i) + end + inv:set_stack("cur", 1, ItemStack({})) + state_meta_reset(pos, meta) + -- item produced, increase aging + machine:keep_running(pos, meta, COUNTDOWN_TICKS) + else + meta:set_int("item_ticks", itemcnt) + end + -- consume fuel tick + if fuel == 0 then + if not inv:is_empty("fuel") then + inv:remove_item("fuel", + ItemStack("tubelib_addons1:biogas 1")) + fuel = BIOGAS_TICKS + else + machine_fault(pos, meta) -- oops + return false + end + end + meta:set_int("fuel_ticks", fuel - 1) + end + meta:set_int("tubelib_countdown", COUNTDOWN_TICKS) + meta:set_int("desired_state", tubelib.RUNNING) + meta:set_string("formspec", formspec(machine, pos, meta)) + return true +end + +--[[ + ----------------- + Node registration + ----------------- +]]-- + +minetest.register_node("biogasmachines:jetfurnace", { + description = "Tubelib Biogas Jet Furnace", + tiles = { + -- up, down, right, left, back, front + "biogasmachines_jetfurnace_top.png", + "biogasmachines_bottom.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + }, + drawtype = "nodebox", + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = { choppy = 2, cracky = 2, crumbly = 2 }, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + drop = "", + can_dig = can_dig, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + machine:after_dig_node(pos, oldnode, oldmetadata, digger) + after_dig_node(pos, oldnode, oldmetadata, digger) + end, + + on_rotate = screwdriver.disallow, + on_timer = on_timer, + on_receive_fields = on_receive_fields, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, + after_place_node = after_place_node, +}) + +minetest.register_node("biogasmachines:jetfurnace_active", { + description = "Tubelib Biogas Jet Furnace", + tiles = { + -- up, down, right, left, back, front + { + image = "biogasmachines_jetfurnace_active_top.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 1.5, + }, + }, + "biogasmachines_bottom.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + "biogasmachines_jetfurnace_side.png", + }, + drawtype = "nodebox", + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = { crumbly = 0, not_in_creative_inventory = 1 }, + is_ground_content = false, + light_source = 6, + sounds = default.node_sound_metal_defaults(), + + drop = "", + can_dig = can_dig, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + machine:after_dig_node(pos, oldnode, oldmetadata, digger) + after_dig_node(pos, oldnode, oldmetadata, digger) + end, + + on_rotate = screwdriver.disallow, + on_timer = on_timer, + on_receive_fields = on_receive_fields, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, +}) + +minetest.register_node("biogasmachines:jetfurnace_defect", { + description = "Tubelib Biogas Jet Furnace", + tiles = { + -- up, down, right, left, back, front + "biogasmachines_jetfurnace_top.png", + "biogasmachines_bottom.png", + "biogasmachines_jetfurnace_side.png^tubelib_defect.png", + "biogasmachines_jetfurnace_side.png^tubelib_defect.png", + "biogasmachines_jetfurnace_side.png^tubelib_defect.png", + "biogasmachines_jetfurnace_side.png^tubelib_defect.png", + }, + drawtype = "nodebox", + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = { choppy = 2, cracky = 2, crumbly = 2, not_in_creative_inventory = 1 }, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + can_dig = can_dig, + after_dig_node = after_dig_node, + on_rotate = screwdriver.disallow, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + after_place_node(pos, placer, itemstack, pointed_thing) + machine:defect(pos, minetest.get_meta(pos)) + end, +}) + +tubelib.register_node("biogasmachines:jetfurnace", + { "biogasmachines:jetfurnace_active", "biogasmachines:jetfurnace_defect" }, { + + on_push_item = function(pos, side, item) + local meta = minetest.get_meta(pos) + if item:get_name() == "tubelib_addons1:biogas" then + return tubelib.put_item(meta, "fuel", item) + end + return tubelib.put_item(meta, "src", item) + end, + + on_pull_item = function(pos, side) + local meta = minetest.get_meta(pos) + return tubelib.get_item(meta, "dst") + end, + + on_pull_stack = function(pos, side) + local meta = minetest.get_meta(pos) + return tubelib.get_stack(meta, "dst") + end, + + on_unpull_item = function(pos, side, item) + local meta = minetest.get_meta(pos) + return tubelib.put_item(meta, "dst", item) + end, + + on_recv_message = function(pos, topic, payload) + local meta = minetest.get_meta(pos) + if topic == "fuel" then + return tubelib.fuelstate(meta, "fuel") + end + local resp = machine:on_receive_message(pos, topic, payload) + if resp then + return resp + else + return "unsupported" + end + end, + + on_node_load = function(pos) + machine:on_node_load(pos) + end, + + on_node_repair = function(pos) + return machine:on_node_repair(pos) + end, +}) + +--[[ + -------- + Crafting + -------- +]]-- + +minetest.register_craft({ + output = "biogasmachines:jetfurnace", + recipe = { + { "default:obsidian_block", "biogasmachines:gasfurnace", "" }, + { "biogasmachines:gasfurnace", "default:goldblock", "" }, + { "", "", "" }, + }, +}) diff --git a/furnace/furnace_top.lua b/furnace/furnace_top.lua new file mode 100644 index 0000000..e7eb767 --- /dev/null +++ b/furnace/furnace_top.lua @@ -0,0 +1,259 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA3 Industrial Furnace Top + +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P = minetest.string_to_pos +local M = minetest.get_meta +-- Consumer Related Data +local CRD = function(pos) return (minetest.registered_nodes[minetest.get_node(pos).name] or {}).consumer end + + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local I,_ = dofile(MP.."/intllib.lua") + +local STANDBY_TICKS = 6 +local COUNTDOWN_TICKS = 6 +local CYCLE_TIME = 2 + +local smelting = techage.furnace.smelting +local get_output = techage.furnace.get_output +local num_recipes = techage.furnace.get_num_recipes +local reset_cooking = techage.furnace.reset_cooking + +local function formspec(self, pos, mem) + local idx = mem.recipe_idx or 1 + local num, output = num_recipes(), get_output(idx) + return "size[8,7.2]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[context;src;0,0;2,2;]".. + "image[2,0.5;1,1;techage_form_arrow.png]".. + "image_button[2,2;1,1;".. self:get_state_button_image(mem) ..";state_button;]".. + "list[context;dst;3,0;2,2;]".. + + "label[6,0;"..I("Outp")..": "..idx.."/"..num.."]".. + "item_image_button[6.5,0.5;1,1;"..output..";b1;]".. + "button[6,1.5;1,1;priv;<<]".. + "button[7,1.5;1,1;next;>>]".. + + "list[current_player;main;0,3.5;8,4;]" .. + "listring[current_player;main]".. + "listring[context;src]" .. + "listring[current_player;main]".. + "listring[context;dst]" .. + "listring[current_player;main]".. + default.get_hotbar_bg(0, 4) +end + +local function on_rightclick(pos, node, clicker) + local mem = tubelib2.get_mem(pos) + M(pos):set_string("formspec", formspec(CRD(pos).State, pos, mem)) +end + +local function firebox_cmnd(pos, cmnd) + return techage.transfer( + {x=pos.x, y=pos.y-1, z=pos.z}, + nil, -- outdir + cmnd, -- topic + nil, -- payload + nil, -- network + {"techage:furnace_firebox", "techage:furnace_firebox_on"}) +end + +local function cooking(pos, crd, mem, elapsed) + if firebox_cmnd(pos, "fuel") then + local state = smelting(pos, mem, elapsed) + --print("cooking", techage.StateStrings[state]) + if state == techage.RUNNING then + crd.State:keep_running(pos, mem, COUNTDOWN_TICKS) + elseif state == techage.BLOCKED then + crd.State:blocked(pos, mem) + elseif state == techage.STANDBY then + crd.State:idle(pos, mem) + end + else + crd.State:fault(pos, mem) + end +end + +local function keep_running(pos, elapsed) + local mem = tubelib2.get_mem(pos) + local crd = CRD(pos) + cooking(pos, crd, mem, elapsed) + return crd.State:is_active(mem) +end + +local function allow_metadata_inventory_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + if listname == "src" then + CRD(pos).State:start_if_standby(pos) + return stack:get_count() + elseif listname == "dst" then + return 0 + end +end + +local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local stack = inv:get_stack(from_list, from_index) + return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) +end + +local function allow_metadata_inventory_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + return stack:get_count() +end + +local function on_receive_fields(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + local mem = tubelib2.get_mem(pos) + mem.recipe_idx = mem.recipe_idx or 1 + if fields.next == ">>" then + mem.recipe_idx = math.min(mem.recipe_idx + 1, num_recipes()) + M(pos):set_string("formspec", formspec(CRD(pos).State, pos, mem)) + reset_cooking(mem) + elseif fields.priv == "<<" then + mem.recipe_idx = math.max(mem.recipe_idx - 1, 1) + M(pos):set_string("formspec", formspec(CRD(pos).State, pos, mem)) + reset_cooking(mem) + end + CRD(pos).State:state_button_event(pos, mem, fields) +end + +local function can_dig(pos, player) + if minetest.is_protected(pos, player:get_player_name()) then + return false + end + local inv = M(pos):get_inventory() + return inv:is_empty("dst") and inv:is_empty("src") +end + +local function on_node_state_change(pos, old_state, new_state) + if new_state == techage.RUNNING then + firebox_cmnd(pos, "start") + else + firebox_cmnd(pos, "stop") + end + local mem = tubelib2.get_mem(pos) + reset_cooking(mem) +end + +local tiles = {} + +-- '#' will be replaced by the stage number +tiles.pas = { + -- up, down, right, left, back, front + "techage_concrete.png^techage_frame_ta#_top.png", + "techage_concrete.png^techage_frame_ta#_top.png", + "techage_concrete.png^techage_frame_ta#.png", + "techage_concrete.png^techage_frame_ta#.png", + "techage_concrete.png^techage_frame_ta#.png", + "techage_concrete.png^techage_appl_furnace.png^techage_frame_ta#.png", +} +tiles.act = tiles.pas +tiles.def = { + -- up, down, right, left, back, front + "techage_concrete.png^techage_frame_ta#_top.png", + "techage_concrete.png^techage_frame_ta#_top.png", + "techage_concrete.png^techage_frame_ta#.png^techage_appl_defect.png", + "techage_concrete.png^techage_frame_ta#.png^techage_appl_defect.png", + "techage_concrete.png^techage_frame_ta#.png^techage_appl_defect.png", + "techage_concrete.png^techage_appl_furnace.png^techage_frame_ta#.png^techage_appl_defect.png", +} + +local tubing = { + on_pull_item = function(pos, in_dir, num) + local meta = minetest.get_meta(pos) + if meta:get_int("pull_dir") == in_dir then + local inv = M(pos):get_inventory() + return techage.get_items(inv, "dst", num) + end + end, + on_push_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + if meta:get_int("push_dir") == in_dir or in_dir == 5 then + local inv = M(pos):get_inventory() + return techage.put_items(inv, "src", stack) + end + end, + on_unpull_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + if meta:get_int("pull_dir") == in_dir then + local inv = M(pos):get_inventory() + return techage.put_items(inv, "dst", stack) + end + end, + on_recv_message = function(pos, topic, payload) + local resp = CRD(pos).State:on_receive_message(pos, topic, payload) + if resp then + return resp + else + return "unsupported" + end + end, + on_node_load = function(pos) + CRD(pos).State:on_node_load(pos) + end, + on_node_repair = function(pos) + return CRD(pos).State:on_node_repair(pos) + end, +} + +local _, node_name_ta3, _ = + techage.register_consumer("furnace", I("TA3 Furnace Top"), tiles, { + drawtype = "normal", + cycle_time = CYCLE_TIME, + standby_ticks = STANDBY_TICKS, + has_item_meter = true, + aging_factor = 10, + formspec = formspec, + tubing = tubing, + on_state_change = on_node_state_change, + after_place_node = function(pos, placer) + local inv = M(pos):get_inventory() + inv:set_size("src", 2*2) + inv:set_size("dst", 2*2) + end, + can_dig = can_dig, + node_timer = keep_running, + on_receive_fields = on_receive_fields, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, + groups = {choppy=2, cracky=2, crumbly=2}, + sounds = default.node_sound_wood_defaults(), + num_items = {0,1,1,1}, + }, + {false, false, true, false}) -- TA3 only + + + +minetest.register_craft({ + output = node_name_ta3, + recipe = { + {"", "techage:baborium_ingot", ""}, + {"", "default:furnace", ""}, + {"", "techage:vacuum_tube", ""}, + }, +}) diff --git a/furnace/recipes.lua b/furnace/recipes.lua new file mode 100644 index 0000000..a568153 --- /dev/null +++ b/furnace/recipes.lua @@ -0,0 +1,56 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Cooking recipes for furnace + +]]-- + + +techage.furnace.register_recipe({ + output = "techage:iron_ingot", + recipe = {"default:iron_lump"}, + time = 2, +}) + +if techage.modified_recipes_enabled then + techage.ironage_register_recipe({ + output = "default:bronze_ingot 4", + recipe = {"default:copper_ingot", "default:copper_ingot", "default:copper_ingot", "default:tin_ingot"}, + time = 2, + }) + + techage.furnace.register_recipe({ + output = "default:steel_ingot 4", + recipe = {"default:coal_lump", "default:iron_lump", "default:iron_lump", "default:iron_lump"}, + time = 4, + }) +end + + +minetest.after(1, function() + for key,_ in pairs(minetest.registered_items) do + if key ~= "" then + local tbl = minetest.get_all_craft_recipes(key) + if tbl then + for _,recipe in ipairs(tbl) do + if recipe and recipe.method == "cooking" then + --print(dump(idef), dump(recipe)) + --print(key, recipe.width) + techage.furnace.register_recipe({ + output = recipe.output, + recipe = recipe.items, + time = math.floor((recipe.width + 1) / 2), + }) + end + end + end + end + end +end) diff --git a/init.lua b/init.lua index dc890c9..204ccc4 100644 --- a/init.lua +++ b/init.lua @@ -104,6 +104,13 @@ else dofile(MP.."/coal_power_station/cooler.lua") dofile(MP.."/coal_power_station/akkubox.lua") + -- Industrial Furnace + dofile(MP.."/furnace/firebox.lua") + dofile(MP.."/furnace/cooking.lua") + dofile(MP.."/furnace/furnace_top.lua") + dofile(MP.."/furnace/booster.lua") + dofile(MP.."/furnace/recipes.lua") + -- Lamps dofile(MP.."/lamps/lib.lua") dofile(MP.."/lamps/simplelamp.lua") diff --git a/lamps/lib.lua b/lamps/lib.lua index 7275ac5..1c68204 100644 --- a/lamps/lib.lua +++ b/lamps/lib.lua @@ -122,6 +122,6 @@ function techage.register_lamp(basename, ndef_off, ndef_on) on_power_pass1 = on_power_pass1, on_power_pass2 = on_power_pass2, power_network = Power, - conn_sides = determine_power_side, -- will be handled by clbk function + conn_sides = ndef_off.conn_sides or determine_power_side, -- will be handled by clbk function }) end diff --git a/lamps/simplelamp.lua b/lamps/simplelamp.lua index a228411..2147ddb 100644 --- a/lamps/simplelamp.lua +++ b/lamps/simplelamp.lua @@ -21,7 +21,7 @@ techage.register_lamp("techage:simplelamp", { tiles = { 'techage_electric_button.png', }, - + conn_sides = {"L", "R", "U", "D", "F", "B"}, paramtype = "light", light_source = 0, sunlight_propagates = true, @@ -34,6 +34,7 @@ techage.register_lamp("techage:simplelamp", { tiles = { 'techage_electric_button.png', }, + conn_sides = {"L", "R", "U", "D", "F", "B"}, paramtype = "light", light_source = minetest.LIGHT_MAX, sunlight_propagates = true, diff --git a/oil/oilpump.lua b/oil/oilpump.lua deleted file mode 100644 index 2892ad8..0000000 --- a/oil/oilpump.lua +++ /dev/null @@ -1,31 +0,0 @@ -local function formspec(self, pos, mem) - if not mem.tower_built then - return formspec0 - end - local icon = "techage_oil_inv" - local depth = "1/480" - return "size[8,8]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "list[context;src;1,1;1,1;]".. - "label[1.3,0.5;IN]".. - "item_image[1,1;1,1;techage:oil_drillbit]".. - "label[1,2;"..I("Drill Bit").."]".. - "label[0.5,3;"..I("Depth")..": "..depth.."]".. - "image[3.5,0;1,1;"..techage.get_power_image(pos, mem).."]".. - "image[3.5,1;1,1;techage_form_arrow.png]".. - "image_button[3.5,2;1,1;".. self:get_state_button_image(mem) ..";state_button;]".. - "label[6.2,0.5;OUT]".. - "list[context;dst;6,1;1,1;]".. - "label[6.2,2;"..I("Oil").."]".. - "label[5.5,3;"..I("Extract")..": "..depth.."]".. - "item_image[6,1;1,1;techage:oil_source]".. - "button_exit[0,3.9;3,1;destroy;"..I("Destroy Tower").."]".. - "list[current_player;main;0,4;8,4;]".. - "listring[context;dst]".. - "listring[current_player;main]".. - "listring[context;src]".. - "listring[current_player;main]".. - default.get_hotbar_bg(0, 4) -end diff --git a/sounds/techage_booster.ogg b/sounds/techage_booster.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ad406f01d85f3a6a282912483813fe1fae6fc2e1 GIT binary patch literal 17790 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 yu-ADlTNilczNS%G}U;vjb? zhIQv;UIICs1s;*b3=G`DAk4@xYmNj^P$Ix5#MLLWaPGz(w;w-sadlmH_*7hceCyQN zg+)aRWuG5=N4AD5hb)q9*lLL>dw@=LHiyM|#F>F*^=lhK7 zP2y$l4Ih5>m1r!UsG9#h{*(IUX&Lrh3(gp8J!6w&wPD^^GhugU_nm*-LVioQLc24} zTFhs>)vvu?f8vO?k?oB254~GXOb8ECv}m8H?sK8T#aG%%?BV5Y)BNq;_A3MmYwa`c keSdA1{?`>&Zz>L(yRBEIqP)z^ zfH%tGaPQx?P4lo8`0YG7Npai&+lMD`#xOE@*33R_r+Fc4bQ9T3G}w0yk{lgO;{*02 zq%(}d)GPGV5ck+`lk8|}V&vH4B`E`?=GwEiCwH-Yy%Ah85XvrFi0 ze$vMTp|GIjFG>2(liLj}#feUTYZ$0x@eYC;?nF|T5`NXh7pTMa6WuzOQrCD34w7_R zHRo=-UPj))rp1@%63SZF{G1r*-Y#=p*P22OB@w(>2_Y|AQoBdy5nA63F@-hHbIM<_ z!ZaGr<=%!|@pFgW8w~GF&sZYZg7+6F-T^vIyZoQr1>o4stalGCl&G$fdxZL}7*Y#U2 zEy>c(_{CfGSnTWL(4P{T+gF56b=KPIwd6ur0%IFn#QL|XIyW1V)?dEVJoDnMN0*wn zPp{P5ZEbA5{`?u6|MxF_et!S{yEktPc1T@Xah~dI4#mWU!eb(%cr7cdJfx5i)#;G^0)tA zv3uvomv>Lt7ggL?qQX+Esj0ebQ`G89e7C+VO?mw~_urqt{0v@BXP!)LIq)^6>aN^@ zGu93V^(LKP&-CHbQ}0dNaz7{jX?(m`%!y;3?eBNd59S<=NG|_=RQvq;3Fp^;{P9^m zrC#^+qhG(;w*Hx=Rl+w%Zw2#|mJ9z>TNIR+9uZ8>{bDD!Dav%Hk!7S?&xe zI@vQ1UKPkzb^m|PT0FB>bLG`6o^@?M>RNqd{0n5-r^e(RdoBKwS$pT5yX$682(7-f zX6-S6wbk4M{_8Tkf|@HyCUT$8 zUS7WPhr`yeqk{FRZjV2fh}+bCn%S3hX+znCWdS-b4D^MM3pXhoD7K9`@4HkO7{A90 zJ071b5s#R5G{a`u&iV;m!pA@GH&YxweVo5P_$KSg z29?=oe3Ch1&a2L@TyXe{kCBU4GUp`4T~j8#x%u1pn*3Vh9a5|6XRq3L;G1-SW delta 1066 zcmeyyahqd;Lp_s_2%~tc@rFwb49vegT^vIyZoQr1>;KqI;&{EwK}RO*scLE)KRZP) zP!HR^eH;6c-u0VasYiV@ymc>NHP_m0yF+$sZK-zP=JZxDJNRS5gpAJO1?_Ko=1lSC zFVsI@ESY|6Ub@kFWBvd458b=h-G29OY*l&ftF2ND?#GX7EM?eGzsB{@2dVR`83fv{ zGd%mgruE_0pJms&wmH9kZ0V&5RCqS5yVd@loZXI9En7nrx|A3usT>SdUG!sG*4C_7 zvDZ!K>WXreUq+~(#jCWWqi_1YyhTlH`0{Q2^^pTBo2TcOPC zdNT>0!cfkHtTcwlYwo{(EK)D~Ywy0y;;z2R@ ztuJfdynjD=d+u!i#r|{VnlV=V`_nG_VA9Ws+WhxN9gnWwuxZnoW9GA;=$=k8kl0ke zXVaMigD0Cmf41ZWSx~3B^u*D^p4DDAw)swbwo0f_X7XikH+3(uW#KFi=QfGGy!&YKHjD0-FhhG+IeI4X zW_;>sq0hy#7bR9R-?aRyy7#gqz-yv^yob)|Qz@cL^i_VAB#S9#tTENN)wgrm^Go4N zbetyaTXek8XJ75bWgD+8D8GJPJ+!v(-|Iwy1+5c;>bq(rSll!!_MN{NUoZEMC;Otz%Bq0xizjAY|NBDba#G5+^X^A8 zqmq`tzAO~8|9eDj{iL3=<^7(ecgwy#EU{w$60*EM?$o B73BZ` diff --git a/textures/techage_frame4_ta3_top.png b/textures/techage_frame4_ta3_top.png index e0866fd0dfb14d05d740b2ff5c5754f98f637ec3..a0660e3dd327f4577ee102b31df08310a9ad01c8 100644 GIT binary patch delta 1093 zcmcc3@r`4GLp>W8w~GF&sZYZg7+6F-T^vIyZoQr1>o4stalGCl&G$fdxZL}7*Y#U2 zEy>c(_{CfGSnTWL(4P{T+gF56b=KPIwd6ur0%IFn#QL|XIyW1V)?dEVJoDnMN0*wn zPp{P5ZEbA5{`?u6|MxF_et!S{yEktPc1T@Xah~dI4#mWU!eb(%cr7cdJfx5i)#;G^0)tA zv3uvomv>Lt7ggL?qQX+Esj0ebQ`G89e7C+VO?mw~_urqt{0v@BXP!)LIq)^6>aN^@ zGu93V^(LKP&-CHbQ}0dNaz7{jX?(m`%!y;3?eBNd59S<=NG|_=RQvq;3Fp^;{P9^m zrC#^+qhG(;w*Hx=Rl+w%Zw2#|mJ9z>TNIR+9uZ8>{bDD!Dav%Hk!7S?&xe zI@vQ1UKPkzb^m|PT0FB>bLG`6o^@?M>RNqd{0n5-r^e(RdoBKwS$pT5yX$682(7-f zX6-S6wbk4M{_8Tkf|@HyCUT$8 zUS7WPhr`yeqk{FRZjV2fh}+bCn%S3hX+znCWdS-b4D^MM3pXhoD7K9`@4HkO7{A90 zJ071b5s#R5G{a`u&iV;m!pA@GH&YxweVo5P_$KSg z29?=oe3Ch1&a2L@TyXe{kCBU4GUp`4T~j8#x%u1pn*3Vh9a5|6XRq3L;G1-SW delta 1066 zcmeyyahqd;Lp_s_2%~tc@rFwb49vegT^vIyZoQr1>;KqI;&{EwK}RO*scLE)KRZP) zP!HR^eH;6c-u0VasYiV@ymc>NHP_m0yF+$sZK-zP=JZxDJNRS5gpAJO1?_Ko=1lSC zFVsI@ESY|6Ub@kFWBvd458b=h-G29OY*l&ftF2ND?#GX7EM?eGzsB{@2dVR`83fv{ zGd%mgruE_0pJms&wmH9kZ0V&5RCqS5yVd@loZXI9En7nrx|A3usT>SdUG!sG*4C_7 zvDZ!K>WXreUq+~(#jCWWqi_1YyhTlH`0{Q2^^pTBo2TcOPC zdNT>0!cfkHtTcwlYwo{(EK)D~Ywy0y;;z2R@ ztuJfdynjD=d+u!i#r|{VnlV=V`_nG_VA9Ws+WhxN9gnWwuxZnoW9GA;=$=k8kl0ke zXVaMigD0Cmf41ZWSx~3B^u*D^p4DDAw)swbwo0f_X7XikH+3(uW#KFi=QfGGy!&YKHjD0-FhhG+IeI4X zW_;>sq0hy#7bR9R-?aRyy7#gqz-yv^yob)|Qz@cL^i_VAB#S9#tTENN)wgrm^Go4N zbetyaTXek8XJ75bWgD+8D8GJPJ+!v(-|Iwy1+5c;>bq(rSll!!_MN{NUoZEMC;Otz%Bq0xizjAY|NBDba#G5+^X^A8 zqmq`tzAO~8|9eDj{iL3=<^7(ecgwy#EU{w$60*EM?$o B73BZ` diff --git a/textures/techage_frame_ta3.png b/textures/techage_frame_ta3.png index 2b2e58b7b8ca9fb42beab6915b88ccaf8b30b0bc..bc2bb4914597017d31fffd597c844091be822a41 100644 GIT binary patch delta 383 zcmV-_0f7G52aW@f7=Hu<0001iRAg?&q$i}_D zys)vaj6DHm00003bW%=JApkxCnDwFn008YtL_t(2&s~yBZhylt3`D0rL3@MVf%uA;THi67ZHq2F!zDx)g~zyk;Z^UOhWdW{eAP!7F~m_U|!h d{eKhs{sVzESaM>0`Xm4V002ovPDHLkV1kM8on8O{ delta 955 zcmV;s14R6e1K9_V7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Re z0uvDz0SgEf+5i9pW=TXrR9M5kmrZXPR}_XH5FU{187f@{m;r{iw!3na?9whQ@BCMy z{-Ve~qwUUWRasG*xJnee&B7{QF*Y;c@m+vHfnh-x%aA7VS?Rgb>uK zRa)&fsXyPHFKki3$Ylw`5P&F(utkAT-czYm^t^4`T3IfeWpFp3R4nQ7=Js}F&--`p zd2(<7Kq&8*I%2lE*IiOS|MJtqt9wW(^**Cw5zX9Oyj~@wqAIEj|sAO+{mu90y;QJgN9rEhz z)$+owyRzoEE?(WEd))RQ4hRsOr=WfaAIvUqT4dQc7%5z%)&g6k8OKQj%6Fqkxix zsEhze8^#vHhY?0DOW^w(Hy-#tl}d%-!wAPIla8V}wr!KkW>-+ZNetw&*)R2GPDbUV zVQeMh0gVCM=IZK-v?{5kVu{0}!$d+bxEtuAmyR#0;Vq(;-lp1C}AERjc|XOG4BOszVTe z@shwm`%_0}f=frl;dAQW7DEx9Czz zGTrMgUfpB(Fj6m7ZG-k}f$yV;kPw1Q;QN{=IIhdZe_tQY)TPefywxN`jd3EfPFB?S z^B=zd4;fMV9M>gpTAC-OI-S;Hor1O6?f*s1;-e~PQ8XuSS}W8{iPHJ{^&d-a_~hVV zg`CC@#^)9B48st^*wRm`y3}+w)9Xr)!k8i>>pCALr*VoMpN~^;wePX{D4OeYk5P5C d*8Hcz6~8BY=)fg?&q$i}_D zys)vaj6DHm00003bW%=JApkxCnDwFn008YtL_t(2&s~yBZhylt3`D0rL3@MVf%uA;THi67ZHq2F!zDx)g~zyk;Z^UOhWdW{eAP!7F~m_U|!h d{eKhs{sVzESaM>0`Xm4V002ovPDHLkV1kM8on8O{ delta 955 zcmV;s14R6e1K9_V7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Re z0uvDz0SgEf+5i9pW=TXrR9M5kmrZXPR}_XH5FU{187f@{m;r{iw!3na?9whQ@BCMy z{-Ve~qwUUWRasG*xJnee&B7{QF*Y;c@m+vHfnh-x%aA7VS?Rgb>uK zRa)&fsXyPHFKki3$Ylw`5P&F(utkAT-czYm^t^4`T3IfeWpFp3R4nQ7=Js}F&--`p zd2(<7Kq&8*I%2lE*IiOS|MJtqt9wW(^**Cw5zX9Oyj~@wqAIEj|sAO+{mu90y;QJgN9rEhz z)$+owyRzoEE?(WEd))RQ4hRsOr=WfaAIvUqT4dQc7%5z%)&g6k8OKQj%6Fqkxix zsEhze8^#vHhY?0DOW^w(Hy-#tl}d%-!wAPIla8V}wr!KkW>-+ZNetw&*)R2GPDbUV zVQeMh0gVCM=IZK-v?{5kVu{0}!$d+bxEtuAmyR#0;Vq(;-lp1C}AERjc|XOG4BOszVTe z@shwm`%_0}f=frl;dAQW7DEx9Czz zGTrMgUfpB(Fj6m7ZG-k}f$yV;kPw1Q;QN{=IIhdZe_tQY)TPefywxN`jd3EfPFB?S z^B=zd4;fMV9M>gpTAC-OI-S;Hor1O6?f*s1;-e~PQ8XuSS}W8{iPHJ{^&d-a_~hVV zg`CC@#^)9B48st^*wRm`y3}+w)9Xr)!k8i>>pCALr*VoMpN~^;wePX{D4OeYk5P5C d*8Hcz6~8BY=)f