From c1b49002b24508eab2dae5f187b76bae46410f92 Mon Sep 17 00:00:00 2001 From: Joachim Stolberg Date: Mon, 18 Mar 2019 22:40:17 +0100 Subject: [PATCH] iron age nodes added coal power station firebox added --- basic_machines/grinder.lua | 4 +- basis/firebox.lua | 110 ++++ basis/guide.lua | 557 +++++++++++++++++++++ {steam_engine => basis}/steam_pipe.lua | 0 coal_power_station/boiler.lua | 340 +++++++++++++ coal_power_station/firebox.lua | 178 +++++++ coal_power_station/turbine.lua | 149 ++++++ depends.txt | 4 - init.lua | 16 +- iron_age/charcoalpile.lua | 214 ++++++++ iron_age/coalburner.lua | 191 +++++++ iron_age/gravelsieve.lua | 2 +- iron_age/lighter.lua | 91 ++++ iron_age/main.lua | 29 ++ iron_age/meltingpot.lua | 534 ++++++++++++++++++++ iron_age/meridium.lua | 128 +++++ mod.conf | 3 +- steam_engine/firebox.lua | 119 +---- textures/techage_appl_distri.png | Bin 1547 -> 471 bytes textures/techage_appl_distri4.png | Bin 6248 -> 1134 bytes textures/techage_appl_distri_blue.png | Bin 277 -> 212 bytes textures/techage_appl_distri_green.png | Bin 272 -> 212 bytes textures/techage_appl_distri_red.png | Bin 272 -> 212 bytes textures/techage_appl_distri_yellow.png | Bin 273 -> 212 bytes textures/techage_appl_firehole4.png | Bin 1046 -> 1034 bytes textures/techage_appl_grinder2.png | Bin 0 -> 468 bytes textures/techage_appl_hole_electric.png | Bin 259 -> 199 bytes textures/techage_appl_sieve.png | Bin 264 -> 261 bytes textures/techage_appl_sieve4.png | Bin 355 -> 352 bytes textures/techage_appl_sieve4_top.png | Bin 6442 -> 963 bytes textures/techage_appl_sieve_top.png | Bin 920 -> 171 bytes textures/techage_ash.png | Bin 0 -> 268 bytes textures/techage_ash_side.png | Bin 0 -> 263 bytes textures/techage_charcoal.png | Bin 0 -> 762 bytes textures/techage_charcoal_burn.png | Bin 0 -> 1104 bytes textures/techage_coal_boiler.png | Bin 0 -> 513 bytes textures/techage_coal_boiler4.png | Bin 0 -> 540 bytes textures/techage_coal_boiler_fire_hole.png | Bin 0 -> 341 bytes textures/techage_coal_boiler_hole.png | Bin 0 -> 269 bytes textures/techage_coal_boiler_inv.png | Bin 0 -> 1748 bytes textures/techage_coal_boiler_mesh.png | Bin 0 -> 1453 bytes textures/techage_electric_junction.png | Bin 677 -> 328 bytes textures/techage_frame4_ta4_top.png | Bin 317 -> 209 bytes textures/techage_handsieve_sieve.png | Bin 1562 -> 467 bytes textures/techage_handsieve_top.png | Bin 934 -> 315 bytes textures/techage_lighter.png | Bin 0 -> 762 bytes textures/techage_lighter_burn.png | Bin 0 -> 924 bytes textures/techage_meltingpot.png | Bin 0 -> 255 bytes textures/techage_meltingpot_top_active.png | Bin 0 -> 634 bytes textures/techage_meridium_ingot.png | Bin 0 -> 266 bytes textures/techage_meridiumaxe.png | Bin 0 -> 248 bytes textures/techage_meridiumpick.png | Bin 0 -> 261 bytes textures/techage_meridiumshovel.png | Bin 0 -> 257 bytes textures/techage_meridiumsword.png | Bin 0 -> 230 bytes textures/techage_smoke.png | Bin 0 -> 291 bytes textures/techage_tool_hammer_diamond.png | Bin 1466 -> 689 bytes textures/techage_tool_hammer_steel.png | Bin 1426 -> 697 bytes 57 files changed, 2564 insertions(+), 105 deletions(-) create mode 100644 basis/firebox.lua create mode 100644 basis/guide.lua rename {steam_engine => basis}/steam_pipe.lua (100%) create mode 100644 coal_power_station/boiler.lua create mode 100644 coal_power_station/firebox.lua create mode 100644 coal_power_station/turbine.lua delete mode 100644 depends.txt create mode 100644 iron_age/charcoalpile.lua create mode 100644 iron_age/coalburner.lua create mode 100644 iron_age/lighter.lua create mode 100644 iron_age/main.lua create mode 100644 iron_age/meltingpot.lua create mode 100644 iron_age/meridium.lua create mode 100644 textures/techage_appl_grinder2.png create mode 100644 textures/techage_ash.png create mode 100644 textures/techage_ash_side.png create mode 100644 textures/techage_charcoal.png create mode 100644 textures/techage_charcoal_burn.png create mode 100644 textures/techage_coal_boiler.png create mode 100644 textures/techage_coal_boiler4.png create mode 100644 textures/techage_coal_boiler_fire_hole.png create mode 100644 textures/techage_coal_boiler_hole.png create mode 100644 textures/techage_coal_boiler_inv.png create mode 100644 textures/techage_coal_boiler_mesh.png create mode 100644 textures/techage_lighter.png create mode 100644 textures/techage_lighter_burn.png create mode 100644 textures/techage_meltingpot.png create mode 100644 textures/techage_meltingpot_top_active.png create mode 100644 textures/techage_meridium_ingot.png create mode 100644 textures/techage_meridiumaxe.png create mode 100644 textures/techage_meridiumpick.png create mode 100644 textures/techage_meridiumshovel.png create mode 100644 textures/techage_meridiumsword.png create mode 100644 textures/techage_smoke.png diff --git a/basic_machines/grinder.lua b/basic_machines/grinder.lua index 2f947fa..9a6c161 100644 --- a/basic_machines/grinder.lua +++ b/basic_machines/grinder.lua @@ -263,8 +263,8 @@ if minetest.global_exists("unified_inventory") then unified_inventory.register_craft_type("grinding", { description = I("Grinding"), icon = 'techage_appl_grinder.png', - width = 1, - height = 1, + width = 2, + height = 2, }) end diff --git a/basis/firebox.lua b/basis/firebox.lua new file mode 100644 index 0000000..d41e203 --- /dev/null +++ b/basis/firebox.lua @@ -0,0 +1,110 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Firebox basic functions + +]]-- + +-- 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") + +techage.firebox = {} + +local BURN_TIME_FACTOR = 2 + +techage.firebox.Burntime = { + ["techage:charcoal"] = true, -- will be replaced by burntime + ["default:coal_lump"] = true, + ["default:coalblock"] = true, +} + +local function determine_burntimes() + for k,_ in pairs(techage.firebox.Burntime)do + local fuel,_ = minetest.get_craft_result({method = "fuel", width = 1, items = {k}}) + techage.firebox.Burntime[k] = fuel.time * BURN_TIME_FACTOR + end +end +minetest.after(1, determine_burntimes) + +function techage.firebox.formspec(mem) + local fuel_percent = 0 + if mem.running then + fuel_percent = ((mem.burn_cycles or 1) * 100) / (mem.burn_cycles_total or 1) + end + 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 + +function techage.firebox.can_dig(pos, player) + local mem = tubelib2.get_mem(pos) + local inv = M(pos):get_inventory() + return inv:is_empty("fuel") and not mem.running +end + +function techage.firebox.allow_metadata_inventory(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + if techage.firebox.Burntime[stack:get_name()] then + return stack:get_count() + end + return 0 +end + +function techage.firebox.on_receive_fields(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + + if fields.update then + local mem = tubelib2.get_mem(pos) + M(pos):set_string("formspec", techage.firebox.formspec(mem)) + end +end + +function techage.firebox.on_rightclick(pos, node, clicker) + local mem = tubelib2.get_mem(pos) + M(pos):set_string("formspec", techage.firebox.formspec(mem)) +end + +function techage.firebox.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 + +function techage.firebox.get_fuel(pos) + local inv = M(pos):get_inventory() + local items = inv:get_stack("fuel", 1) + if items:get_count() > 0 then + local taken = items:take_item(1) + inv:set_stack("fuel", 1, items) + return taken + end +end + diff --git a/basis/guide.lua b/basis/guide.lua new file mode 100644 index 0000000..cd9e3a4 --- /dev/null +++ b/basis/guide.lua @@ -0,0 +1,557 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Construction Guide + +]]-- + + + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + +local SMELTING_TIME = 2 + +local Tabs = S("Manufacture,Construction") + +local Recipes = {} -- registered recipes +local KeyList = {} -- index to Recipes key translation +local NumRecipes = 0 + +-- formspec images +local function plan(images) + local tbl = {} + for y=0,9 do + for x=0,9 do + local img = images[y+1][x+1] or false + if img ~= false then + tbl[#tbl+1] = "item_image["..(x*0.8)..","..(y*0.8)..";0.8,0.8;"..img..".png]" + end + end + end + return table.concat(tbl) +end + +local function formspec_manufacture(idx) + idx = math.min(idx, #KeyList) + local key = KeyList[idx] + local input1 = Recipes[key].input[1] or "" + local input2 = Recipes[key].input[2] or "" + local input3 = Recipes[key].input[3] or "" + local input4 = Recipes[key].input[4] or "" + local num = Recipes[key].number + local output = Recipes[key].output + if num > 1 then + output = output.." "..num + end + return "size[11,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;"..Tabs..";2;;true]".. + "label[1,0.2;"..S("Manufacture Manual").."]".. + + "container[1,1]".. + "item_image_button[0,0;1,1;"..input1..";b1;]".. + "item_image_button[1,0;1,1;"..input2..";b2;]".. + "item_image_button[0,1;1,1;"..input3..";b3;]".. + "item_image_button[1,1;1,1;"..input4..";b4;]".. + "item_image[2.6,0;0.8,0.8;"..Recipes[key].icon.."]".. + "image[2.3,0.6;1.6,1;gui_furnace_arrow_bg.png^[transformR270]".. + "item_image_button[4,0.5;1,1;"..output..";b5;]".. + "label[2,2.2;"..Recipes[key].hints.."]".. + "label[2,4;Recipe "..idx.." of "..NumRecipes.."]".. + "button[2,5.5;0.8,0.8;priv;<<]".. + "button[3,5.5;0.8,0.8;next;>>]".. + "container_end[]" +end + +local function formspec_construction(recipe) + return "size[11,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;"..Tabs..";3;;true]".. + "label[0,0;"..BurnerHelp.."]".. + "label[1,5;"..S("Cross-section")..":]".. + "container[4,4]".. + draw(BurnerImages).. + "container_end[]" +end + +local function on_receive_fields(pos, formname, fields, sender) + local meta = minetest.get_meta(pos) + local recipe_idx = meta:get_int("recipe_idx") + if recipe_idx == 0 then recipe_idx = 1 end + if fields.tab == "1" then + meta:set_string("formspec", formspec1) + elseif fields.tab == "2" then + meta:set_string("formspec", formspec2(recipe_idx)) + elseif fields.next == ">>" then + recipe_idx = math.min(recipe_idx + 1, NumRecipes) + meta:set_int("recipe_idx", recipe_idx) + meta:set_string("formspec", formspec2(recipe_idx)) + elseif fields.priv == "<<" then + recipe_idx = math.max(recipe_idx - 1, 1) + meta:set_int("recipe_idx", recipe_idx) + meta:set_string("formspec", formspec2(recipe_idx)) + end +end + +local function can_dig(pos, player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + return inv:is_empty("dst") and inv:is_empty("src") +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 + 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 + +-- generate an unique key based on the unsorted and +-- variable number of inventory items +local function recipe_key(items) + local tbl = {} + -- remove items which exist more than once + for _,item in ipairs(items) do + tbl[item] = true + end + local names = {} + for key,_ in pairs(tbl) do + names[#names + 1] = key + end + -- bring in a sorted order + table.sort(names) + return table.concat(names, "-") +end + +-- determine recipe based on inventory items +local function get_recipe(inv) + -- collect items + local stacks = {} + local names = {} + for _,stack in ipairs(inv:get_list("src")) do + if not stack:is_empty() then + table.insert(names, stack:get_name()) + table.insert(stacks, stack) + else + table.insert(stacks, ItemStack("")) + end + end + local key = recipe_key(names) + local recipe = Recipes[key] + + if recipe then + return { + input = recipe.input, + stacks = stacks, + output = ItemStack(recipe.output.." "..recipe.number), + heat = recipe.heat, + time = recipe.time, + } + end + return nil +end + +-- prepare recipe and store in cache table for faster access +local function store_recipe_in_cache(pos) + local hash = minetest.hash_node_position(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local recipe = get_recipe(inv) + Cache[hash] = recipe + return recipe +end + +-- read value from the node below +local function get_heat(pos) + local heat = 0 + pos.y = pos.y - 1 + local node = minetest.get_node(pos) + local meta = minetest.get_meta(pos) + pos.y = pos.y + 1 + if minetest.get_item_group(node.name, "techage_flame") > 0 then + heat = meta:get_int("heat") + end + return heat +end + +-- Start melting if heat is ok AND source items available +function techage.switch_to_active(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + local recipe = store_recipe_in_cache(pos) + + if recipe and heat >= recipe.heat then + minetest.swap_node(pos, {name = "techage:meltingpot_active"}) + minetest.registered_nodes["techage:meltingpot_active"].on_construct(pos) + meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")") + minetest.get_node_timer(pos):start(2) + return true + end + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") + return false +end + +function techage.update_heat(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") +end + +local function set_inactive(meta, pos, heat) + minetest.get_node_timer(pos):stop() + minetest.swap_node(pos, {name = "techage:meltingpot"}) + minetest.registered_nodes["techage:meltingpot"].on_construct(pos) + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") +end + +-- Stop melting if heat to low OR no source items available +local function switch_to_inactive(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + local hash = minetest.hash_node_position(pos) + local recipe = Cache[hash] or store_recipe_in_cache(pos) + + if not recipe or heat < recipe.heat then + set_inactive(meta, pos, heat) + return true + end + meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")") + return false +end + + +local function index(list, x) + for idx, v in pairs(list) do + if v == x then return idx end + end + return nil +end + +-- move recipe src items to output inventory +local function process(inv, recipe, heat) + if heat < recipe.heat then + return false + end + local res = false + if inv:room_for_item("dst", recipe.output) then + for _,item in ipairs(recipe.input) do + res = false + for _, stack in ipairs(recipe.stacks) do + if stack:get_count() > 0 and stack:get_name() == item then + stack:take_item(1) + res = true + break + end + end + if res == false then + return false + end + end + inv:add_item("dst", recipe.output) + inv:set_list("src", recipe.stacks) + return true + end + return false +end + +local function smelting(pos, recipe, heat, elapsed) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + elapsed = elapsed + meta:get_int("leftover") + + while elapsed >= recipe.time do + if process(inv, recipe, heat) == false then + meta:set_int("leftover", 0) + set_inactive(meta, pos, heat) + return false + end + elapsed = elapsed - recipe.time + end + meta:set_int("leftover", elapsed) + return true +end + +local function pot_node_timer(pos, elapsed) + if switch_to_inactive(pos) == false then + local hash = minetest.hash_node_position(pos) + local heat = get_heat(pos) + local recipe = Cache[hash] or store_recipe_in_cache(pos) + if recipe then + return smelting(pos, recipe, heat, elapsed) + end + end + return false +end + +minetest.register_node("techage:meltingpot_active", { + description = S("Melting Pot"), + tiles = { + { + image = "techage_meltingpot_top_active.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1, + }, + }, + "default_cobble.png^techage_meltingpot.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-10/16, -8/16, -10/16, 10/16, 9/16, -6/16}, + {-10/16, -8/16, 6/16, 10/16, 9/16, 10/16}, + {-10/16, -8/16, -10/16, -6/16, 9/16, 10/16}, + { 6/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + { -6/16, -8/16, -6/16, 6/16, 5/16, 6/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-10/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec1) + local inv = meta:get_inventory() + inv:set_size('src', 4) + inv:set_size('dst', 4) + end, + + on_timer = function(pos, elapsed) + return pot_node_timer(pos, elapsed) + end, + + on_receive_fields = function(pos, formname, fields, sender) + on_receive_fields(pos, formname, fields, sender) + end, + + on_metadata_inventory_move = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + on_metadata_inventory_put = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + on_metadata_inventory_take = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + can_dig = can_dig, + + drop = "techage:meltingpot", + is_ground_content = false, + groups = {cracky = 3, not_in_creative_inventory=1}, + sounds = default.node_sound_metal_defaults(), + + 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("techage:meltingpot", { + description = S("Melting Pot"), + tiles = { + "default_cobble.png", + "default_cobble.png^techage_meltingpot.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-10/16, -8/16, -10/16, 10/16, 9/16, -6/16}, + {-10/16, -8/16, 6/16, 10/16, 9/16, 10/16}, + {-10/16, -8/16, -10/16, -6/16, 9/16, 10/16}, + { 6/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + { -6/16, -8/16, -6/16, 6/16, -4/16, 6/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-10/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec1) + meta:set_string("infotext", S("Melting Pot inactive (heat=0)")) + local inv = meta:get_inventory() + inv:set_size('src', 4) + inv:set_size('dst', 4) + end, + + on_metadata_inventory_move = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_metadata_inventory_put = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_metadata_inventory_take = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_receive_fields = function(pos, formname, fields, sender) + on_receive_fields(pos, formname, fields, sender) + end, + + can_dig = can_dig, + + is_ground_content = false, + groups = {cracky = 3}, + sounds = default.node_sound_metal_defaults(), + + 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_craft({ + output = "techage:meltingpot", + recipe = { + {"default:cobble", "default:copper_ingot", "default:cobble"}, + {"default:cobble", "", "default:cobble"}, + {"default:cobble", "default:cobble", "default:cobble"}, + }, +}) + +if minetest.global_exists("unified_inventory") then + unified_inventory.register_craft_type("melting", { + description = S("Melting"), + icon = "default_cobble.png^techage_meltingpot.png", + width = 2, + height = 2, + }) + unified_inventory.register_craft_type("burning", { + description = S("Burning"), + icon = "techage_smoke.png", + width = 1, + height = 1, + }) + unified_inventory.register_craft({ + output = "techage:charcoal", + items = {"group:wood"}, + type = "burning", + }) +end + +function techage.ironage_register_recipe(recipe) + local key = recipe_key(recipe.recipe) + local output = string.split(recipe.output, " ") + local number = tonumber(output[2] or 1) + table.insert(KeyList, key) + Recipes[key] = { + input = recipe.recipe, + output = output[1], + number = number, + heat = math.max(recipe.heat or 3, 2), + time = math.max(recipe.time or 2, 2*number), + } + NumRecipes = NumRecipes + 1 + + if minetest.global_exists("unified_inventory") then + recipe.items = recipe.recipe + recipe.type = "melting" + unified_inventory.register_craft(recipe) + end +end + + +techage.ironage_register_recipe({ + output = "default:obsidian", + recipe = {"default:cobble"}, + heat = 5, + time = 4, +}) + +techage.ironage_register_recipe({ + output = "default:coral_skeleton", + recipe = {"gravelsieve:compressed_gravel"}, + heat = 4, + time = 4, +}) + +techage.ironage_register_recipe({ + output = "default:bronze_ingot 4", + recipe = {"default:copper_ingot", "default:copper_ingot", "default:copper_ingot", "default:tin_ingot"}, + heat = 4, + time = 8, +}) + +local PileHelp = S([[Coal Pile to produce charcoal: +- build a 5x5 block dirt base +- place a lighter in the centre +- build a 3x3x3 wood cube around +- cover all with dirt to a 5x5x5 cube +- keep a hole to the lighter +- ignite the lighter and immediately +- close the pile with one wood and one dirt +- open the pile after the smoke disappeared]]) + +local BurnerHelp = S([[Coal Burner to heat the melting pot: +- build a 3x3xN cobble tower +- more height means more flame heat +- keep a hole open on one side +- put a lighter in +- fill the tower from the top with charcoal +- ignite the lighter +- place the pot in the flame]]) + +local PileImages = { + {"default_dirt", "default_dirt", "default_dirt", "default_dirt", "default_dirt"}, + {"default_dirt", "default_wood", "default_wood", "default_wood", "default_dirt"}, + {"default_dirt", "default_wood", "default_wood", "default_wood", "default_dirt"}, + {"default_dirt", "default_wood", "techage_lighter", "default_wood", "default_dirt"}, + {"default_dirt", "default_dirt", "default_dirt", "default_dirt", "default_dirt"}, +} + +local BurnerImages = { + false, false, "default_cobble", "techage_charcoal", "default_cobble", + false, false, "default_cobble", "techage_charcoal", "default_cobble", + false, false, "default_cobble", "techage_charcoal", "default_cobble", + false, false, false, "techage_lighter", "default_cobble", + false, false, "default_cobble", "default_cobble", "default_cobble", +} diff --git a/steam_engine/steam_pipe.lua b/basis/steam_pipe.lua similarity index 100% rename from steam_engine/steam_pipe.lua rename to basis/steam_pipe.lua diff --git a/coal_power_station/boiler.lua b/coal_power_station/boiler.lua new file mode 100644 index 0000000..74e8ec0 --- /dev/null +++ b/coal_power_station/boiler.lua @@ -0,0 +1,340 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA3 Coal Power Station Boiler + +]]-- + +-- 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 CYCLE_TIME = 4 +local STANDBY_TICKS = 2 +local COUNTDOWN_TICKS = 2 +local HEAT_STEP = 10 +local WATER_CONSUMPTION = 0.5 +local MAX_WATER = 10 +local POWER_CAPACITY = 10 + +local Pipe = techage.SteamPipe +local generator = techage.generator + + +local Water = { + ["bucket:bucket_river_water"] = true, + ["bucket:bucket_water"] = true, + ["bucket:bucket_empty"] = true, +} + +local function formspec(self, pos, mem) + local temp = mem.temperature or 20 + return "size[8,7]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "image_button[0,0.2;1,1;techage_form_inventory.png;storage;;true;false;]".. + "list[context;water;1,0.2;1,1;]".. + "image_button[0,1.6;1,1;techage_form_input.png;input;;true;false;]".. + "list[context;input;1,1.6;1,1;]".. + "image[1,1.6;1,1;bucket_water.png]".. + "image[1,1.6;1,1;techage_form_mask.png]".. + "image[2,0.5;1,2;techage_form_temp_bg.png^[lowpart:".. + temp..":techage_form_temp_fg.png]".. + "image[7,0.5;1,2;"..generator.formspec_level(mem, mem.power_result).. + "image_button[6,1;1,1;".. self:get_state_button_image(mem) ..";state_button;]".. + "button[3,1.5;2,1;update;"..I("Update").."]".. + "list[current_player;main;0,3;8,4;]".. + "listring[current_name;water]".. + "listring[current_player;main]".. + default.get_hotbar_bg(0, 3) +end + +local function start_node(pos, mem, state) + generator.turn_power_on(pos, POWER_CAPACITY) +end + +local function stop_node(pos, mem, state) + generator.turn_power_on(pos, 0) +end + +local State = techage.NodeStates:new({ + node_name_passive = "techage:coalboiler1", + cycle_time = CYCLE_TIME, + standby_ticks = STANDBY_TICKS, + has_item_meter = false, + formspec_func = formspec, + start_node = start_node, + stop_node = stop_node, +}) + +local function get_water(pos) + local inv = M(pos):get_inventory() + local items = inv:get_stack("water", 1) + if items:get_count() > 0 then + local taken = items:take_item(1) + inv:set_stack("water", 1, items) + return true + end + return false +end + +local function water_temperature(pos, mem) + mem.temperature = mem.temperature or 20 + if mem.fire_trigger then + mem.temperature = math.min(mem.temperature + HEAT_STEP, 100) + else + mem.temperature = math.max(mem.temperature - HEAT_STEP, 20) + end + mem.fire_trigger = false + + if mem.water_level == 0 then + if get_water(pos) then + mem.water_level = 100 + else + mem.temperature = 20 + end + end + return mem.temperature +end + +local function steaming(pos, mem, temp) + mem.water_level = math.max((mem.water_level or 0) - WATER_CONSUMPTION, 0) + if temp >= 80 then + if mem.power_result > 0 then + State:keep_running(pos, mem, COUNTDOWN_TICKS) + else + State:fault(pos, mem) + end + else + State:stop(pos, mem) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end +end + +local function node_timer(pos, elapsed) + local mem = tubelib2.get_mem(pos) + local temp = water_temperature(pos, mem) + if State:is_active(mem) then + steaming(pos, mem, temp) + end + return mem.temperature > 20 +end + +local function turn_power_on(pos, in_dir, sum) + local mem = tubelib2.get_mem(pos) + -- store result for formspec + mem.power_result = sum + if State:is_active(mem) and sum <= 0 then + State:fault(pos, mem) + -- No automatic turn on + mem.power_capacity = 0 + end + M(pos):set_string("formspec", formspec(State, pos, mem)) +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) + State:state_button_event(pos, mem, fields) + + if fields.update then + M(pos):set_string("formspec", formspec(State, pos, mem)) + end +end + + +local function on_rightclick(pos) + local mem = tubelib2.get_mem(pos) + M(pos):set_string("formspec", formspec(State, pos, mem)) +end + +local function can_dig(pos, player) + local inv = M(pos):get_inventory() + local mem = tubelib2.get_mem(pos) + return inv:is_empty("water") and inv:is_empty("input") and not mem.running +end + +local function move_to_water(pos) + local inv = M(pos):get_inventory() + local water_stack = inv:get_stack("water", 1) + local input_stack = inv:get_stack("input", 1) + + if input_stack:get_name() == "bucket:bucket_empty" then + if input_stack:get_count() == 1 then + if water_stack:get_count() > 0 then + water_stack:set_count(water_stack:get_count() - 1) + input_stack = ItemStack("bucket:bucket_water") + inv:set_stack("water", 1, water_stack) + inv:set_stack("input", 1, input_stack) + end + end + elseif water_stack:get_count() < MAX_WATER then + if water_stack:get_count() == 0 then + water_stack = ItemStack("default:water_source") + else + water_stack:set_count(water_stack:get_count() + 1) + end + input_stack = ItemStack("bucket:bucket_empty") + inv:set_stack("water", 1, water_stack) + inv:set_stack("input", 1, input_stack) + end +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 == "input" and Water[stack:get_name()] then + return stack:get_count() + end + return 0 +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 + if listname == "input" then + return stack:get_count() + end + return 0 +end + +minetest.register_node("techage:coalboiler2", { + + + paramtype2 = "facedir", + groups = {cracky=1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_node("techage:coalboiler1", { + description = I("TA3 Coal Power Station Firebox"), + inventory_image = "techage_coal_boiler_inv.png", + tiles = {"techage_coal_boiler_mesh.png"}, + drawtype = "mesh", + mesh = "techage_boiler_large.obj", + selection_box = { + type = "fixed", + fixed = {-14/32, -16/32, -14/32, 14/32, 16/32, 14/32}, + }, + + 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 = can_dig, + allow_metadata_inventory_put = allow_metadata_inventory, + allow_metadata_inventory_take = allow_metadata_inventory, + on_receive_fields = on_receive_fields, + on_rightclick = 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", formspec(mem)) + local inv = meta:get_inventory() + inv:set_size('fuel', 1) + end, + + on_metadata_inventory_put = function(pos) + local mem = tubelib2.init_mem(pos) + mem.running = true + -- activate the formspec fire temporarily + mem.burn_cycles = BURN_CYCLES + M(pos):set_string("formspec", formspec(mem)) + mem.burn_cycles = 0 + swap_node(pos, "techage:firebox_on") + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, +}) + + +-- boiler2: Main part, needed as generator +minetest.register_node("techage:boiler2", { + description = I("TA3 Boiler Top"), + tiles = {"techage_coal_boiler.png"}, + drawtype = "mesh", + mesh = "techage_boiler_large.obj", + selection_box = { + type = "fixed", + fixed = {-10/32, -48/32, -10/32, 10/32, 16/32, 10/32}, + }, + + can_dig = can_dig, + on_timer = node_timer, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_take = allow_metadata_inventory_take, + on_receive_fields = on_receive_fields, + on_rightclick = on_rightclick, + + techage = { + turn_on = turn_power_on, + read_power_consumption = generator.read_power_consumption, + power_network = Pipe, + trigger_boiler = function(pos) + local mem = tubelib2.get_mem(pos) + mem.fire_trigger = true + if not minetest.get_node_timer(pos):is_started() then + minetest.get_node_timer(pos):start(CYCLE_TIME) + end + end, + power_side = "U", + }, + + on_construct = function(pos) + local inv = M(pos):get_inventory() + inv:set_size('water', 1) + inv:set_size('input', 1) + end, + + after_place_node = function(pos, placer) + local mem = generator.after_place_node(pos) + State:node_init(pos, mem, "") + local node = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}) + if node.name == "techage:boiler1" then + on_rightclick(pos) + end + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + State:after_dig_node(pos, oldnode, oldmetadata, digger) + generator.after_dig_node(pos, oldnode) + end, + + after_tube_update = generator.after_tube_update, + + on_metadata_inventory_put = function(pos) + minetest.after(0.5, move_to_water, pos) + end, + + --paramtype2 = "facedir", + groups = {cracky=1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), +}) + diff --git a/coal_power_station/firebox.lua b/coal_power_station/firebox.lua new file mode 100644 index 0000000..9a98560 --- /dev/null +++ b/coal_power_station/firebox.lua @@ -0,0 +1,178 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA3 Coal Power Station 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 +-- Techage Related Data +local TRD = function(pos) return (minetest.registered_nodes[minetest.get_node(pos).name] or {}).techage end + +-- 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 firehole(pos, on) + local param2 = minetest.get_node(pos).param2 + local pos2 = techage.get_pos(pos, 'F') + if on == true then + minetest.swap_node(pos2, {name="techage:coalfirehole_on", param2 = param2}) + elseif on == false then + minetest.swap_node(pos2, {name="techage:coalfirehole", param2 = param2}) + else + minetest.swap_node(pos2, {name="air"}) + end +end + +local function node_timer(pos, elapsed) + local mem = tubelib2.get_mem(pos) + if mem.running then + local trd = TRD({x=pos.x, y=pos.y+2, z=pos.z}) + if trd and trd.trigger_boiler then + trd.trigger_boiler({x=pos.x, y=pos.y+2, z=pos.z}) + end + 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 + mem.running = false + firehole(pos, false) + M(pos):set_string("formspec", firebox.formspec(mem)) + return false + end + end + return true + end +end + +minetest.register_node("techage:coalfirebox", { + description = I("TA3 Coal Power Station Firebox"), + inventory_image = "techage_coal_boiler_inv.png", + tiles = {"techage_coal_boiler_mesh.png"}, + drawtype = "mesh", + mesh = "techage_boiler_large.obj", + selection_box = { + type = "fixed", + fixed = {-13/32, -16/32, -13/32, 13/32, 16/32, 13/32}, + }, + + 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) + firehole(pos, false) + end, + + on_destruct = function(pos) + firehole(pos, nil) + end, + + on_metadata_inventory_put = function(pos, listname, index, stack, player) + local mem = tubelib2.init_mem(pos) + mem.running = true + -- activate the formspec fire temporarily + mem.burn_cycles = firebox.Burntime[stack:get_name()] / CYCLE_TIME + mem.burn_cycles_total = mem.burn_cycles + M(pos):set_string("formspec", firebox.formspec(mem)) + mem.burn_cycles = 0 + firehole(pos, true) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, +}) + +minetest.register_node("techage:coalfirehole", { + description = I("TA3 Coal Power Station Firebox"), + tiles = { + -- up, down, right, left, back, front + "techage_coal_boiler.png", + "techage_coal_boiler.png", + "techage_coal_boiler.png", + "techage_coal_boiler.png", + "techage_coal_boiler.png", + "techage_coal_boiler.png^techage_appl_firehole.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-6/16, -6/16, 6/16, 6/16, 6/16, 12/16}, + }, + }, + + paramtype2 = "facedir", + pointable = false, + diggable = false, + is_ground_content = false, + groups = {not_in_creative_inventory=1}, +}) + +minetest.register_node("techage:coalfirehole_on", { + description = I("TA3 Coal Power Station Firebox"), + tiles = { + -- up, down, right, left, back, front + "techage_coal_boiler.png^[colorize:black:80", + "techage_coal_boiler.png^[colorize:black:80", + "techage_coal_boiler.png^[colorize:black:80", + "techage_coal_boiler.png^[colorize:black:80", + "techage_coal_boiler.png^[colorize:black:80", + { + image = "techage_coal_boiler4.png^[colorize:black:80^techage_appl_firehole4.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-6/16, -6/16, 6/16, 6/16, 6/16, 12/16}, + }, + }, + paramtype2 = "facedir", + light_source = 8, + pointable = false, + diggable = false, + is_ground_content = false, + groups = {not_in_creative_inventory=1}, +}) + diff --git a/coal_power_station/turbine.lua b/coal_power_station/turbine.lua new file mode 100644 index 0000000..72e0334 --- /dev/null +++ b/coal_power_station/turbine.lua @@ -0,0 +1,149 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA2 Steam Engine Cylinder + +]]-- + +-- 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 = 8 + +local Pipe = techage.SteamPipe +local consumer = techage.consumer + +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 + +-- called from pipe network +local function valid_power_dir(pos, power_dir, in_dir) + return power_dir == in_dir +end + +-- called from pipe network +local function turn_power_on_clbk(pos, in_dir, sum) + local mem = tubelib2.get_mem(pos) + -- Simply store state to be prepared, when flywheel wants to start. + mem.running = sum > 0 +end + +-- called from flywheel +local function start_cylinder(pos, on) + local mem = tubelib2.get_mem(pos) + if on and mem.running then + consumer.turn_power_on(pos, POWER_CONSUMPTION) + swap_node(pos, "techage:cylinder_on") + return true + else + consumer.turn_power_on(pos, 0) + swap_node(pos, "techage:cylinder") + end + return false +end + + +minetest.register_node("techage:cylinder", { + description = I("TA2 Cylinder"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta2.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_appl_open.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_steam_hole.png", + "techage_filling_ta2.png^techage_cylinder.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_cylinder.png^techage_frame_ta2.png", + }, + techage = { + turn_on = turn_power_on_clbk, + read_power_consumption = consumer.read_power_consumption, + power_network = Pipe, + power_side = "L", + valid_power_dir = valid_power_dir, + start_cylinder = start_cylinder, + }, + + after_place_node = function(pos, placer) + local mem = consumer.after_place_node(pos, placer) + mem.power_consume = 0 -- needed power to run + mem.power_supply = false -- power available? + end, + + after_tube_update = consumer.after_tube_update, + after_dig_node = consumer.after_dig_node, + + 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:cylinder_on", { + description = I("TA2 Cylinder"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta2.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_appl_open.png^techage_frame_ta2.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_steam_hole.png", + { + image = "techage_filling4_ta2.png^techage_cylinder4.png^techage_frame4_ta2.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + { + image = "techage_filling4_ta2.png^techage_cylinder4.png^techage_frame4_ta2.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + }, + techage = { + turn_on = turn_power_on_clbk, + read_power_consumption = consumer.read_power_consumption, + power_network = Pipe, + power_side = "L", + valid_power_dir = valid_power_dir, + start_cylinder = start_cylinder, + }, + + after_tube_update = consumer.after_tube_update, + after_dig_node = consumer.after_dig_node, + + paramtype2 = "facedir", + groups = {not_in_creative_inventory=1}, + diggable = false, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + diff --git a/depends.txt b/depends.txt deleted file mode 100644 index 6bd4325..0000000 --- a/depends.txt +++ /dev/null @@ -1,4 +0,0 @@ -default -tubelib2 -basic_materials - diff --git a/init.lua b/init.lua index 149d58f..baaaa62 100644 --- a/init.lua +++ b/init.lua @@ -14,6 +14,7 @@ local MP = minetest.get_modpath("techage") -- Load support for intllib. dofile(MP.."/basis/intllib.lua") +-- Basis features dofile(MP.."/basis/power.lua") -- power distribution dofile(MP.."/basis/node_states.lua") dofile(MP.."/basis/trowel.lua") -- hidden networks @@ -21,15 +22,23 @@ dofile(MP.."/basis/junction.lua") -- network junction box dofile(MP.."/basis/tubes.lua") -- tubelib replacement dofile(MP.."/basis/command.lua") -- tubelib replacement dofile(MP.."/basis/consumer.lua") -- consumer base model +dofile(MP.."/basis/steam_pipe.lua") +dofile(MP.."/basis/firebox.lua") -- Iron Age +dofile(MP.."/iron_age/main.lua") dofile(MP.."/iron_age/gravelsieve.lua") dofile(MP.."/iron_age/hammer.lua") - +dofile(MP.."/iron_age/lighter.lua") +dofile(MP.."/iron_age/charcoalpile.lua") +dofile(MP.."/iron_age/coalburner.lua") +dofile(MP.."/iron_age/meltingpot.lua") +if minetest.global_exists("wielded_light") then + dofile(MP.."/iron_age/meridium.lua") +end -- Steam Engine dofile(MP.."/steam_engine/drive_axle.lua") -dofile(MP.."/steam_engine/steam_pipe.lua") dofile(MP.."/steam_engine/firebox.lua") dofile(MP.."/steam_engine/boiler.lua") dofile(MP.."/steam_engine/cylinder.lua") @@ -51,6 +60,9 @@ dofile(MP.."/basic_machines/distributor.lua") dofile(MP.."/basic_machines/gravelsieve.lua") dofile(MP.."/basic_machines/chest.lua") +-- Coal power station +dofile(MP.."/coal_power_station/firebox.lua") + --dofile(MP.."/fermenter/biogas_pipe.lua") --dofile(MP.."/fermenter/gasflare.lua") diff --git a/iron_age/charcoalpile.lua b/iron_age/charcoalpile.lua new file mode 100644 index 0000000..a9ca33b --- /dev/null +++ b/iron_age/charcoalpile.lua @@ -0,0 +1,214 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Charcoalpile to produce charcoal + +]]-- + + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + +local PILE_BURN_TIME = 1200 +local COAL_BURN_TIME = 700 + +-- determine the number of wood nodes +local function num_wood(pos) + local pos1 = {x=pos.x-1, y=pos.y, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y+2, z=pos.z+1} + local nodes = minetest.find_nodes_in_area(pos1, pos2, "group:wood") + return #nodes +end + +-- determine the number of nodes nodes (around wood) +local function num_dirt(pos) + local pos1 = {x=pos.x-2, y=pos.y-1, z=pos.z-2} + local pos2 = {x=pos.x+2, y=pos.y+3, z=pos.z+2} + local nodes = minetest.find_nodes_in_area(pos1, pos2, {"default:dirt", "default:dirt_with_grass", + "default:dirt_with_dry_grass", "default:dirt_with_snow", "techage:dirt_with_ash"}) + return #nodes +end + +-- replace pile top nodes +local function make_dirt_with_dry_grass(pos) + local pos1 = {x=pos.x-2, y=pos.y+3, z=pos.z-2} + local pos2 = {x=pos.x+2, y=pos.y+3, z=pos.z+2} + for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, "default:dirt_with_grass")) do + minetest.swap_node(p, {name = "default:dirt_with_dry_grass"}) + end +end + +-- replace pile bottom nodes +local function make_dirt_with_ash(pos) + local pos1 = {x=pos.x-1, y=pos.y-1, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y-1, z=pos.z+1} + for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, "default:dirt")) do + minetest.swap_node(p, {name = "techage:dirt_with_ash"}) + end +end + +local function start_smoke(pos) + local meta = minetest.get_meta(pos) + pos = {x=pos.x, y=pos.y+3.6, z=pos.z} + local id = meta:get_int("smoke") + local above = minetest.get_node(pos).name + + if id ~= 0 then + minetest.delete_particlespawner(id) + meta:set_int("smoke", 0) + end + + if above == "air" then + id = minetest.add_particlespawner({ + amount = 4, time = 0, collisiondetection = true, + minpos = {x=pos.x-0.25, y=pos.y+0.1, z=pos.z-0.25}, + maxpos = {x=pos.x+0.25, y=pos.y+5, z=pos.z+0.25}, + minvel = {x=-0.2, y=0.3, z=-0.2}, maxvel = {x=0.2, y=1, z=0.2}, + minacc = {x=0,y=0,z=0}, maxacc = {x=0,y=0.5,z=0}, + minexptime = 1, maxexptime = 3, + minsize = 6, maxsize = 12, + texture = "techage_smoke.png", + }) + meta:set_int("smoke", id) + end +end + +local function stop_smoke(pos) + local meta = minetest.get_meta(pos) + local id = meta:get_int("smoke") + if id ~= 0 then + minetest.delete_particlespawner(id) + end + meta:set_int("smoke", 0) +end + +-- replace wood by burning coal +local function collapse_pile(pos) + local pos1 = {x=pos.x-1, y=pos.y, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y+2, z=pos.z+1} + techage.ironage_swap_nodes(pos1, pos2, "group:wood", "techage:charcoal_burn") + stop_smoke(pos) + make_dirt_with_ash(pos) +end + +-- replace wood by coal +local function convert_to_coal(pos) + local pos1 = {x=pos.x-1, y=pos.y+1, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y+2, z=pos.z+1} + techage.ironage_swap_nodes(pos1, pos2, "group:wood", "air") + pos1 = {x=pos.x-1, y=pos.y+0, z=pos.z-1} + pos2 = {x=pos.x+1, y=pos.y+1, z=pos.z+1} + techage.ironage_swap_nodes(pos1, pos2, "group:wood", "techage:charcoal") + stop_smoke(pos) + minetest.swap_node(pos, {name = "techage:charcoal"}) + make_dirt_with_ash(pos) + make_dirt_with_dry_grass(pos) +end + +function techage.start_pile(pos) + local meta = minetest.get_meta(pos) + meta:set_int("ignite", minetest.get_gametime()) + minetest.get_node_timer(pos):start(20) +end + +-- node timer function +function techage.keep_running_pile(pos) + local meta = minetest.get_meta(pos) + if meta:get_int("running") == 0 then + if num_wood(pos) == 26 and num_dirt(pos) == 98 then + meta:set_int("running", 1) + start_smoke(pos) + elseif minetest.get_gametime() > (meta:get_int("ignite") + 10) then + collapse_pile(pos) + minetest.remove_node(pos) + return false + end + else + if num_wood(pos) ~= 26 or num_dirt(pos) ~= 98 then + collapse_pile(pos) + minetest.remove_node(pos) + return false + elseif minetest.get_gametime() > (meta:get_int("ignite") + PILE_BURN_TIME) then + convert_to_coal(pos) + return false + end + end + return true +end + +function techage.stop_pile(pos) + collapse_pile(pos) +end + + +minetest.register_node("techage:dirt_with_ash", { + description = S("Dirt with Ash"), + tiles = { + "techage_ash.png", + "default_dirt.png", + {name = "default_dirt.png^techage_ash_side.png", + tileable_vertical = false}}, + groups = {crumbly = 3, soil = 1, spreading_dirt_type = 1, not_in_creative_inventory=1}, + drop = 'default:dirt', + sounds = default.node_sound_dirt_defaults({ + footstep = {name = "default_grass_footstep", gain = 0.4}, + }), +}) + + +minetest.register_node("techage:charcoal_burn", { + tiles = {"techage_charcoal_burn.png"}, + after_place_node = function(pos) + minetest.get_node_timer(pos):start(math.random(COAL_BURN_TIME, COAL_BURN_TIME*1.2)) + end, + on_timer = function(pos) + minetest.remove_node(pos) + return false + end, + drop = "", + light_source = 10, + is_ground_content = false, + groups = {crumbly = 2, falling_node = 1, not_in_creative_inventory=1}, + sounds = default.node_sound_dirt_defaults(), +}) + +minetest.register_node("techage:charcoal", { + description = S("Charcoal"), + tiles = {"techage_charcoal.png"}, + on_ignite = function(pos, igniter) + minetest.after(2, minetest.swap_node, pos, {name = "techage:charcoal_burn"}) + minetest.after(COAL_BURN_TIME/2, minetest.remove_node, pos) + end, + is_ground_content = false, + groups = {crumbly = 2, falling_node = 1}, + sounds = default.node_sound_dirt_defaults(), +}) + + +minetest.register_craft({ + type = "fuel", + recipe = "techage:charcoal", + burntime = 370, +}) + +minetest.register_lbm({ + label = "[techage] Lighter update", + name = "techage:update", + nodenames = {"techage:lighter_burn"}, + run_at_every_load = true, + action = function(pos, node) + local meta = minetest.get_meta(pos) + if meta:get_int("running") == 1 then + start_smoke(pos) + end + end +}) + diff --git a/iron_age/coalburner.lua b/iron_age/coalburner.lua new file mode 100644 index 0000000..9b3974f --- /dev/null +++ b/iron_age/coalburner.lua @@ -0,0 +1,191 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Coalburner as heater for the Meltingpot + +]]-- + + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + +local function num_coal(pos) + local pos1 = {x=pos.x, y=pos.y+1, z=pos.z} + local pos2 = {x=pos.x, y=pos.y+32, z=pos.z} + local nodes = minetest.find_nodes_in_area(pos1, pos2, {"techage:charcoal", "techage:charcoal_burn"}) + return #nodes +end + +local function num_cobble(pos, height) + local pos1 = {x=pos.x-1, y=pos.y+1, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y+height, z=pos.z+1} + local nodes = minetest.find_nodes_in_area(pos1, pos2, {"default:cobble", "default:desert_cobble"}) + return #nodes +end + +local function start_burner(pos, height) + local pos1 = {x=pos.x-1, y=pos.y+1, z=pos.z-1} + local pos2 = {x=pos.x+1, y=pos.y+height, z=pos.z+1} + techage.ironage_swap_nodes(pos1, pos2, "techage:charcoal", "techage:charcoal_burn") +end + +local function remove_flame(pos, height) + local idx + pos = {x=pos.x, y=pos.y+height, z=pos.z} + for idx=height,1,-1 do + pos = {x=pos.x, y=pos.y+1, z=pos.z} + local node = minetest.get_node(pos) + if string.find(node.name, "techage:flame") then + minetest.remove_node(pos) + elseif node.name == "techage:meltingpot" then + techage.update_heat(pos) + end + end +end + +local function flame(pos, height, heat, first_time) + local idx + pos = {x=pos.x, y=pos.y+height, z=pos.z} + for idx=heat,1,-1 do + pos = {x=pos.x, y=pos.y+1, z=pos.z} + idx = math.min(idx, 12) + local node = minetest.get_node(pos) + if node.name == "techage:meltingpot_active" then + return + end + if node.name == "techage:meltingpot" then + if first_time then + techage.switch_to_active(pos) + else + techage.update_heat(pos) + end + return + end + minetest.add_node(pos, {name = "techage:flame"..math.min(idx,7)}) + local meta = minetest.get_meta(pos) + meta:set_int("heat", idx) + end +end + + +lRatio = {120, 110, 95, 75, 55, 28, 0} +lColor = {"000080", "400040", "800000", "800000", "800000", "800000", "800000"} +for idx,ratio in ipairs(lRatio) do + local color = "techage_flame_animated.png^[colorize:#"..lColor[idx].."B0:"..ratio + minetest.register_node("techage:flame"..idx, { + tiles = { + { + name = color, + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1 + }, + }, + }, + + after_destruct = function(pos, oldnode) + pos.y = pos.y + 1 + local node = minetest.get_node(pos) + if minetest.get_item_group(node.name, "techage_flame") > 0 then + minetest.remove_node(pos) + end + end, + + use_texture_alpha = true, + inventory_image = "techage_flame.png", + paramtype = "light", + light_source = 13, + walkable = false, + buildable_to = true, + floodable = true, + sunlight_propagates = true, + damage_per_second = 4 + idx, + groups = {igniter = 2, dig_immediate = 3, techage_flame=1, not_in_creative_inventory=1}, + drop = "", + }) +end + +minetest.register_node("techage:ash", { + description = S("Ash"), + tiles = {"techage_ash.png"}, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-4/8, -4/8, -4/8, 4/8, -3/8, 4/8}, + }, + }, + is_ground_content = false, + groups = {crumbly = 3, not_in_creative_inventory=1}, + drop = "", + sounds = default.node_sound_defaults(), +}) + +function techage.start_burner(pos, playername) + local height = num_coal(pos) + if minetest.is_protected( + {x=pos.x, y=pos.y+height, z=pos.z}, + playername) then + return + end + if num_cobble(pos, height) == height * 8 then + local meta = minetest.get_meta(pos) + meta:set_int("ignite", minetest.get_gametime()) + meta:set_int("height", height) + start_burner(pos, height) + flame(pos, height, height, true) + local handle = minetest.sound_play("techage", { + pos = {x=pos.x, y=pos.y+height, z=pos.z}, + max_hear_distance = 20, + gain = height/32.0, + loop = true}) + meta:set_int("handle", handle) + minetest.get_node_timer(pos):start(5) + end +end + +function techage.keep_running_burner(pos) + local meta = minetest.get_meta(pos) + local height = meta:get_int("height") + remove_flame(pos, height) + local handle = meta:get_int("handle") + if handle then + minetest.sound_stop(handle) + meta:set_int("handle", 0) + end + if num_cobble(pos, height) == height * 8 then + local new_height = num_coal(pos) + if new_height > 0 then + flame(pos, height, new_height, false) + handle = minetest.sound_play("techage", { + pos = {x=pos.x, y=pos.y+height, z=pos.z}, + max_hear_distance = 32, + gain = new_height/32.0, + loop = true}) + meta:set_int("handle", handle) + else + minetest.swap_node(pos, {name="techage:ash"}) + return false + end + return true + end + return true +end + +function techage.stop_burner(pos) + local meta = minetest.get_meta(pos) + local height = meta:get_int("height") + remove_flame(pos, height) + local handle = meta:get_int("handle") + minetest.sound_stop(handle) +end diff --git a/iron_age/gravelsieve.lua b/iron_age/gravelsieve.lua index 4ae7461..325dbef 100644 --- a/iron_age/gravelsieve.lua +++ b/iron_age/gravelsieve.lua @@ -8,7 +8,7 @@ LGPLv2.1+ See LICENSE.txt for more information - TA2/TA3/TA4 Gravel Sieve, sieving gravel to find ores + Gravel Sieve, sieving gravel to find ores ]]-- diff --git a/iron_age/lighter.lua b/iron_age/lighter.lua new file mode 100644 index 0000000..03ed0f6 --- /dev/null +++ b/iron_age/lighter.lua @@ -0,0 +1,91 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Lighter for Coalburner and Charcoalpile + +]]-- + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + +minetest.register_node("techage:lighter_burn", { + tiles = {"techage_lighter_burn.png"}, + + after_place_node = function(pos) + techage.start_pile(pos) + end, + + on_timer = function(pos, elapsed) + return techage.keep_running_pile(pos) + end, + + on_destruct = function(pos) + techage.stop_pile(pos) + end, + + drop = "", + light_source = 10, + is_ground_content = false, + groups = {crumbly = 2, not_in_creative_inventory=1}, + sounds = default.node_sound_dirt_defaults(), +}) + +minetest.register_node("techage:coal_lighter_burn", { + tiles = {"techage_lighter_burn.png"}, + + after_place_node = function(pos) + local meta = minetest.get_meta(pos) + local playername = meta:get_string("playername") + techage.start_burner(pos, playername) + end, + + on_timer = function(pos, elapsed) + return techage.keep_running_burner(pos) + end, + + on_destruct = function(pos) + techage.stop_burner(pos) + end, + + drop = "", + light_source = 10, + is_ground_content = false, + groups = {crumbly = 2, not_in_creative_inventory=1}, + sounds = default.node_sound_dirt_defaults(), +}) + +minetest.register_node("techage:lighter", { + description = S("Lighter"), + tiles = {"techage_lighter.png"}, + on_ignite = function(pos, igniter) + if minetest.find_node_near(pos, 1, "techage:charcoal") then + minetest.after(1, techage.ironage_swap_node, pos, "techage:coal_lighter_burn") + else + minetest.after(1, techage.ironage_swap_node, pos, "techage:lighter_burn") + end + end, + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + meta:set_string("playername", placer:get_player_name()) + end, + is_ground_content = false, + groups = {crumbly = 2, flammable = 2}, + sounds = default.node_sound_dirt_defaults(), +}) + +minetest.register_craft({ + output = 'techage:lighter 2', + recipe = { + {'group:wood'}, + {'farming:straw'}, + {''}, + } +}) diff --git a/iron_age/main.lua b/iron_age/main.lua new file mode 100644 index 0000000..03b8391 --- /dev/null +++ b/iron_age/main.lua @@ -0,0 +1,29 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + +]]-- + +function techage.ironage_swap_node(pos, name) + minetest.swap_node(pos, {name = name}) + local node = minetest.registered_nodes[name] + if node.on_construct then + node.on_construct(pos) + end + if node.after_place_node then + node.after_place_node(pos) + end +end + +function techage.ironage_swap_nodes(pos1, pos2, name1, name2) + for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, name1)) do + techage.ironage_swap_node(p, name2) + end +end + diff --git a/iron_age/meltingpot.lua b/iron_age/meltingpot.lua new file mode 100644 index 0000000..629473e --- /dev/null +++ b/iron_age/meltingpot.lua @@ -0,0 +1,534 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Meltingpot to produce metal and alloy ingots + +]]-- + + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + +local SMELTING_TIME = 2 + +local Tabs = S("Menu,Recipes") + +local Recipes = {} -- registered recipes +local KeyList = {} -- index to Recipes key translation +local NumRecipes = 0 +local Cache = {} -- store melting pot inventory data + +-- formspec images +local function draw(images) + local tbl = {} + for y=0,4 do + for x=0,4 do + local idx = 1 + x + y * 5 + local img = images[idx] + if img ~= false then + tbl[#tbl+1] = "image["..(x*0.8)..","..(y*0.8)..";0.8,0.8;"..img..".png]" + end + end + end + return table.concat(tbl) +end + +local formspec1 = + "size[8,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;"..Tabs..";1;;true]".. + "label[1,0.2;"..S("Menu").."]".. + + "container[1,1]".. + "list[current_name;src;0,0;2,2;]".. + "item_image[2.6,0;0.8,0.8;techage:meltingpot]".. + "image[2.3,0.6;1.6,1;gui_furnace_arrow_bg.png^[transformR270]".. + "list[current_name;dst;4,0;2,2;]".. + "container_end[]".. + + "list[current_player;main;0,4;8,4;]".. + "listring[current_name;dst]".. + "listring[current_player;main]".. + "listring[current_name;src]".. + "listring[current_player;main]" + +local function formspec2(idx) + idx = math.min(idx, #KeyList) + local key = KeyList[idx] + local input1 = Recipes[key].input[1] or "" + local input2 = Recipes[key].input[2] or "" + local input3 = Recipes[key].input[3] or "" + local input4 = Recipes[key].input[4] or "" + local num = Recipes[key].number + local heat = Recipes[key].heat + local time = Recipes[key].time + local output = Recipes[key].output + if num > 1 then + output = output.." "..num + end + return "size[8,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;"..Tabs..";2;;true]".. + "label[1,0.2;"..S("Melting Guide").."]".. + + "container[1,1]".. + "item_image_button[0,0;1,1;"..input1..";b1;]".. + "item_image_button[1,0;1,1;"..input2..";b2;]".. + "item_image_button[0,1;1,1;"..input3..";b3;]".. + "item_image_button[1,1;1,1;"..input4..";b4;]".. + "item_image[2.6,0;0.8,0.8;techage:meltingpot]".. + "image[2.3,0.6;1.6,1;gui_furnace_arrow_bg.png^[transformR270]".. + "item_image_button[4,0.5;1,1;"..output..";b5;]".. + "label[2,2.2;"..S("Heat")..": "..heat.." / "..S("Time")..": "..time.." s]".. + "label[2,4;Recipe "..idx.." of "..NumRecipes.."]".. + "button[2,5.5;1,1;priv;<<]".. + "button[3,5.5;1,1;next;>>]".. + "container_end[]" +end + +local function on_receive_fields(pos, formname, fields, sender) + local meta = minetest.get_meta(pos) + local recipe_idx = meta:get_int("recipe_idx") + if recipe_idx == 0 then recipe_idx = 1 end + if fields.tab == "1" then + meta:set_string("formspec", formspec1) + elseif fields.tab == "2" then + meta:set_string("formspec", formspec2(recipe_idx)) + elseif fields.next == ">>" then + recipe_idx = math.min(recipe_idx + 1, NumRecipes) + meta:set_int("recipe_idx", recipe_idx) + meta:set_string("formspec", formspec2(recipe_idx)) + elseif fields.priv == "<<" then + recipe_idx = math.max(recipe_idx - 1, 1) + meta:set_int("recipe_idx", recipe_idx) + meta:set_string("formspec", formspec2(recipe_idx)) + end +end + +local function can_dig(pos, player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + return inv:is_empty("dst") and inv:is_empty("src") +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 + 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 + +-- generate an unique key based on the unsorted and +-- variable number of inventory items +local function recipe_key(items) + local tbl = {} + -- remove items which exist more than once + for _,item in ipairs(items) do + tbl[item] = true + end + local names = {} + for key,_ in pairs(tbl) do + names[#names + 1] = key + end + -- bring in a sorted order + table.sort(names) + return table.concat(names, "-") +end + +-- determine recipe based on inventory items +local function get_recipe(inv) + -- collect items + local stacks = {} + local names = {} + for _,stack in ipairs(inv:get_list("src")) do + if not stack:is_empty() then + table.insert(names, stack:get_name()) + table.insert(stacks, stack) + else + table.insert(stacks, ItemStack("")) + end + end + local key = recipe_key(names) + local recipe = Recipes[key] + + if recipe then + return { + input = recipe.input, + stacks = stacks, + output = ItemStack(recipe.output.." "..recipe.number), + heat = recipe.heat, + time = recipe.time, + } + end + return nil +end + +-- prepare recipe and store in cache table for faster access +local function store_recipe_in_cache(pos) + local hash = minetest.hash_node_position(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local recipe = get_recipe(inv) + Cache[hash] = recipe + return recipe +end + +-- read value from the node below +local function get_heat(pos) + local heat = 0 + pos.y = pos.y - 1 + local node = minetest.get_node(pos) + local meta = minetest.get_meta(pos) + pos.y = pos.y + 1 + if minetest.get_item_group(node.name, "techage_flame") > 0 then + heat = meta:get_int("heat") + end + return heat +end + +-- Start melting if heat is ok AND source items available +function techage.switch_to_active(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + local recipe = store_recipe_in_cache(pos) + + if recipe and heat >= recipe.heat then + minetest.swap_node(pos, {name = "techage:meltingpot_active"}) + minetest.registered_nodes["techage:meltingpot_active"].on_construct(pos) + meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")") + minetest.get_node_timer(pos):start(2) + return true + end + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") + return false +end + +function techage.update_heat(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") +end + +local function set_inactive(meta, pos, heat) + minetest.get_node_timer(pos):stop() + minetest.swap_node(pos, {name = "techage:meltingpot"}) + minetest.registered_nodes["techage:meltingpot"].on_construct(pos) + meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")") +end + +-- Stop melting if heat to low OR no source items available +local function switch_to_inactive(pos) + local meta = minetest.get_meta(pos) + local heat = get_heat(pos) + local hash = minetest.hash_node_position(pos) + local recipe = Cache[hash] or store_recipe_in_cache(pos) + + if not recipe or heat < recipe.heat then + set_inactive(meta, pos, heat) + return true + end + meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")") + return false +end + + +local function index(list, x) + for idx, v in pairs(list) do + if v == x then return idx end + end + return nil +end + +-- move recipe src items to output inventory +local function process(inv, recipe, heat) + if heat < recipe.heat then + return false + end + local res = false + if inv:room_for_item("dst", recipe.output) then + for _,item in ipairs(recipe.input) do + res = false + for _, stack in ipairs(recipe.stacks) do + if stack:get_count() > 0 and stack:get_name() == item then + stack:take_item(1) + res = true + break + end + end + if res == false then + return false + end + end + inv:add_item("dst", recipe.output) + inv:set_list("src", recipe.stacks) + return true + end + return false +end + +local function smelting(pos, recipe, heat, elapsed) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + elapsed = elapsed + meta:get_int("leftover") + + while elapsed >= recipe.time do + if process(inv, recipe, heat) == false then + meta:set_int("leftover", 0) + set_inactive(meta, pos, heat) + return false + end + elapsed = elapsed - recipe.time + end + meta:set_int("leftover", elapsed) + return true +end + +local function pot_node_timer(pos, elapsed) + if switch_to_inactive(pos) == false then + local hash = minetest.hash_node_position(pos) + local heat = get_heat(pos) + local recipe = Cache[hash] or store_recipe_in_cache(pos) + if recipe then + return smelting(pos, recipe, heat, elapsed) + end + end + return false +end + +minetest.register_node("techage:meltingpot_active", { + description = S("Melting Pot"), + tiles = { + { + image = "techage_meltingpot_top_active.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1, + }, + }, + "default_cobble.png^techage_meltingpot.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-10/16, -8/16, -10/16, 10/16, 9/16, -6/16}, + {-10/16, -8/16, 6/16, 10/16, 9/16, 10/16}, + {-10/16, -8/16, -10/16, -6/16, 9/16, 10/16}, + { 6/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + { -6/16, -8/16, -6/16, 6/16, 5/16, 6/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-10/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec1) + local inv = meta:get_inventory() + inv:set_size('src', 4) + inv:set_size('dst', 4) + end, + + on_timer = function(pos, elapsed) + return pot_node_timer(pos, elapsed) + end, + + on_receive_fields = function(pos, formname, fields, sender) + on_receive_fields(pos, formname, fields, sender) + end, + + on_metadata_inventory_move = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + on_metadata_inventory_put = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + on_metadata_inventory_take = function(pos) + store_recipe_in_cache(pos) + switch_to_inactive(pos) + end, + + can_dig = can_dig, + + drop = "techage:meltingpot", + is_ground_content = false, + groups = {cracky = 3, not_in_creative_inventory=1}, + sounds = default.node_sound_metal_defaults(), + + 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("techage:meltingpot", { + description = S("Melting Pot"), + tiles = { + "default_cobble.png", + "default_cobble.png^techage_meltingpot.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-10/16, -8/16, -10/16, 10/16, 9/16, -6/16}, + {-10/16, -8/16, 6/16, 10/16, 9/16, 10/16}, + {-10/16, -8/16, -10/16, -6/16, 9/16, 10/16}, + { 6/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + { -6/16, -8/16, -6/16, 6/16, -4/16, 6/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-10/16, -8/16, -10/16, 10/16, 9/16, 10/16}, + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec1) + meta:set_string("infotext", S("Melting Pot inactive (heat=0)")) + local inv = meta:get_inventory() + inv:set_size('src', 4) + inv:set_size('dst', 4) + end, + + on_metadata_inventory_move = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_metadata_inventory_put = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_metadata_inventory_take = function(pos) + store_recipe_in_cache(pos) + techage.switch_to_active(pos) + end, + + on_receive_fields = function(pos, formname, fields, sender) + on_receive_fields(pos, formname, fields, sender) + end, + + can_dig = can_dig, + + is_ground_content = false, + groups = {cracky = 3}, + sounds = default.node_sound_metal_defaults(), + + 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_craft({ + output = "techage:meltingpot", + recipe = { + {"default:cobble", "default:copper_ingot", "default:cobble"}, + {"default:cobble", "", "default:cobble"}, + {"default:cobble", "default:cobble", "default:cobble"}, + }, +}) + +if minetest.global_exists("unified_inventory") then + unified_inventory.register_craft_type("melting", { + description = S("Melting"), + icon = "default_cobble.png^techage_meltingpot.png", + width = 2, + height = 2, + }) + unified_inventory.register_craft_type("burning", { + description = S("Burning"), + icon = "techage_smoke.png", + width = 1, + height = 1, + }) + unified_inventory.register_craft({ + output = "techage:charcoal", + items = {"group:wood"}, + type = "burning", + }) +end + +function techage.ironage_register_recipe(recipe) + local key = recipe_key(recipe.recipe) + local output = string.split(recipe.output, " ") + local number = tonumber(output[2] or 1) + table.insert(KeyList, key) + Recipes[key] = { + input = recipe.recipe, + output = output[1], + number = number, + heat = math.max(recipe.heat or 3, 2), + time = math.max(recipe.time or 2, 2*number), + } + NumRecipes = NumRecipes + 1 + + if minetest.global_exists("unified_inventory") then + recipe.items = recipe.recipe + recipe.type = "melting" + unified_inventory.register_craft(recipe) + end +end + + +techage.ironage_register_recipe({ + output = "default:obsidian", + recipe = {"default:cobble"}, + heat = 5, + time = 4, +}) + +techage.ironage_register_recipe({ + output = "default:coral_skeleton", + recipe = {"gravelsieve:compressed_gravel"}, + heat = 4, + time = 4, +}) + +techage.ironage_register_recipe({ + output = "default:bronze_ingot 4", + recipe = {"default:copper_ingot", "default:copper_ingot", "default:copper_ingot", "default:tin_ingot"}, + heat = 4, + time = 8, +}) + diff --git a/iron_age/meridium.lua b/iron_age/meridium.lua new file mode 100644 index 0000000..9150f3d --- /dev/null +++ b/iron_age/meridium.lua @@ -0,0 +1,128 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Brilliant Meririum and tools (mod "wielded_light" needed) + +]]-- + +-- Load support for intllib. +local MP = minetest.get_modpath("techage") +local S, NS = dofile(MP.."/intllib.lua") + + +minetest.register_craftitem("techage:meridium_ingot", { + description = "Meridium Ingot", + inventory_image = "techage_meridium_ingot.png", +}) + + +minetest.register_tool("techage:pick_meridium", { + description = S("Meridium Pickaxe"), + inventory_image = "techage_meridiumpick.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=1, + groupcaps={ + cracky = {times={[1]=4.00, [2]=1.60, [3]=0.80}, uses=30, maxlevel=2}, + }, + damage_groups = {fleshy=4}, + }, + sound = {breaks = "default_tool_breaks"}, + light_source = 12, +}) + +minetest.register_tool("techage:shovel_meridium", { + description = S("Meridium Shovel"), + inventory_image = "techage_meridiumshovel.png", + wield_image = "techage_meridiumshovel.png^[transformR90", + tool_capabilities = { + full_punch_interval = 1.1, + max_drop_level=1, + groupcaps={ + crumbly = {times={[1]=1.50, [2]=0.90, [3]=0.40}, uses=40, maxlevel=2}, + }, + damage_groups = {fleshy=3}, + }, + sound = {breaks = "default_tool_breaks"}, + light_source = 12, +}) + +minetest.register_tool("techage:axe_meridium", { + description = S("Meridium Axe"), + inventory_image = "techage_meridiumaxe.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=1, + groupcaps={ + choppy={times={[1]=2.50, [2]=1.40, [3]=1.00}, uses=20, maxlevel=2}, + }, + damage_groups = {fleshy=4}, + }, + sound = {breaks = "default_tool_breaks"}, + light_source = 12, +}) + +minetest.register_tool("techage:sword_meridium", { + description = S("Meridium Sword"), + inventory_image = "techage_meridiumsword.png", + tool_capabilities = { + full_punch_interval = 0.8, + max_drop_level=1, + groupcaps={ + snappy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2}, + }, + damage_groups = {fleshy=6}, + }, + sound = {breaks = "default_tool_breaks"}, + light_source = 12, +}) + +minetest.register_craft({ + output = 'techage:pick_meridium', + recipe = { + {'techage:meridium_ingot', 'techage:meridium_ingot', 'techage:meridium_ingot'}, + {'', 'group:stick', ''}, + {'', 'group:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'techage:shovel_meridium', + recipe = { + {'techage:meridium_ingot'}, + {'group:stick'}, + {'group:stick'}, + } +}) + +minetest.register_craft({ + output = 'techage:axe_meridium', + recipe = { + {'techage:meridium_ingot', 'techage:meridium_ingot'}, + {'techage:meridium_ingot', 'group:stick'}, + {'', 'group:stick'}, + } +}) + +minetest.register_craft({ + output = 'techage:sword_meridium', + recipe = { + {'techage:meridium_ingot'}, + {'techage:meridium_ingot'}, + {'group:stick'}, + } +}) + +techage.register_recipe({ + output = "techage:meridium_ingot", + recipe = {"default:steel_ingot", "default:mese_crystal_fragment"}, + heat = 4, + time = 3, +}) diff --git a/mod.conf b/mod.conf index 95386aa..53d7069 100644 --- a/mod.conf +++ b/mod.conf @@ -1,3 +1,4 @@ name = techage -depends = default, tubelib2, basic_materials +depends = default,tubelib2,basic_materials +optional_depends = unified_inventory,wielded_light description = Hello World! \ No newline at end of file diff --git a/steam_engine/firebox.lua b/steam_engine/firebox.lua index 6f88ee1..8c08994 100644 --- a/steam_engine/firebox.lua +++ b/steam_engine/firebox.lua @@ -23,85 +23,11 @@ local TRD = function(pos) return (minetest.registered_nodes[minetest.get_node(po local MP = minetest.get_modpath("techage") local I,_ = dofile(MP.."/intllib.lua") +local firebox = techage.firebox + local CYCLE_TIME = 2 -local BURN_CYCLES = 10 -local Fuels = { - ["techage:charcoal"] = true, - ["default:coal_lump"] = true, - ["default:coalblock"] = true, -} - -local function formspec(mem) - local fuel_percent = 0 - if mem.running then - fuel_percent = (mem.burn_cycles * 100) / BURN_CYCLES - end - 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 - -local function can_dig(pos, player) - local inv = M(pos):get_inventory() - return inv:is_empty("fuel") -end - -local function allow_metadata_inventory(pos, listname, index, stack, player) - if minetest.is_protected(pos, player:get_player_name()) then - return 0 - end - if Fuels[stack:get_name()] then - return stack:get_count() - end - return 0 -end - -local function on_receive_fields(pos, formname, fields, player) - if minetest.is_protected(pos, player:get_player_name()) then - return - end - - if fields.update then - local mem = tubelib2.get_mem(pos) - M(pos):set_string("formspec", formspec(mem)) - end -end - -local function on_rightclick(pos, node, clicker) - local mem = tubelib2.get_mem(pos) - M(pos):set_string("formspec", formspec(mem)) -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 get_fuel(pos) - local inv = M(pos):get_inventory() - local items = inv:get_stack("fuel", 1) - if items:get_count() > 0 then - local taken = items:take_item(1) - inv:set_stack("fuel", 1, items) - return taken - end -end - local function node_timer(pos, elapsed) local mem = tubelib2.get_mem(pos) if mem.running then @@ -111,12 +37,14 @@ local function node_timer(pos, elapsed) end mem.burn_cycles = (mem.burn_cycles or 0) - 1 if mem.burn_cycles <= 0 then - if get_fuel(pos) then - mem.burn_cycles = BURN_CYCLES + 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 mem.running = false - swap_node(pos, "techage:firebox") - M(pos):set_string("formspec", formspec(mem)) + firebox.swap_node(pos, "techage:firebox") + M(pos):set_string("formspec", firebox.formspec(mem)) return false end end @@ -142,30 +70,31 @@ minetest.register_node("techage:firebox", { sounds = default.node_sound_stone_defaults(), on_timer = node_timer, - can_dig = can_dig, - allow_metadata_inventory_put = allow_metadata_inventory, - allow_metadata_inventory_take = allow_metadata_inventory, - on_receive_fields = on_receive_fields, - on_rightclick = on_rightclick, + 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", formspec(mem)) + meta:set_string("formspec", firebox.formspec(mem)) local inv = meta:get_inventory() inv:set_size('fuel', 1) end, - on_metadata_inventory_put = function(pos) + on_metadata_inventory_put = function(pos, listname, index, stack, player) local mem = tubelib2.init_mem(pos) mem.running = true -- activate the formspec fire temporarily - mem.burn_cycles = BURN_CYCLES - M(pos):set_string("formspec", formspec(mem)) + mem.burn_cycles = firebox.Burntime[stack:get_name()] / CYCLE_TIME + mem.burn_cycles_total = mem.burn_cycles + M(pos):set_string("formspec", firebox.formspec(mem)) mem.burn_cycles = 0 - swap_node(pos, "techage:firebox_on") + firebox.swap_node(pos, "techage:firebox_on") minetest.get_node_timer(pos):start(CYCLE_TIME) end, }) @@ -199,10 +128,10 @@ minetest.register_node("techage:firebox_on", { drop = "techage:firebox", on_timer = node_timer, - can_dig = can_dig, - allow_metadata_inventory_put = allow_metadata_inventory, - allow_metadata_inventory_take = allow_metadata_inventory, - on_receive_fields = on_receive_fields, - on_rightclick = on_rightclick, + 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, }) diff --git a/textures/techage_appl_distri.png b/textures/techage_appl_distri.png index 9361894b992886da9de0d2ca153a6c0635081d42..b3ad288959fb1cda17fd13b3c45203d3cf4934a3 100644 GIT binary patch delta 456 zcmV;(0XP1O4A%pY8Gi!+002a!ipBr{00d`2O+f$vv5yPP)t-sOlffd006(l$-~CVz{AJ5yT04p-v9sr zy1l^2%+TiM=bWCOo}i%#90>vw0B7n=&rM@(f~$5c*p0&G9&M)GAnZ9mzi zKEKOMU|B~tqEXKeU>(R-=Pd*XEL%O-5%(bOFYA5XK|z9WyzY5m*q16!2PTc2PXOm8 y>*0K885jsynd`A{n(ML88Ge0PUmx$c=l=pX&>PQqo}x3$g8^?d=&b{L-ZKt$T)FQ8x!dRt+R-i>Hd5H_C zR7FLJOM)>mYFwg5qsf~WmqsuWgCH6g)QF-+(V{E@7Nk;3DcIU=icqCekOEz#oia0b zJANn{m1#SGvCoI|;V$QY{GRQ9&JjQlP!A~lyJiD902K_KRVYK?`JYW0%EbXtfy{$14-^RBt|a%Rq*&f-@V(%aoZ`fxwHs>^uriM#l4 z^-7}Q<1@mcP(^uF&4#p8pR1%~)xbs2-;4UXI&yRKTpKr+HHUufcxb_rXIQvw0pUm( z#i8JKxj+b{lvp-@G`H;I{ZHQEjtAzla`k(Jx;kcV+FaHZ2xO0%|M1nRo8D zl~wTrq@V3)-Nr3zjvo1C}MtNHkUB+wZpX*_w|qBjNMPch)``%Yl;s zuD#&~jBsReeXxejm1Ss7H70p{An@t>PublZ#MYw=<4QqmTl;@Z+s=G@#mlIw7FXE& zmpO&wdX$fUN>&nyg!z2!`djwx3HC2U4RsAPH8k9OEYwZa_G(0e=yT)mp6L=@*FpB> zTq66PY5K`Kj%`y_w-ZB;5)FsuWCSkyxL<(m@!4$MR{3AI5#`z9XHb>cLTO8zRNrtE zheCI#2V0jET~manYB7b(=}@tyjmzboHmDTaMkK|5H7Z|3_{?`4pwH@Mu z%mCS0*~ARjiE8NVXeY^+e9oW%mSu@(Bto$1a9@M>2Vm&9-EIIzXN)E>A+cXzAp{57 zj-Z>?;G2=l<2}^Y@*^Lv{(!Tb&X^M#ra?h&0assf6+i9!i7!hxpg08r5wjB%AUCG~ zkK5CKF(AN?dv@~S0e+r2SiD;UW9pCR@?fSI{XAA(@ zH@vm_Fvca_Vm0G$joAI%U&D~$4Qe~P78rJ5%NCKQN1co%^9-` z(o+0noIeJqQ~CI4>ps#(X7sNEBmBufYnmE=ymaz3q?D&ELI9>^VOk~+-}5-OrBhgt z_1T{nj|s5ui`Dp2hu=T@>Khq-e&!hgASHzpr!sLuK9&^og-EgoRl-@<&1Paw*4D(- zw9l73_Zq3g{Mgbyb?;4+K*m@ezyE*ehIwG-Wz&k|+Kd2>c6C!YId}G1E_dtl#Vfdf zXiPSiWyKW@%eF8~gThILEPCoC9GYgG<8|h5-&I3gn-M_Tuq5j18!4PRIpB7Cn_qq5 zO=ewIgdWw=4Fichahm&!eqh-a9+!vPuD_lCKU$1L93ORF`j|IfU3BP@NqMJsBFf1b z$DYQ<$n;AK#+p(tz3Ig+3Z5wQf*X3fyv>Ckq{2Saf>t=c* zy_*Wg2mIALD_WMlyXwpjrL9N0M*a{CF3_CX9g1)ejf7AgD!O41iG)e;CLt7os%aaO z{b}=a#*V6LIe3s!BSs8(C><2wcRP0M7(Sn`@2{%wuatsudNk_O4P7L76FS{X9(V18 zaTjb8LfE^Tn#j8F!gy-6@{i%P?p0rxY5*Hb%Y3If#hC&816HY=xTOx2*h{e5 diff --git a/textures/techage_appl_distri4.png b/textures/techage_appl_distri4.png index 06233a90f5c186c27420d6cda87ecc0b5862ac3e..ad2494ef9cf4c022d9f362cb1861a53c600d4ff3 100644 GIT binary patch delta 1124 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 delta 241 zcmcb@IF)IFgfs^m0|P^GRn-?D#g^pl?!xdN1Q+aGK6#>@L_K?nr>`sfV`eU19r+VK z)))X)E%J1642d|LoFKuvIH7@2*>cvEJL~@)SF6ijZg%pSVXqsbBJ1t#`Tx%!IB-Bg zQ?pa?`=6i1Y@VK;7QH%b%xr9I6>k$1pD+Lg+TQHR*;Hd*dMV`84x2=VI|d(I85Ix1zM>;i1<5FRtnreNYNN pe&dO)t*xY_+d&PWn;7^R7;&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3i1c|gt+=-7Fyd`U$}5#`}XY&{~1_ym7_tLOM?7@862M7NCR>l zJzX3_G|nd{ERanoEG#^r-@w*0b;^{fQ*C>uJYec-N>E@CH*gVeSS7H`gRok>JNR9M69mE8@)APj^rQJ1(aSc6$2b(l(-g*Dj1W6-BWsLc;0 zMYPg=FGv`SIU5WJ(V3xYS3=tH_OZWFDL7jYLP!8W^37&uAi0kD+=6rN@QmA803fh{kNJ8Hm|4>lj3o-xMp6=d(^pUuyi(9qfv=?CiUNP}75{z_c2__jQTKKnV@$8@ mH&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3i1c|gt+=-7Fyd`U$}5#`}Xbs85lGszB&TZToU9L%;50sMjDXg z=;`7ZqH#VsVS#KyVPW9`{RXz4sZ*v*ood@N! oZHEn5fGT<#|Cy&rxG*qGI>)nV`RiHbK+_mJUHx3vIVCg!0P*BLUH||9 delta 236 zcmVJNR9M69mE8@)APj^rQJ1(aSc6$2b(l(-g*Dj1W6-BWsLc;0 zMYPg=FGv`SIU5WJ(V3xUQv%YCvyc6aO2OHJ5JCa~l5aLM1IcyF=N6oEhiBZ*0w9U$ zK2_are$3Zvz|5MaU@TFfMwXJ`o4$gQ;FW@=3VbC6R}}b*ulVE mzq!15JKnN;?*|AW!~svsyE<>hf!?+N0000&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3i1c|gt+=-7Fyd`U$}5#`}Xbs|1<2~D!vV*xg^Lhn8D%MjWi&~ z(bL5-MB{vN!UEZZ!otD>`VDM7Q>RRsI@Pvk$^)jZrUV5RaRV3ehE)Q~T)6c_om3>e o+727A09Euf{xeULaA9DWbdG1!^4GJy{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2jc@05-&GYx6Cw=TP%NWNkl zRMemr$Ht(Zv$~@h>3+J8b7wxCfdPk_n|1~YU<`T$1V8`;VBmiM*5BPYf5U$dSm{9@ zXXmD!5B@Pgr}M=g0L*kX0{1$4$QJ=VGCn2tgl1^cMrDT4Po6@} z%tPS9)eGoaK=a!Ts`-%Z4aJ(Ln0@Yz!>xh)qgjGVSuy%loK#bTmq+J6A>5AReO+RG zt%_6Dm=(pXf{A~Mdb|tnuE+2jHae>bvNqLh4EOWTi0)p)v}}IA0~^1ZWD6#=z>f4^ zrtEihjuQ_)Z|ez`H+0G$`IGup0WSdMv<4q3Zj3rS!2F8DK_V9V&e(HDqS75YrkpiCG zYvzD!_Z|v>e5d^aJpq^j79V9R^(7MmXCIbC9yJrnJD~swfB*=9gq#HeAOHd&en9-d zq5Z%+r7PG;h)-CkbR0eYG%vor`0x1ve;2A%7&#ha#0Pd=mcP10=*Fll6v<`l#HtZS z4H}I;_b@T^m`W<1!Ju(wgcD;11@Qyo z2jnyq009sH@dM%qWDyF000@Bifyc%VsFyy3und3K-$yqB{O`A^dm(O4k*z3}rr+$Z zAJFa>dw$^7ZpRO3jGE$S&xC>cfp#DLfT^LLa=2O4M3p?T(k3dG=G^ng_Ui}Ao%(^5 ze)s{Y9}qtvr=b7{fB=Xe5I-P`Pyhr#0K^YGzJB08_kY34@+Rnm>j!@H3?^f$C!c2f z@#TL{7O64Kk=1~qF$X_s(_WZoICFB}4Ns$#anGT%6d;UJOKmY{b{~mt=T=FGNFDiwd000UA07*qoM6N<$f*yjrP5=M^ delta 978 zcmV;@11N@i5?B(vKphKPp_}KW2$P=2ONgI_KMn8E9 zH8T&s3s*0oYd+0y*Qw?Lw$>G^MaArMZyat7+~1ldsFW3>Pen;IH57B`+$DmoD8;W! ztgTjY${MqxxRrl5QDKkYg1c)GyoQa=X#9*#H50-8;&Z~g*Dx)c-|xWsuO^wi2`#Y0 zzwGeo^EW(ws1Z2x+A(5PA2Wb@!{O$7NKjd2{PrE%*(6hwM@i)Za?K_~J1w?-f8VL> z8X(LEZ0Bm2y2au*Ay+p|Y$xQI7)-_6q+*)lc?u?E=-Se&E9E zDPmQh@A7S$p3M*0kg%3v^p`dK?F1($j*xT%+*XT;FzlZnIIdeP{uZ*b=LZa?;)>H4 zG|r50V$2{fen9+y?1lm$00JO>K>UC#LIDr}0T6#b@YwhP_0mTWl;QgO=!TE?{TB6N zfSXlhDvHJFH~Z@cw0mXO58T@6_yLVkQ~X>sVW57X-A6xQYN#h1ZWc6QB}b&RfyyR1 z_x!QF`hjw%eqgyDen9F6#1F`BC;$Q=0OAM456B`E009sH@dJ;qANbGxU$DHi0eb)X zfggX129puhlcU*Q`|>A?#F*yDiqFuPgBQ1HFU&KXI=SZuVs(QE?D_%g0YAXpw4=HP zIQ8;4(MEs~^3eK$XsykVq4%B2t^ss0pyW0%X|r-Wp A1ONa4 diff --git a/textures/techage_appl_grinder2.png b/textures/techage_appl_grinder2.png new file mode 100644 index 0000000000000000000000000000000000000000..47d85663df3cb499e0e9949f0f3e01a538ed7a7a GIT binary patch literal 468 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|emUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIC?nIRD+&iT2ysd*(pE(3#eQEFmIYKlU6W=V#EyQgnJie4%^P@J>CBeIx* zfm;}a85w5HkpK$H1^9%x`eYW)#Q^?(ehm!`85tQ5vX$$AD%nbc{DK(_E7O+UGQ0S$ z4d@CMPZ!4!kK=ErSo0ln;BonRJ!Iv|6`B?dLgEV+@pU4u2?9idMoJ63#J>flelg$uinAf_a-4qMZs*x zrsm8>-AW~O#)RGnjGtt^9QchGR!m`7$#m@ffhc>%OU`J=vK{~c literal 0 HcmV?d00001 diff --git a/textures/techage_appl_hole_electric.png b/textures/techage_appl_hole_electric.png index 916592e82c3c0adb2f367be069e82d424e91bf36..da19107fde6c5b44ea6a540a67611f6087305a48 100644 GIT binary patch delta 183 zcmZo>I?gyjqMn7Bfq|hhB>gy$Vo7)Ob!1@J*w6hZkrl{iEDmyaVpw-h<|UBBS>O>_ z%)r1c48n{Iv*t(u1tkJ}LR@_^3-ik=u3kB@VE(kA@QAbZI`1BeY8PssIr8)%$dr;G zzhDN3XE)M-9Boe*#}JM4$q5o=2Lb~ESPWbpJUl%GLl4}L@I7Dy#3nN*h?O`Su8|dE ZV0fs&GJR5Uz7kL$gQu&X%Q~loCIBFjI7I*e delta 223 zcmV<503iRz0fPdN7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Re z0|^%+9inPjn*aa+kV!;AR9M4fU>F6XU=)mkQ83a0BdHpK!Xy66$}3=%d;IVL!@89# zNHUX!R0qh)D=?(R1z?q%wP*v$A;3az(49Vg4ux-OYDAVfOq9f5_ob-}x(}vNlm;kr zz#=|I2F0Y86ghx}63v$tIkFfo?_(Gk2ap@*6g%MY!v{E1`{RcXMhmu4FbYP&C>RAK Z0|2BS9p-`h<~{%b002ovPDHLkV1hc%RHOg^ diff --git a/textures/techage_appl_sieve.png b/textures/techage_appl_sieve.png index c0d6515d56d898159f528c8fe6d0485200e455e2..109d8631c175ae92f7661a55a257ee14e2e6ae2f 100644 GIT binary patch delta 94 zcmeBRYGsGZx^prwfgGLypAc7{%t9w8Cm=5| qF|l;c>L!q)k|4ie28U-i(tsS!0*}aI1_o|n5N2eUHAiA%buR$7VHn&1 delta 97 zcmZo=>R_6n63!Fg6XKexo9^V~#K6Fin3%Xn^`a(_#aI&L7tG-B>_!@hljQC0!qCAg t>jC6&7I;J!Gca%qgD@k*tT_@uLG}_)Usv|W%-o{lf;+2!FPd1<3jl{47#siq diff --git a/textures/techage_appl_sieve4.png b/textures/techage_appl_sieve4.png index deaa6f7a6a1f11fb5cd0de5da6fe53d57c28ffbf..50061a61115fb313764a55e9b2cfdb83fcc76e00 100644 GIT binary patch delta 94 zcmaFN^nhuCN(f83qpu?a!^VE@KZ&di3=E9LLGDfr>(0r%1af!+d_r7(G7FuYoPfN< q#Kh7$tD8WIN`m}?862M7NCR>>3p^r=85p>QL70(Y)*Ok6)h__~+8ONt delta 97 zcmaFB^q6UaN;pq|Pl)UE1&f`WoER7w5)%_|ZYqBWWHFWm`2{mLJiCzw;v{*yyD)UH u%6b4foCO|{#S9GG!XV7ZFl&wkP>{XE)7O>#F*CQQgz$y8ZH^NwUH|}Ia2m`2 diff --git a/textures/techage_appl_sieve4_top.png b/textures/techage_appl_sieve4_top.png index 6a836ad8cc4c4ba109328b6d2defed0a1252fd17..edcf1263147b6ad3f2b98b80be14d01634219fdb 100644 GIT binary patch delta 953 zcmV;q14jI+GQ$Uu7=Hu<0001(NOmaz000DMK}|sb0I`n?{9y$E0004VQb$4nuFf3k z00009a7bBm000XU000XU0RWnu7ytkO6;Mo6MNDaNWLi#FPDDvXKyztafq#CVpP%Aw zih=+D00DGTPE!Ct=GbNc00SyXL_t(Y$L*9slI%7JhUW@^x_|c|&Fn!=?`8YV-tB(6 zLGFKrAfry+BD++ovWVQ9rV#vmfIdDx8nP8&7UKFbPBzH`&8P`q1_QLz5>mWDr>4GN{2Kv@im<%K*qCb;C^>UIsx0!2Lz*8bMJKdL}$%{fsL>mAP(5 zZF(8FEr41`{C|W}z#G6V3uy$!mX|#>*MZ?dEh~9F#uOfkm^M_|fn;Xc5@X7VW>6%o zn^tu$?-;_lLK|Gd;2aDtwH8;L12_E~T%tJV09bC6(+uhy_Ubh|m&-6XB&z~{W^oRh zp_p^I49=n4`1dxrguywiv~Gh-Wbso$kXwl>g9Az{gMZ7^f&TOQTK+9s=^U~$4=$Hs za6rSNg3*SA*^7aK?cEpNrLAD=H$`zT+wkR4@srBz%fW0SymRQNwu1kIRc9;ZXF`|l zePNx#sy78pk!d3I6b*Lw3#MiQh5Qyy>bCDixCwzxVaTr4YFS_!3>s&sptCQMR!Fp` z!*HFg)PI&sNswow@@HyTX+gxu;vuzp1Y(L>Sj|AC0OSbj97Kr<>OQ;g91=BlE-~v4 z40szOaYUBE;pxQ=F0*{qT9OSeb1a!mcNtt}8ytrM;wa?h(V1&m|e_gnZf+QZ9OT|AS{tz45YhQoNTdiB?0DZc!v z-saE=r;bN`Nf_YXv(FH<{tBhJa+R95Gv(O1@1ChY>v#8U43E?F*ecaB(;}DpyuT28 zK7Yc4{S9hI-fWGXGx>VF905u{<~!z3hfh#FHG=l%qdodpKNh%0wMx5dSx%m_-jDm& zqi5~U^oST)Pe|>ob(vFm*QXsF4!-yBFqgWW?2lFGxTk)5oQTe4W-0b_bSa;6U%t!h z@nh%SJAZ|T)8Kmgmm@~+!2ML&;48A!*D`oHS@T}ckN?u?znodiU*F$*{Nix_ALCz& b@sH0Rb>sj)*sT8D00000NkvXXu0mjfWsJx5 literal 6442 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 diff --git a/textures/techage_appl_sieve_top.png b/textures/techage_appl_sieve_top.png index d54fe6385c16b2f5be694347728e8b2542cee93d..ce3658fcb3cae95b343dba639d0224bb66f3e061 100644 GIT binary patch delta 155 zcmbQizM64@L_HHT0|Nt}$fR^2#ggvm>&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3i1T_gt+=-7B)0AczAd$m_PH+hWArJI!l85f*Bm1-ADs+q&;06 yLnJOICn&H@X=a=-k(KR)(&+;U9jg?17({-Sy6^mSXx4S0ItEWyKbLh*2~7Y^KrR;m delta 889 zcmV-<1BU#o0hkAn7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Re z0}B-qDqbqJ00003B}qg33x|u;b8JreB7A_>XaRg;H zMns}yE9gHEqArwZHd*Lw#Fe^tXK?&Ll$a=(VS>yr5V8o&48#_-ou((~IQ3PUtq{>J+Hni_<@Fq^e3WRE7a&UUTlAdmb~^0s_vo&5*t_}vx!-Q(@y_etr^RdZw$?D1Wr*Dvkef-8rL^PMa-nV7^`Q7`4vn!I- zlWQNofM0K-w97Qn>>NH`>I6>y@s(utWaqQjNumg)4XqW$e>XRuEY$;VZz#!vNb)4y z9zIp-1cF;zlBOxA9%>`nMJ``FL$>hIkB6mt;QWRof+eWcXMDJ_d6sP9yIZGAoj|w$ ztr)A&lHk-=k?SA7M7HpA@8MEC@ZO8Qh-kDIpgkY?^^8PN^Pv=lNtFM2qIYqvIXPf^Xp*cx%HKKk(3Q z1yOqO99SFtA|{#QFrF}p@LZuRq*Pv3uh z_wC1*AHTl;`~T-hRZB5Y4P%nGy9+}HtE>l*!&%@F2^8cO24O~qS#uYz~Fk(+#rQ@QmMzSw5+WYRt9Y4?wFA2ZCC!g ztmZ++f}+RaPt*2g*DPeJ%P#S??tAf0i}_|rlaoc^qs3S4-L4CBEuAWGR;M~=-?p_~ z5tb60t4?2Z3~6`yq!E3cr&|1dz^l(Z%HA7R#ve^>IQqmsflu4u;>m7%peq literal 0 HcmV?d00001 diff --git a/textures/techage_ash_side.png b/textures/techage_ash_side.png new file mode 100644 index 0000000000000000000000000000000000000000..4efa1a3e3892dffcc8fb19d3b2d7895286f71075 GIT binary patch literal 263 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 Y-y<)~B6#VWHYf%?UHx3vIVCg!0Fxi#$^ZZW literal 0 HcmV?d00001 diff --git a/textures/techage_coal_boiler_fire_hole.png b/textures/techage_coal_boiler_fire_hole.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2f7be4404339f811d328344ebefa52a120c091 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP-vyhOC^~$WL`amJs%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dD8AFv#WAGf*4t|vd7BLcS|3jDNSzQ=!LlNcz3a?`KqF_a zvVx;rno5R!!HP>enNCQ={;=;nHmUdh$@EAjpmx5OAusIBJC3z`R#&UsUAGcN%#m1Z6zEOF8pTTC=$MvEOGc}J{RR38# zb<%H!FfseBll}{TSbIYL=Px#32;bRa{vGf6951U69E94oEQKA0C-SLR7Ff_amd8D z#lW+De0YR|f1a9_eSCU%c5%bJzl4H-gMxl?aBGN%gpG`ek&unm)5*}y#LLLNm6Vdi zz_i@k(rRa7otv1sx2~_Oq*PK;qoSZaIywXd1RNY3!9x$3D+|$N6N^$dyPt4d>9fGT_tnrmtb;4>}o`G6b=~vltt?W9Gtx80d#-6OZu`MYg$$=vk zLAQ_bTWaMfIj3#m9P@Q00=kZ;V_ot`io}d^^FSX{Mr12Ft0eKxV)0Yk8hLz4=@Si9 z<_=)P$%f1U$6I4x5vMZQ{KEtSKqaur|>k%Su|F_Yd>FO0Z}P6x8O%{2pK!JBGLDme$NN zD@v;g3DFDc!3+SDT?qF8`(?5+kuYQG)*PVRsK;&NZPp$QZ?$kQ;!YoDg{(pGY;0OHytJ=4(LTF*PCu zvqI3%HpmL*W|k&p;+YZPv1t-OXs!aB7O*4~9{|sp_CUtZk#AhJGlJou<^eELAqo>f zDL0+`taGp*uQE_kB`LGEAP3kJ0l*GWD4_!(WFPU*VV-+qZViD*^l2oMCKwGCD5|=C zp3#8807O3lo^(F0vrh!rEK!iFP>*_F0Ze<4+3&+01FR} zNUkiITFIOf>qMnDj>s+~H0(%}FQq%h_d?9vb=Tz0#mNq^|ipz!0z zn+aK0v=%x}MFjn0g+sVUdJ((@Rk+&`bv>--^ZDo7kMrx9b{=O}D49D!^_07ScmWOJ z2L;>%Pi`|?|b0Th21Sc)`P<3bUK~SrWz*-D@p3P!kH0RRO|#`&|ArVz0b^8 zA8*xmJps^iYW>erNNNN25}EohZ+7jvh-FL+oR`!<)(TXgnpx!QTkT{KGNVqcwmPHC zc(P~xJe?GueSNo`yWSWwQCbTr+$Rn~^(5f)?{4>VNi$6}8_v$&8%aZN%gOui9~khT z`aKyN-@BwZSt#Dxx|i%Xj~scblr19z=uL%B3nEn^`|Tr-tv&KYzvH~OI&dQ*D$(-) zdT82-rA*FQVw%@qy>ZWf^4yiBb(Px#9cffpbVF}#ZDnqB0000007G(RVRU6=Aa`kWXdp*P zO;BVmWd{HN010qNS#tmY3ljhU3ljkVnw%H_000tDOjJe0z_HBAz17mkvaX)q-P4PQ zd6-Xf8~^|W`$M255ml~ZS8I4 z)W_S-MrlhWpJu!_3iK;kYt6NKTS{be%lY3NwQ#~6oa-Eh1@@j8lw!EVgOK=e6(`4G z!6h(f*EvI@lUOW|apbJNX>j;k-O*r#s-7F0dMhR5yiEsF$Y~wJUD`YHUQ_^3Db%^h zJn@xsc<X6uU769tjtVT$x?D4{M2=1o}FXhz@s%W)AylJ<;jAjR4cn}qCAeR zJx02LY&maknT%wM%CbW2t2xDoh)F|;$3^-0&dCu%s_m4vY{l#8*^YzqbA>~I22sG7 z%Nbm_a2s|6^mwkQGgrkWDctX5X(VOu^wFkr;7A+I=U32@Mans3ZhNKMm&QA!-N)K# z%#x__QrT}1EVowLQ+5Gwi5Ar%jcMHn$MV(yChL+_7(CNkd!&-axM|_f4KC}niYv@T zK3+W&v?RthL3enXq47FLcbjLhC6R)M(^-m>8UYsun`xXohuMRiy;G-Yp7nmucOa?WDP=BQgInN$r5iqfu-PsXAlFhGvB2))`!m?LfABCGC&Q(Z>5?}BD< z4KE(!*`jHaXSAcj)P3lVTr_M<6 zv$2*mxiD5+D=!T-YOdovA!=zYwM#=GFb(fPb+LiP_tB~4^TlYrGSIgtNI@7usySZ) z`sxD8p7BO5Cm%^8+lIFHNYnd`d(ac1%ucXYPfuzQ9Qy~q%=HxBy_`3*ZD8OzWZe%8Ulhc_}n z8$zANg5gN81|1l&o(U0w75#3AbV|Y-qGCr1s>u2iF&py%RDQZ~&EHe7wWV%^qmt!C zTK6>`FcH@Q6=&qKOTY+lG;@*+Ml%Ks!i%FVW=3Y4V;@l0v^NBA-OwzO68}KPR%Cip z3nPc+=fe<%feEt9vfCXTx@dYKe1agbJMkX+#FlDNdoy;4?b#f;Le&jX zNUg!HO&NQ@87#q-HzKz-tXIztCAu{^4Z>;$E&uAwcw>;QhzO&Lzc;6_hUDKb@-I0q zl{C?Tt!G}JX-(kXDGTX=-3jR#5D$YNK4EA8IaAqTwUya*JI(fkcIb3TON&ncSY3Z` zXD2uX>llI!AvV)`E9-y{rX5I%h8N-Lxnm`KrGIV&U5=xn&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3MvNpgt+=-7GAw_V)}x`>$h${a%A_}GY9g^Di+M2<`v_AwqEDm zL(%r*B3U5gN`m}?862M7NCR>fd%8G=Xq>N~JJFG^$w0uho#oq~ls4um+!9|Hgbu|` z`o@sgVeX*+K-8w``j%Xlg_{k2P1pZlZuPlyk?9`<4=mj!8!9t!6dK%^n8rWWY zZ11zopr00#JZm;e9( delta 644 zcmV-~0(1R9M69S3PgjKp1}RBPXBYL{9dqVM7!aq)M$=7+^pw`~n6B z2L1{&42=B&h^6X)gjgz&Dk0Irh*X3aDv%q=uAJIuA46r=PMz2}H_pO4o$t=i`@DDW z*AW=V_ivx|^qlAU*xG(Te-_2!Xdp@{D5Vet69hqEgwcoq=Nz1KSe9jsL7Nb8-P3F= zrPV$GDP`d_S|gBJ-sxN_rIfZuAhq07X=4JZ<%C>DU6Le51S;e9kseL&w4W zd)uT@7`o#zPX7GGvV>yAvT_0o2k-m6U97E-O4okIL7&T2+#8%rNm(1l%IfYcVXMzTFeg) zz7)PUH}972V~l|@1|bA`Jv*zM>6oUOEeIx1N+C%SBuSE0xq0&P@D=VHzph1CYmGRL z5r!cEU39|XZ~!SSe{jx0DFvkzob$>8v(qQI@$94)0nhUhh7p7iP%6e`a$zu^-Xw$o zV-$pt%ItPDdV!-4^_;EW?`7jFKH$2ikWxZQ3zS|cCxn18HWP5xozIf=YNJRY2!c!> z-?+UsH&wpf-P6;dR_5*L+Ra-Vg}EyHh4a9?IGB;c0000&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3i1c|gt!7}pUgra=^c1l-|Wcxi#tq#9OjZBzhFfMJEj?57Xi68 zo-U3d9>*~vq-RF;x^zq)OcV8!(JAHMU78Z9P!{={4&|3wr5%Y j#V>}l?A%y0CfG4p|CRe7md^YEXb^*^tDnm{r-UW|r4Tox delta 281 zcmcb}xR+^ygfs^m0|P@=TDBCBVoUONcVYMsf(!O8pFGh{qMp6P)7O>#F*BzatBB+( zP931CQ=Tr4Ar-gY-caN_{Yz)Z+&5|caXop{spFzfgydT zT>_9_3lfIt29k50{%Vi~DFX__1q-IVW-eP$CSSpLB+JQ_IdtJwkd&vZpUXO@geCwR C17th^ diff --git a/textures/techage_handsieve_sieve.png b/textures/techage_handsieve_sieve.png index dbf785c07725735abe4d5d57d737ffe610694ed1..109f3577b53618ba9ee3dd941a2a95d7cc04e467 100644 GIT binary patch delta 453 zcmV;$0XqJg4ATRU7=Hu<0001iRAe+L_t(2&s~l&Yr`-Q zg|C0*WCC6#5y)B#L$eDBnk~YEcO!$d3VW`hkUyXydvG9K3;9`<6bNz;o_O!$-XQ>R zOQ)Q&O39OiILq`aCutX_7neZyu)Q1XAoZvd_?)htbNVi|du|Nz@h>9VpcvLcA=v;o4EGilBf{DGU zxoF$tt5OI?k+bSm`L0TO#yR!ia)E3@YE2H>I(#e##y&vvo`Z%1FLS^O*kBG2fv!ME v_c$Rm1wuNq930sn`wDC9d>2h400000NkvXXu0mjflbN;m delta 1536 zcmV+b2LJif1DXtw7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Rc z1`h}iEegAqa{vGZr%6OXR9M5cmCcSDSslfH_tvdjU;Zk)-Q8{{1V$McF|$Bo%Yr>i zc1Sz`8y*L-Wxqg<}KRk!YCkxYi}gd{@vzpQg~&QEnL zm*C$0`d7d7{a%mhB&R%7e{{}qyWS8;$*`Ysy;u=N5dfzr6ZTb&4i%fthBWJOI8>bE zL)P0JQVPOQ^Yr-@C*vV;r1{fd{wn^nPu>T3^k7C9hWzX4B~PEf;F~AU&|wI`wkQ!& zQPl?TJ?mXT94flb@!;YD=^du7xw+jCh9R3>Nj@6zHS2AGwU%BlC5|E{`GDFqI3X#@1H;h(+jVr#;XMdEPo6&K;rs$A z1yya{0(k2U)J?+|Uwn?%nyNBPPsS`)E3^&?q(p0t4iz`c6<60w_GQJ37ca@ug!9>) z+BARwJX&jP=efFGf8d-WP0|koj0QcV3^+T<@p!)Z_BmHKD})Sq{OMz~4k`8rrsEM; z*Gq(eG>N&qy5{Szz9xwyvMj|rM-T+e&!>#@A&(wDKuY;xfX-UJ{OT)iH#=r$r>r+S zrjwkuZMk}J#eQFKaej(vER%eI_fYIhjA+#@sA# zsj8awX3L|87f2brAK;I_`Rr#!Su-ES)SH(S#hy3{v0X>owha0iX&RHpAyNj6h6ASK z5jqU1jNwO*e;)FKhjV7rF}vN4G)pMgGQzHz?m(kP2rFbe15V5C<_xiIf4(9#Bz=6%x_5XjgDLFWI*sRrF3> zN(tcvXYH$;|KhVwBKv~^k4MWJLR}Ci|DZl>X&VE0gvyA+1%V0?-XMaQ>&xML^=cn~ zR8Alzf9mqGL+c8Bj@hr++jkfOmUNu0!Z5Ao>~+f*o>pl28O zuEALeRuEX#^4fKVTcE3@6_i>LfaT*;NF|_#)oV0-a|A+$bft*F*ZPm zfWhcMnrsMkjOssNnAeCPe6`~T^pDL^w}mB3DpK#Mial+e5C%2Xc1^JaPe^UPM9L9$ ze-hI+dpr=RB{$m&+k$6-cP)#3fe0hY)=^g>S-(fMUDH+xo&?{makiqk72NK2m=e<8B-^m<^7KuHJLppQ@ycyn}s@A^#Rh&U>cv7h@-A}f&3SRCZ;#IWw1%u67Lv%n*= zn1O*?7=#%aX3dcR3W^5!gt#7@6uzg^>(soshK2?Y503@&XRfVt{`22t4p6~*PZ!4! zjq|w|5AwDc@UR>x)%x)4p!80*b(keGq`NmfVc#^(Q|wAKOLIAomfA9> zgVx7bZv0t2SIh9mfpcZEB$GBUznv^+!n1qd|8j|h)WZtX#PkgRN8Ssl$T(NPWSVc0 zqm*v5;ZcD^^|^wGsa826>}4x$F3owk;njx=uP)qG+-1p^*LU*l$GT+8$A6EVij>;o tx_xy*d#9T!h?4Ee3qQy=8$?dFf4@64?3AYUEuiBWJYD@<);T3K0RXV5d0GGf delta 903 zcmV;219<$q0;UI$7#j!%0001UdV2H#000JJOGiWi{{a60|De66laW3ce+P6)O+^Rc z1`h}kII|~BM*si=Gf6~2R9M5!m(6QjRT##9=bZaFlezQ3gqk+kids>EL|1Vk*;px3 zrCUMq9}uc8l-4fVg=SM+se5-8;|HQ>t6)kB#)Tg%!9^g6))X~nm~`&k&vRVF!kt-N zLhsPKxtx~=o_k*&ew;^Ke_49QXq{u*4$89W@AYt;8lLA9M=AASiec|2ZgU1DGkSmj zL%mT)=!juz(I3RL>Nd5YQ%VO=^pm|o%I>Wc+i_90%P`VpX@bsTJl`Qp61+x}Jla8L z35|9aAr#t31cp4%(K;`!8~3w+=kw z6e8cluXi}&`=m*XRJxQdkY>bB`gVq68A$$R5OzQ(7-PVlMo2|_S`udpArwI?q~`Zf zj)&ElrP+=U%0^jsY2CP=dg}#|+b-gs`O>;(yg|`Vd~xxFX|%g2+ok6B_FKMM;B zcl7-76FEgcasB=0jm{D*uT9VjS^o7n$BxdhvAJEz`+?yKML%)v-4{(B?cfE65khhC z`t}`4L4wf3f=B3nohvq|&sfC7=BE zNTnHgby1FhNQ{&4TK~~XH{h=?ljk`{4s{U9MqECB!W0K@f4N&}2F@&62n>F`vCoIE zmQI=C;IsAlN;hz9=To$iD3M_`W)UlwPMYH2o9%lm&A=N^dkCRWj*oIY#CPwUHpRi! z8?%*epu4p^e+r^;5}x}osx$-VpKFhTXwoFSZ*YASMB^m9a$}~_47~PaHVUFibKw4P zl_K~i&Vg4JJv`q**)Bq=$#dXn?^|>hV-fCOdM3?*Gm92knozITFvd)t1MRKL3?oga zSp%I+o&$IPji_o4-2FGA*FWf2nt?Z;?T&)z>!s7CVF-quzqU$s!~S0XKpoQHjDSeG zo=u)c=y01njZ1gKgLcSqKP!rc|AS!)lZ14d<+@>6-FP<~_mfEyG8iUgX+oY1N_WG9 dcF1wR{{T~d!@W{@+28;G002ovPDHLkV1mC?xR(F` diff --git a/textures/techage_lighter.png b/textures/techage_lighter.png new file mode 100644 index 0000000000000000000000000000000000000000..47ec8ccfa443a5cd1a981af4976ba9488c8f8a59 GIT binary patch literal 762 zcmV-xa0GcCq zjUiZZ6hQ>7bA8!_WdPi0h^{quQCNX;=32>OGIjuphA=EwaXDPVa?Q+95FuRvMI}NK zXqv38>?ciS0R|$C04t<6RU$J)Xr0}iRYg@GEda`LRb(Il5fg|sNcfrE+MWO>tf)g# z5fw9^a&yrD5s91toX+jpm}ZQ&Kh)ta^dLwKAH$=#&t=-+<+Rvowt)>)(09Sg5GF-?s zB36y_%x=&(ivY-`SKZklC3Z0!+BeODTuJ~zuf|6C%*cq`INUYV8nR3P%y)Zr_ew-W zG9t}BXARU%KOXJ~ros&@ECyhC$0D7lnwI_U*Q6+qV z0(Z(tM*w6*ashtHMNFB7td?;^WO@KZrBzj$q$oo&GR8!tHG_))Mx~N+Q|WOw+WNK9Bbuv+4b!b~V{rDgEk2TC)W2z1&m2 z$C;)J0B%A$r+()cZTp=fZF7*e`Yjn3dl%F?ITwHTdmnAS-=&SavShuQ*jj9bN{NwU zJI|5poDXa2Pe~?Hb<`MpBWGibuBH&evZQ(y$81UHG@k4fUu2~NAPS+e_8nAFQ!HWI zHt)!FlCpdybgjP>)_rfi_qNURoYTtozk=o1Z8)Ci@GGzl$2FdtzRs@;Em+)fIHAm@ zqm*D#=J7nG>k?(Z;%?6S@QcRQPG|_crBq^?mI(lc-Xf;i`=GdV zD)=ZRJ7x4VNkCm^XU=(khhGFp_QGha7t6eJx44$VratfcZd%2QR%5NQfOIIp@Ds@5 z^f3lFo5++>t#!v+yjCz>;)H)tfUOe*pbW)AQrK;kPPIj;RAE*Y$#4uM{r%*e1PquU zudl@_*GuFQKU28Vmft&90MwL@v3ackxL{*<(h`1=j`M6nu{HZ~KTie#DL|_Z6^d%4 z>EosF&{7`vqlJPQ9iJW8A`en8tARiuwudbQVGMuy#BNf!qc!F8{PkGSQn6A98~F=4 z0MbcPR8xL_vAF2I=qn|}Uh2An6|<9=1yne4NUL!WOYJFzWKzySeA+=9Dr>^hV!|FN zj)BwwFlPD}mzh4k39^Psu=L{{S(|!1+oC=q$OBSU{kIX`D*rx5HflYe@XVsWQh2eZpj6u*v{=Gu0B)ox>FA6qSJ*HFuCzbY96Fr)ohttT0000NS%G}U;vjb? zhIQv;UIICiC9V-A&iT2ysd*&~&PAz-C8;S2<(VZJ3hti10pX2&;y^{51s;*b3=G`D zAk4@xYmNj^P%gkH#MLLWa8rf*;vBnuozW-O^d6d?cVvF`gczf1TV)>{F<|fH0J)5@ zB*-tA!Qt7BG$6;q)5S4F<9u>JkcfyGFK=-nr#MwZAF1HRspLhgAQ8Vi?P pIk#ZR3XP1MS#1G^TyA^}413OT?Rmm&brNU>gQu&X%Q~loCIBM~N&)}? literal 0 HcmV?d00001 diff --git a/textures/techage_meltingpot_top_active.png b/textures/techage_meltingpot_top_active.png new file mode 100644 index 0000000000000000000000000000000000000000..1e952566b0bb5a90b9bc9b271ded0024db7fce79 GIT binary patch literal 634 zcmV-=0)_pFP)Px#8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X z4i^9b010qNS#tmY3ljhU3ljkVnw%H_001gbOjJex^#D{*PGDVINkv0$YH0u62gcwR z{pJCLfPF9!*hYm; zaskIafm=|x#9Sz)LhS)s(Bh)9V3!yL6!i!_Qg>JKILQRK0vUO=Bfb3p7hS#CZg*Xa zVs~3@>!L;z-~cpufUY;JVE{tM8(js8z=hFO$mIz5z-Q2-cii(Y89pdt^9B`GvMKpW zNPB~p^ymfU`nAq?>pSFm-ZWKF;tCI#$DQ-YBH>I+vn?ZPtnRyJH@C*ma zl;`UZdBVUO$2{^tSj@BcQ6CG-vM`Umv3w8nNW~}*_fX<7AmN$x;ZM6y`gr)LkN@#e zAFePDLg1dIJbHE1hyOM6kQjL8mr(o~=Ap?i^r!kH9zvh|A`^M^=GJhFaf|)JWqz*k zAdE?$#B-7#7kN}?;qji`DQD$d{3}1|Zsh6VgeRMMSZCoOvPa%NJO#U+%Ddz5C%^ zL8lHXpF9wL^-S`eOPLR^<-U99@Z>?s`zKW&-*tTb)bsoM#J|5LZ?)PG2-MD4666=m z;PC858ivL>4nJa0`PlBg3pY5H=O_D8JJf)*y9^Us|F z3I%w&IEF}Ep4;mv)S$q@;OM(p;m`lsnnS@ZEt!}rf^zrJ?-`_;4c|07PIcE*w*zhDN3 zXE)M7oFs2|7lsa2Sq~tGv%n*=n1O*?7=#%aX3dcR3bL1Y`ns||VwD!uU}hAkoB$NE z^K@|xk+__kaDa7(Mc~gnOm{SD`0VQJL{xKR_SD$uoY8Qy+4IInW7mxxHv)d>ta(wR lruA7eaPPqrCmQ$}81BqtbP5r-cm*_x!PC{xWt~$(698FPQilKl literal 0 HcmV?d00001 diff --git a/textures/techage_meridiumpick.png b/textures/techage_meridiumpick.png new file mode 100644 index 0000000000000000000000000000000000000000..de24b64e9d9f474099a55f561c6fcad87b869c65 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFP2=EDUt!H2`QRKH%5wKPjaMl<1 zH{}hql!3p^r=85p>QL70(Y)*K0-AbW|YuPgf_R%ro!kuweq%Yj0Eo-U3d z9M_W*8kpCW)+em_T%NGzZ&tvyRqxpZ*gjXWavUx-bFqz9zFudbe7GU;F5?6T@{@_mGlLsZQpH+Q(=k)1aO>_%)r1c48n{Iv*t(u1=&kHeO=ifu}TYSawT5>Pzn@s z_jGX#k+{6}w6{=$0uS>A5qnXC#^3Y9%0kjhQp@xwGrV88WOsEqZ(j4lg3g&X&klL7 uv#J&I)=K&^&8F&Z?jE^g^=*=N#f-hFOgg7uU0Vw@lEKr}&t;ucLK6UI2UlDG literal 0 HcmV?d00001 diff --git a/textures/techage_meridiumsword.png b/textures/techage_meridiumsword.png new file mode 100644 index 0000000000000000000000000000000000000000..621568db81f7fd6c4b068ce2046af2d8deb9c269 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv3GfMV1=5!4LjI<_IiA8985xr& zPhPpi>d>CxGsk0JKdt%vw&nlN3EiekmjKlA%&K)~ zY6-kMlRFVdQ&MBb@02yFJ^#A|> literal 0 HcmV?d00001 diff --git a/textures/techage_smoke.png b/textures/techage_smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..07b9fae5682f1c3d7722d869310f789f81dd89d5 GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF?e9DTYnu;u*TEHF@)o~Zm%KNAqNrGbk3KX zS@b_Jt35r`wfE$rY5PAitb4@h6mUD^@y5S`%PTM2uuXiY%y!B5>%*Ty*Cwy;D4)Er zNm%)9muuC%rJY47dWH8kKYf#XVP1E?DT^l*kAm05*k*Pj z57`c&GGr?vy1lHnHS_`M`UM)TS=w|A4BT*kZ+oO{=MvFXx4UxHkB>ipO14t}x)AS^ z=UD~Yq@cwHFa=28h0c%$3)Gs<91(^WY=KdfN zEgkQ0=fHw_^!~VL5uMYFFy8kI77KJ*Yl_0caf^YC(#iya1ZJc4+(RNV>bn`I z;2s7*^ssY}5wTzs2r&f7KnSyyHZ9b%in#%Xzhk>PPn6c0qFWS;BpRShXCk#A*A3}Qbd3k0?=b< zf?%_wcXA7O4*&{_var^+!odWX6hKc|#el9GUsD$Hj6R42=q}HZ741F-9A-dFP}nHt z8vrYw#1^grvT}b55a*}ABP>?PC00000NkvXXu0mjfhNKKR delta 1420 zcmV;71#|kb1-c867()mK00020X>r~F000JJOGiWi{{a60|De66lK=n!32;bRa{vGf z6951U69E94oEQKA00(qQO+^Re0}&1f48Lf8K#@Kxe+3>%L_t(|+U;9UXdG1-|Gi}o z(hvkwZR$a-T}`dK-3T_&qur8&S`65t9s(XLhJr##j6!m3Kx+1)2oW#B9;!5lRji0L z@l*=iOO}acS8}KzwlUdS3@IsUpBKOBn>X*hd9#0JlHL6-lbH+*zu)_PzwdkBOMq^4 zqoadGfBnH1zg^_?Gr#vQ_&tvAM}}Ek1SBH<9>4qNEqwR#Q9SebV>q~PAD-R+BnBs^ zc_%1<=L+-g&BEvjj5{j;fZE^x;J0c80Py1*Bdi?>@PG$^J2!9O%6sp)Bv6!>mm?)1 z5jFTbH*Wv{cJ=Q?t}u^$CgpiKn@;1{`637Ke^5Ipz_;?M5Yr)QH6-E&Zwf&T${5Rn zk7dEPBLPa;%N{^iRVp}@s!YOZ8T<=R0FU1(Gp77ic zf6dGS07_F6N<);*@zj?io@c8Yz_l7MR}P1XwHmyv0s>BeRiPrmsZ>2_A(F7VasR!r zBqW**US)V9_@S9u7yQ=xn)1D&nOWpBDW5UQ72@;PiyrWCd4Ol-WmD6c8~{A9`(fqt zt@SmRC>;q-rHTjlKZS2j6#)RZ(Gyz=e{cW@l0U6}J)!djN{jOHGEyIoga+R%1uz0g z`7O4)x)lx+DkX@XkWHtt@vv`RMVU>hcn+5Oz}u~q?}CxkV`AcH(~ z=6!7{kRcwwbeYFl2DQd$s{#?Uf65@q51dNXlWKJTbpIXzNXOO(5DB75*_vWdKOp`t zb6~)uF!6qHa@vWL0Am^qU%2R!VB7rIHeeLF(KnU7LDXk*4YkWbJp+-bstQh}ia)Mi z@l}M#6fl+r|LPT>o<>R68LHHhE6n4ElP|$wzNaHsfH=hkfY-i;yP=W$e*M%Vw^w$@DY%R13UnHeDwZGtH9f) z1cA>L=6&E*^U0-iXZs>a0TXy}kooQ+@M`I2Z2WrNldmEvVm`}?kYyic^;<@OE%-l72m6A-1HkXK zO%c3tj-Vq#9>R)2eqaLM)D$4_dwPSx8|L?%z^N1=dIMeanUppSg1`enavOnn7bKra z`G}CKgsN&t=Ma4WZnb41~$Su$x2zF`HZf57X`>gqYRGAW{- zd~d1(A^>kxgjgk5GFcb+x=k=*@X(p%;N&!QcdQX+8?N&agKzGnrjx;2n;`uGlCHzs zso<>?5T?fOH1L50v^#hs0i6I|OF*Z9R}#=k;C%#i8hB+Z+)3bl1k9Dee!AXXbw0EL z2H%>6NJ0Z!+MQbkKBmvS&DR${gFN19H}J+jVDjW){?{*8T@u<6y!8gC(SJ+nMmM^# aZR0kAm0RR90sHmvY($bMa0RR91 zsZRiCF94E~lCZF_d~s(swIJ~T0007XQchC8$r@<)NYf1?3D|KNMK=l9B;PLqwur;ffYq1kP%kDR z1%&`u=YA&<9UX6R>%fBk;r)IwB06Unp}nscA`a+`F$ECg=dRkvfB}cnT$3W0RIy-x z?2(R1(^#;#v4!J=tqDaYTE`(XM@_WXqk?83R+52p9P#f75qH7eu|>~8X=MUI0=+V3 z_K=8-d{<)^?4bdKhn+n}#6nCUL?0wQA@o|tw9w8eI+7nH!hVb?Z)XEA33~H;DP}f} zg8S$@tL>#&xt?N99!DXd`Lw4H8m`oTVyEM@3Q})k!C_oT*lAHVu#Q6H{)y+^hL{DG zu8Tq(yl>`>^pAYSjuF87J3W~ce8W%xc2$GcuIT23JGv2os(t}3=TJHXK+Ovw1;3IZ z0^AdT9zzoZt1Z2gbHH-|(6DG0*4j=am;jRk=qalhP*vk=Vg)=#A4CFFr{~C2if$7F zb~7L-Y&7LF04pBE7Cr)G<^CKX&JXV`ELNCN3UL451M)8b;}q-EkLr(41JuQH8lY62 f22lM{|Fqr!1vbqU-gXOu00000NkvXXu0mjf52FoZ delta 1380 zcmV-q1)KW01(FMp7()mK00020X>r~F000JJOGiWi{{a60|De66lK=n!32;bRa{vGf z6951U69E94oEQKA00(qQO+^Re0}&1dGE6@KX^}oFe+2ePL_t(|+U;90Ya>S#{zj-X zZsH;+jp9avV;5(OG%gGY?&Bt~MaqL184}W@y7I~clfW9MPoZ@B1$UjB6pLALpsgY> zCk)x+h;eC83j4{kqc^*=@@m(X^gu|v5-i_0-}~O1H#V@2b*yxVsvmf9aUtpR&CLyN zZ*Q@^f4wbMg>V^$p#&h+2!cSaf&ye1hSLkr^C0B|?%{9<0B~JbEF%Fa@BlC#k8yBt zkRpMIsFH+6)!@hDF#w?5ZX*ms2$9C>I1ZvHk^nwi1_jKlJQsR8RH=qW-QcrAQ3D%? zJCKJvkWY~SR`$9F&^wn3aUAO;^mOpwdK2|NIFH?JQ>!a_|@1+RkD6+zdk3P=HJ?CfYrQ@IC=f&x>Iaiw3W&fPfQVE;th6I8LR7O2XyU!|#fc&?p-` ze`k0q_#pC?SdKxr`?4iWs|76xBd3J3y7`K`9Q zrWGy{loBRSa2y9me@3-d0ZA)l%3IUwe-jy|bSSEXqOEgTCMXEFl#+eSoLawaBFiI8exN?baU9rgr^{A= z$dYKD3J84O%IhR>1V>Sn)Rh842w_}xodkWgb@2N4be*LHN(Gx)<06L*Qgv*-f0Y$N z%-t90m0>g*83{i*Il393oinqpo*pnlisbW#da zuY(|nYHNh@a&|NtO-ZnAery{se~MnO$MyzQ5+J(Wu91*=Oi@*fIz!MKBO)URf}}PA z4tF5``VN?LlpHPa;7jBCk2(NT5->`r6&yMGyo* zrBy%$PeCL|DN~M3pVh53S0PkFs6zO@kHKItLqwgOAPb)2@(TFr?HyRXe@oQL#|#Pj ze!=&B*bHRv2RB;)J}z7VdEf!y_irDRpzj`u6zUaJv$FxuO{C7w&Y*%{m;!9T19u~A z060BI?pc7Uh@vtl@Z6rDQ5X16*dXvCuZHU;No1L1ufAu2FP(tH9mp43`QQQQBkasM zf{6%y2+PSo3w)^r5ctnGfAhf`PH%*I7^0h^-bT}y&GS6={Luz{X(m_#_>?CJA*5Ok zIYPLqp`Al)>4vwEGH3$N9a+tBcsWA^XNrTtU<$n1Y{GS2EAWp_0S^4-RcYX9fkfUp zp_=da=K>#XAJ8ZpJQs6<&m=;v63u4QD&I>>fr`N!Q-j+2d4SJNe*jnKD+XWg9(|R; zTbDul10++2_gukSDWFJ=e{SG&30QXUnFOo?cq0L;0-hycmB7ysuxjAhR(O@b&k%5W z4)N}v<*M_A74X%|H&96^xSxOiCI|qpe)&=U`}Pm$=_pVe@j`%I@a-I7UO?bWlI{D#pLJ!0000