From 65a51d1091b5280773eedfd1e48012abcd5be4f8 Mon Sep 17 00:00:00 2001 From: Joachim Stolberg Date: Sun, 10 Mar 2019 13:53:53 +0100 Subject: [PATCH] consumer model added for grinder --- basic_machines/grinder.lua | 294 ++++++++++++++++++ basic_machines/pusher.lua | 8 +- basis/consumer.lua | 227 ++++++++++++++ basis/node_states.lua | 15 +- init.lua | 3 +- ...age_defect.png => techage_appl_defect.png} | Bin textures/techage_appl_grinder.png | Bin 0 -> 1874 bytes textures/techage_appl_grinder4.png | Bin 0 -> 3430 bytes textures/techage_appl_hole_electric.png | Bin 0 -> 259 bytes ...age_pusher.png => techage_appl_pusher.png} | Bin ...pusher14.png => techage_appl_pusher14.png} | Bin textures/techage_frame4_ta2_top.png | Bin 0 -> 428 bytes textures/techage_frame4_ta3_top.png | Bin 0 -> 1115 bytes textures/techage_frame4_ta4_top.png | Bin 0 -> 317 bytes 14 files changed, 538 insertions(+), 9 deletions(-) create mode 100644 basic_machines/grinder.lua create mode 100644 basis/consumer.lua rename textures/{techage_defect.png => techage_appl_defect.png} (100%) create mode 100644 textures/techage_appl_grinder.png create mode 100644 textures/techage_appl_grinder4.png create mode 100644 textures/techage_appl_hole_electric.png rename textures/{techage_pusher.png => techage_appl_pusher.png} (100%) rename textures/{techage_pusher14.png => techage_appl_pusher14.png} (100%) create mode 100644 textures/techage_frame4_ta2_top.png create mode 100644 textures/techage_frame4_ta3_top.png create mode 100644 textures/techage_frame4_ta4_top.png diff --git a/basic_machines/grinder.lua b/basic_machines/grinder.lua new file mode 100644 index 0000000..dd01329 --- /dev/null +++ b/basic_machines/grinder.lua @@ -0,0 +1,294 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + TA2/TA3/TA4 Grinding Cobble/Basalt to Gravel + +]]-- + +-- 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("tubelib2") +local I,_ = dofile(MP.."/intllib.lua") + +local STANDBY_TICKS = 10 +local COUNTDOWN_TICKS = 10 +local CYCLE_TIME = 4 + + +-- Grinder recipes +local Recipes = {} + +local function formspec(self, pos, mem) + return "size[8,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[context;src;0,0;3,3;]".. + "item_image[0,0;1,1;default:cobble]".. + "image[0,0;1,1;techage_form_mask.png]".. + "image[3.5,1;1,1;techage_form_arrow.png]".. + "image_button[3.5,2;1,1;".. self:get_state_button_image(mem) ..";state_button;]".. + "list[context;dst;5,0;3,3;]".. + "item_image[5,0;1,1;default:gravel]".. + "image[5,0;1,1;techage_form_mask.png]".. + "list[current_player;main;0,4;8,4;]".. + "listring[context;dst]".. + "listring[current_player;main]".. + "listring[context;src]".. + "listring[current_player;main]" +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" and TRD(pos).State:get_state(M(pos)) == techage.STANDBY then + TRD(pos).State:start(pos, M(pos)) + end + return stack:get_count() +end + +local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) + local inv = M(pos):get_inventory() + local stack = inv:get_stack(from_list, from_index) + return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) +end + +local function allow_metadata_inventory_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + return stack:get_count() +end + +local function src_to_dst(src_stack, idx, num_items, inv, dst_name) + local taken = src_stack:take_item(num_items) + local output = ItemStack(dst_name) + print("taken:get_count()", taken:get_count(), output:get_count() * taken:get_count()) + output:set_count(output:get_count() * taken:get_count()) + if inv:room_for_item("dst", output) then + print("output:get_count()", output:get_count()) + inv:set_stack("src", idx, src_stack) + inv:add_item("dst", output) + return true + end + return false +end + +local function grinding(pos, trd, mem, inv) + local num_items = 0 + for idx,stack in ipairs(inv:get_list("src")) do + if not stack:is_empty() then + local name = stack:get_name() + if Recipes[name] then + if src_to_dst(stack, idx, trd.num_items, inv, Recipes[name]) then + trd.State:keep_running(pos, mem, COUNTDOWN_TICKS) + else + trd.State:blocked(pos, mem) + end + else + trd.State:fault(pos, mem) + end + return + end + end + trd.State:idle(pos, mem) +end + +local function keep_running(pos, elapsed) + local mem = tubelib2.get_mem(pos) + local trd = TRD(pos) + local inv = M(pos):get_inventory() + grinding(pos, trd, mem, inv) + return trd.State:is_active(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) + TRD(pos).State:state_button_event(pos, mem, fields) +end + +local function can_dig(pos, player) + if minetest.is_protected(pos, player:get_player_name()) then + return false + end + local inv = M(pos):get_inventory() + return inv:is_empty("dst") and inv:is_empty("src") +end + + +local tiles = {} +-- '#' will be replaced by the stage number +-- '{power}' will be replaced by the power PNG +tiles.pas = { + -- up, down, right, left, back, front + "techage_appl_grinder.png^techage_frame_ta#_top.png", + "techage_filling_ta#.png^techage_frame_ta#.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png", +} +tiles.act = { + -- up, down, right, left, back, front + { + image = "techage_appl_grinder4.png^techage_frame4_ta#_top.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 1.0, + }, + }, + "techage_filling_ta#.png^techage_frame_ta#.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png", +} +tiles.def = { + -- up, down, right, left, back, front + "techage_appl_grinder.png^techage_frame_ta#_top.png", + "techage_filling_ta#.png^techage_frame_ta#.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png^techage_appl_defect.png", + "techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png^techage_appl_defect.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png^techage_appl_defect.png", + "techage_filling_ta#.png^{power}^techage_frame_ta#.png^techage_appl_defect.png", +} + +local tubing = { + on_pull_item = function(pos, in_dir, num) + local meta = minetest.get_meta(pos) + if meta:get_int("pull_dir") == in_dir then + local inv = M(pos):get_inventory() + return techage.get_items(inv, "dst", num) + end + end, + on_push_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + if meta:get_int("push_dir") == in_dir then + local inv = M(pos):get_inventory() + return techage.put_items(inv, "src", stack) + end + end, + on_unpull_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + if meta:get_int("pull_dir") == in_dir then + local inv = M(pos):get_inventory() + return techage.put_items(inv, "dst", stack) + end + end, + on_recv_message = function(pos, topic, payload) + local resp = TRD(pos).State:on_receive_message(pos, topic, payload) + if resp then + return resp + else + return "unsupported" + end + end, + on_node_load = function(pos) + TRD(pos).State:on_node_load(pos) + end, + on_node_repair = function(pos) + return TRD(pos).State:on_node_repair(pos) + end, +} + +local node_name_ta2, node_name_ta3, node_name_ta4 = + techage.register_consumer("grinder", I("Grinder"), tiles, { + cycle_time = CYCLE_TIME, + standby_ticks = STANDBY_TICKS, + has_item_meter = true, + aging_factor = 10, + formspec = formspec, + tubing = tubing, + after_place_node = function(pos, placer) + print("my after_place_node") + local inv = M(pos):get_inventory() + inv:set_size('src', 9) + inv:set_size('dst', 9) + end, + can_dig = can_dig, + node_timer = keep_running, + on_receive_fields = on_receive_fields, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, + groups = {choppy=2, cracky=2, crumbly=2}, + sounds = default.node_sound_wood_defaults(), + num_items = {0,1,2,4}, + power_consumption = {0,2,3,4}, + }) + +minetest.register_craft({ + output = node_name_ta2, + recipe = { + {"group:wood", "default:tin_ingot", "group:wood"}, + {"tubelib:tubeS", "default:mese_crystal", "tubelib:tubeS"}, + {"group:wood", "default:tin_ingot", "group:wood"}, + }, +}) + +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, + }) +end + +function techage.add_grinder_recipe(recipe) + Recipes[recipe.input] = recipe.output + if minetest.global_exists("unified_inventory") then + recipe.items = {recipe.input} + recipe.type = "grinding" + unified_inventory.register_craft(recipe) + end +end + + +techage.add_grinder_recipe({input="default:cobble", output="default:gravel"}) +techage.add_grinder_recipe({input="default:desert_cobble", output="default:gravel"}) +techage.add_grinder_recipe({input="default:mossycobble", output="default:gravel"}) +techage.add_grinder_recipe({input="default:gravel", output="default:sand"}) +techage.add_grinder_recipe({input="gravelsieve:sieved_gravel", output="default:sand"}) +techage.add_grinder_recipe({input="default:coral_skeleton", output="default:silver_sand"}) +techage.add_grinder_recipe({input="tubelib:basalt_stone", output="default:silver_sand"}) + +if minetest.global_exists("skytest") then + techage.add_grinder_recipe({input="default:desert_sand", output="skytest:dust"}) + techage.add_grinder_recipe({input="default:silver_sand", output="skytest:dust"}) + techage.add_grinder_recipe({input="default:sand", output="skytest:dust"}) +else + techage.add_grinder_recipe({input="default:desert_sand", output="default:clay"}) + techage.add_grinder_recipe({input="default:silver_sand", output="default:clay"}) + techage.add_grinder_recipe({input="default:sand", output="default:clay"}) +end + +techage.add_grinder_recipe({input="default:sandstone", output="default:sand 4"}) +techage.add_grinder_recipe({input="default:desert_sandstone", output="default:desert_sand 4"}) +techage.add_grinder_recipe({input="default:silver_sandstone", output="default:silver_sand 4"}) + +techage.add_grinder_recipe({input="default:tree", output="default:leaves 8"}) +techage.add_grinder_recipe({input="default:jungletree", output="default:jungleleaves 8"}) +techage.add_grinder_recipe({input="default:pine_tree", output="default:pine_needles 8"}) +techage.add_grinder_recipe({input="default:acacia_tree", output="default:acacia_leaves 8"}) +techage.add_grinder_recipe({input="default:aspen_tree", output="default:aspen_leaves 8"}) diff --git a/basic_machines/pusher.lua b/basic_machines/pusher.lua index 2ce7cfb..1fb6697 100644 --- a/basic_machines/pusher.lua +++ b/basic_machines/pusher.lua @@ -185,10 +185,10 @@ local function register_pusher(stage, num_items) -- up, down, right, left, back, front "techage_filling_ta"..stage..".png^techage_frame_ta"..stage.."_top.png^techage_appl_arrow.png", "techage_filling_ta"..stage..".png^techage_frame_ta"..stage..".png", - "techage_filling_ta"..stage..".png^techage_frame_ta"..stage..".png^techage_appl_outp.png^tubelib_defect.png", - "techage_filling_ta"..stage..".png^techage_frame_ta"..stage..".png^techage_appl_inp.png^tubelib_defect.png", - "techage_appl_pusher.png^[transformR180]^techage_frame_ta"..stage..".png^tubelib_defect.png", - "techage_appl_pusher.png^techage_frame_ta"..stage..".png^tubelib_defect.png", + "techage_filling_ta"..stage..".png^techage_frame_ta"..stage..".png^techage_appl_outp.png^techage_appl_defect.png", + "techage_filling_ta"..stage..".png^techage_frame_ta"..stage..".png^techage_appl_inp.png^techage_appl_defect.png", + "techage_appl_pusher.png^[transformR180]^techage_frame_ta"..stage..".png^techage_appl_defect.png", + "techage_appl_pusher.png^techage_frame_ta"..stage..".png^techage_appl_defect.png", }, techage = { diff --git a/basis/consumer.lua b/basis/consumer.lua new file mode 100644 index 0000000..05063eb --- /dev/null +++ b/basis/consumer.lua @@ -0,0 +1,227 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Consumer node basis functionality. + It handles: + - 3 stages of nodes (TA2/TA3/TA4) + - power consumption + - node state handling + - registration of passive, active and defect nodes + - Tube connections are on left and right side (from left to right) + - Power connection are on front and back side (front or back) +]]-- + +-- 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 +local TRDN = function(node) return (minetest.registered_nodes[node.name] or {}).techage end + +local consumer = techage.consumer + +local function valid_power_dir(pos, power_dir, in_dir) + return power_dir == in_dir or power_dir == tubelib2.Turn180Deg[in_dir] +end + +local function start_node(pos, mem, state) + consumer.turn_power_on(pos, TRD(pos).power_consumption) +end + +local function stop_node(pos, mem, state) + consumer.turn_power_on(pos, 0) +end + +local function turn_on_clbk(pos, in_dir, sum) + local mem = tubelib2.get_mem(pos) + local trd = TRD(pos) + local state = trd.State:get_state(mem) + if sum <= 0 and state == techage.RUNNING then + trd.State:fault(pos, mem) + end +end + +local function prepare_tiles(tiles, stage, power_png) + local tbl = {} + for _,item in ipairs(tiles) do + if type(item) == "string" then + tbl[#tbl+1] = item:gsub("#", stage):gsub("{power}", power_png) + else + local temp = table.copy(item) + temp.image = temp.image:gsub("#", stage):gsub("{power}", power_png) + tbl[#tbl+1] = temp + end + end + return tbl +end + +function techage.register_consumer(base_name, inv_name, tiles, tNode) + local names = {} + for stage = 2,4 do + local name_pas = "techage:ta"..stage.."_"..base_name.."_pas" + local name_act = "techage:ta"..stage.."_"..base_name.."_act" + local name_def = "techage:ta"..stage.."_"..base_name.."_def" + local name_inv = "TA"..stage.." "..inv_name + names[#names+1] = name_pas + + local power_network = techage.Axle + local on_recv_message = tNode.tubing.on_recv_message + local power_png = 'techage_axle_clutch.png' + + if stage > 2 then + power_network = techage.ElectricCable + on_recv_message = function(pos, topic, payload) + return "unsupported" + end + power_png = 'techage_appl_hole_electric.png' + end + + local tState = techage.NodeStates:new({ + node_name_passive = name_pas, + node_name_active = name_act, + node_name_defect = name_def, + infotext_name = name_inv, + cycle_time = tNode.cycle_time, + standby_ticks = tNode.standby_ticks, + has_item_meter = tNode.has_item_meter, + aging_factor = tNode.aging_factor, + formspec_func = tNode.formspec, + start_node = start_node, + stop_node = stop_node, + }) + local tTechage = { + State = tState, + num_items = tNode.num_items[stage], + turn_on = turn_on_clbk, + read_power_consumption = consumer.read_power_consumption, + power_network = power_network, + power_side = "F", + valid_power_dir = valid_power_dir, + power_consumption = tNode.power_consumption[stage], + } + + tNode.groups.not_in_creative_inventory = 0 + + minetest.register_node(name_pas, { + description = name_inv, + tiles = prepare_tiles(tiles.pas, stage, power_png), + techage = tTechage, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + local mem = consumer.after_place_node(pos, placer) + local meta = M(pos) + local node = minetest.get_node(pos) + meta:set_int("push_dir", techage.side_to_indir("L", node.param2)) + meta:set_int("pull_dir", techage.side_to_indir("R", node.param2)) + local number = "-" + if stage > 2 then + number = techage.add_node(pos, name_pas) + end + if tNode.after_place_node then + tNode.after_place_node(pos, placer, itemstack, pointed_thing) + end + TRD(pos).State:node_init(pos, mem, number) + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + if tNode.after_dig_node then + tNode.after_dig_node(pos, oldnode, oldmetadata, digger) + end + techage.remove_node(pos) + TRDN(oldnode).State:after_dig_node(pos, oldnode, oldmetadata, digger) + consumer.after_dig_node(pos, oldnode) + end, + + after_tube_update = consumer.after_tube_update, + can_dig = tNode.can_dig, + on_rotate = screwdriver.disallow, + on_timer = tNode.node_timer, + on_receive_fields = tNode.on_receive_fields, + allow_metadata_inventory_put = tNode.allow_metadata_inventory_put, + allow_metadata_inventory_move = tNode.allow_metadata_inventory_move, + allow_metadata_inventory_take = tNode.allow_metadata_inventory_take, + + drop = "", + paramtype2 = "facedir", + groups = table.copy(tNode.groups), + is_ground_content = false, + sounds = tNode.sounds, + }) + + tNode.groups.not_in_creative_inventory = 1 + + minetest.register_node(name_act, { + description = name_inv, + tiles = prepare_tiles(tiles.act, stage, power_png), + techage = tTechage, + + after_tube_update = consumer.after_tube_update, + on_rotate = screwdriver.disallow, + on_timer = tNode.node_timer, + on_receive_fields = tNode.on_receive_fields, + allow_metadata_inventory_put = tNode.allow_metadata_inventory_put, + allow_metadata_inventory_move = tNode.allow_metadata_inventory_move, + allow_metadata_inventory_take = tNode.allow_metadata_inventory_take, + + paramtype2 = "facedir", + diggable = false, + groups = tNode.groups, + is_ground_content = false, + sounds = tNode.sounds, + }) + + minetest.register_node(name_def, { + description = name_inv, + tiles = prepare_tiles(tiles.def, stage, power_png), + techage = tTechage, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + local mem = consumer.after_place_node(pos, placer) + local meta = M(pos) + local node = minetest.get_node(pos) + meta:set_int("push_dir", techage.side_to_indir("L", node.param2)) + meta:set_int("pull_dir", techage.side_to_indir("R", node.param2)) + local number = "-" + if stage > 2 then + number = techage.add_node(pos, name_pas) + end + if tNode.after_place_node then + tNode.after_place_node(pos, placer, itemstack, pointed_thing) + end + TRD(pos).State:defect(pos, mem) + end, + + after_tube_update = consumer.after_tube_update, + on_rotate = screwdriver.disallow, + on_receive_fields = tNode.on_receive_fields, + allow_metadata_inventory_put = tNode.allow_metadata_inventory_put, + allow_metadata_inventory_move = tNode.allow_metadata_inventory_move, + allow_metadata_inventory_take = tNode.allow_metadata_inventory_take, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + if tNode.after_dig_node then + tNode.after_dig_node(pos, oldnode, oldmetadata, digger) + end + techage.remove_node(pos) + consumer.after_dig_node(pos, oldnode) + end, + + paramtype2 = "facedir", + groups = tNode.groups, + is_ground_content = false, + sounds = tNode.sounds, + }) + + techage.register_node(name_pas, {name_act, name_def}, tNode.tubing) + power_network:add_secondary_node_names({name_pas, name_act}) + end + return names[1], names[2], names[3] +end diff --git a/basis/node_states.lua b/basis/node_states.lua index 69fc134..42d68f4 100644 --- a/basis/node_states.lua +++ b/basis/node_states.lua @@ -406,6 +406,13 @@ function NodeStates:on_node_load(pos, not_start_timer) return end + -- wrong number? + local info = techage.get_node_info(number) + if not info or not info.pos or not vector.equals(pos, info.pos) then + swap_node(pos, "techage:defect_dummy") + return + end + -- state corrupt? local state = mem.techage_state if state == 0 then @@ -472,10 +479,10 @@ minetest.register_node("techage:defect_dummy", { tiles = { "techage_filling_ta2.png^techage_frame_ta2.png", "techage_filling_ta2.png^techage_frame_ta2.png", - "techage_filling_ta2.png^techage_frame_ta2.png^techage_defect.png", - "techage_filling_ta2.png^techage_frame_ta2.png^techage_defect.png", - "techage_filling_ta2.png^techage_frame_ta2.png^techage_defect.png", - "techage_filling_ta2.png^techage_frame_ta2.png^techage_defect.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png", + "techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png", }, drop = "", groups = {cracky=2, crumbly=2, choppy=2, not_in_creative_inventory=1}, diff --git a/init.lua b/init.lua index e299c05..a6cb66f 100644 --- a/init.lua +++ b/init.lua @@ -19,6 +19,7 @@ dofile(MP.."/basis/trowel.lua") -- hidden networks 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 -- Steam Engine dofile(MP.."/steam_engine/drive_axle.lua") @@ -44,5 +45,5 @@ dofile(MP.."/basic_machines/grinder.lua") --dofile(MP.."/fermenter/gasflare.lua") -dofile(MP.."/nodes/test.lua") +--dofile(MP.."/nodes/test.lua") --dofile(MP.."/mechanic/perf_test.lua") diff --git a/textures/techage_defect.png b/textures/techage_appl_defect.png similarity index 100% rename from textures/techage_defect.png rename to textures/techage_appl_defect.png diff --git a/textures/techage_appl_grinder.png b/textures/techage_appl_grinder.png new file mode 100644 index 0000000000000000000000000000000000000000..87e5c5825860c7cd6f5f85f7a1027149771d28c9 GIT binary patch literal 1874 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 T@-6&;p~T?n>gTe~DWM4f9Hgw4 literal 0 HcmV?d00001 diff --git a/textures/techage_frame4_ta3_top.png b/textures/techage_frame4_ta3_top.png new file mode 100644 index 0000000000000000000000000000000000000000..e0866fd0dfb14d05d740b2ff5c5754f98f637ec3 GIT binary patch literal 1115 zcmeAS@N?(olHy`uVBq!ia0vp^3P9Yz!3HF=(z2z16lZ})WHAE+w=f7ZGR&GI0Tg5} z@$_|Nf6OE#!YCeVyx|f91M@FW7srr_TW@Fh`agD)I9~5^(2>b{s+!uy&rZ<`)Wddf z-^PBVcm1YU>QNsJZ`})6&9!#h?vUMDTdEznIlUFk4*r-hA)~W+LHnDYIa9p(3-!+z zOQs*2mu__4SpWb1L-+1=x8J=RTUB2BYO553`|;x%OBpt-aXs`w>ilX3fwt=m&%Uo| zeYo{!*|n~1&aWR^dT9dHoek@5wZA84w_{by))0j*C5A~V2SZgC{g{@uHS1OEbB)C^Ng>OoFE{ zlrteKjp6Z{`>!91$o|^9FSEF-uX1_BpQa~uE+Y5+zOqWhA3ivtHNO7$(}%D1_cJ|s z|NT8@GsF4szAJN+i|1~>td=OSVejU%r<?gXXlMEy_?b&puz~IT| z&z~)MLFU(KEpAdjVj;xdggM#QF0pbjw^w?0Z~c#GF{+yEkm{{nwi} z?wl9NR}q(L#5^zj;ip$z2Xc;o=4(7>nIP0uwKmOfla9~)1!^oWh4s5-GHPwiwGZkX zUi|oRPSX3|Ko3-IwK>@c46N^q1<%j3JFCKOW_Gs=}*D>iZIoPJbi^610Y**=pDJddRrpE_FTbFu72 ziPg+EEx)Sny(|gvn&=z6Fis%x3m7gWaVu~4SOf_!x?OgW!Quq=brwRKO9WV6R zS9@{U#%l}8uU}UWt?m2wI#FOj>x7`L8VMFRjf#EeFUHr)y>OJ>SC{P4p?BFSC2Y-$ z=lT2pzl%?D`TqQ;o$GBwtHOtW_WU%^J~(UbwfaeEFN5+kJdKuRSO;J7nf>nb)z$aa zZ+$pD{t#4@<1rzl1FB&v>ixR^{%u8@leAUspViv`e_X?E~XO=0of){2cNE ob_z8PKN_en@ZkS*=Kl=)KTlrc>kzvdSTHbny85}Sb4q9e0Hn_uQ2+n{ literal 0 HcmV?d00001 diff --git a/textures/techage_frame4_ta4_top.png b/textures/techage_frame4_ta4_top.png new file mode 100644 index 0000000000000000000000000000000000000000..38d61be8abed9c24b9296dccb03d0df7686c0a49 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^3P9Yz!3HF=(z2z16kC$Fy9>jA5L~c#`DCC7XMsm# zF#`j)FbFd;%$g$s6l5>)^mS!_%*-jqDk8axQwJz?%G1R$q~g}w8;X2~90VLLHkwqo zUV9^1pds|G>ERre&>iy_92MRQu3&6l#jrI}=-#PWm6>in|6Ucy%zJF&-g|wk=B&b5 zkIU*^)^6`Rwcct$WSzvh-L)<;+-FbE&aMB&QNh3e1|!fOFlhL1ds