From dee6c47555f8c0456a2350e96eb6dd3662ed23bd Mon Sep 17 00:00:00 2001 From: Joachim Stolberg Date: Sat, 11 Dec 2021 20:15:55 +0100 Subject: [PATCH] Collider system added --- basic_machines/recycler.lua | 58 +-- basis/assemble.lua | 115 ++++++ basis/fly_lib.lua | 19 +- basis/laser_lib.lua | 11 +- basis/lib.lua | 60 ++- basis/oggfiles.lua | 103 +++++ collider/cooler.lua | 91 +++++ collider/detector.lua | 379 ++++++++++++++++++ collider/inlets.lua | 364 +++++++++++++++++ collider/magnet.lua | 271 +++++++++++++ collider/terminal.lua | 208 ++++++++++ collider/vacuumtube.lua | 130 ++++++ collider/worker.lua | 205 ++++++++++ energy_storage/heatexchanger1.lua | 6 +- energy_storage/heatexchanger2.lua | 83 +++- energy_storage/heatexchanger3.lua | 13 +- init.lua | 11 + items/ceramic.lua | 14 + liquids/pump.lua | 5 + logic/terminal.lua | 364 +++++++++++------ move_controller/flycontroller.lua | 57 ++- move_controller/soundblock.lua | 128 ++++++ settingtypes.txt | 6 +- sounds/techage_hum.ogg | Bin 0 -> 11397 bytes sounds/techage_watermill.ogg | Bin 23079 -> 18269 bytes ta1_watermill/watermill.lua | 2 +- textures/techage_appl_sound.png | Bin 0 -> 146 bytes textures/techage_collider_detector_appl.png | Bin 0 -> 135 bytes textures/techage_collider_detector_banner.png | Bin 0 -> 124 bytes textures/techage_collider_detector_core.png | Bin 0 -> 490 bytes textures/techage_collider_magnet.png | Bin 0 -> 168 bytes textures/techage_collider_magnet_appl.png | Bin 0 -> 153 bytes textures/techage_collider_magnet_sign.png | Bin 0 -> 138 bytes textures/techage_collider_magnet_tube.png | Bin 0 -> 123 bytes textures/techage_collider_plan.png | Bin 0 -> 585 bytes textures/techage_collider_tube.png | Bin 0 -> 223 bytes textures/techage_collider_tube_open.png | Bin 0 -> 289 bytes textures/techage_round_ceramic.png | Bin 0 -> 435 bytes 38 files changed, 2527 insertions(+), 176 deletions(-) create mode 100644 basis/oggfiles.lua create mode 100644 collider/cooler.lua create mode 100644 collider/detector.lua create mode 100644 collider/inlets.lua create mode 100644 collider/magnet.lua create mode 100644 collider/terminal.lua create mode 100644 collider/vacuumtube.lua create mode 100644 collider/worker.lua create mode 100644 move_controller/soundblock.lua create mode 100644 sounds/techage_hum.ogg create mode 100644 textures/techage_appl_sound.png create mode 100644 textures/techage_collider_detector_appl.png create mode 100644 textures/techage_collider_detector_banner.png create mode 100644 textures/techage_collider_detector_core.png create mode 100644 textures/techage_collider_magnet.png create mode 100644 textures/techage_collider_magnet_appl.png create mode 100644 textures/techage_collider_magnet_sign.png create mode 100644 textures/techage_collider_magnet_tube.png create mode 100644 textures/techage_collider_plan.png create mode 100644 textures/techage_collider_tube.png create mode 100644 textures/techage_collider_tube_open.png create mode 100644 textures/techage_round_ceramic.png diff --git a/basic_machines/recycler.lua b/basic_machines/recycler.lua index ce85b3f..f2adfc2 100644 --- a/basic_machines/recycler.lua +++ b/basic_machines/recycler.lua @@ -26,40 +26,44 @@ local CYCLE_TIME = 8 local Recipes = {} local SpecialItems = { - ["techage:sieved_gravel"] = "default:sand", - ["basic_materials:heating_element"] = "default:copper_ingot", - ["techage:ta4_wlanchip"] = "", - ["techage:basalt_cobble"] = "default:sand", - ["default:stone"] = "techage:sieved_gravel", - ["default:wood"] = "default:stick 5", - ["basic_materials:concrete_block"] = "techage:sieved_gravel", - ["dye:green"] = "", - ["dye:red"] = "", - ["dye:white"] = "", - ["dye:blue"] = "", - ["dye:brown"] = "", + ["techage:sieved_gravel"] = "default:sand", + ["basic_materials:heating_element"] = "default:copper_ingot", + ["techage:ta4_wlanchip"] = "", + ["techage:basalt_cobble"] = "default:sand", + ["default:stone"] = "techage:sieved_gravel", + ["default:wood"] = "default:stick 5", + ["basic_materials:concrete_block"] = "techage:sieved_gravel", + ["dye:green"] = "", + ["dye:red"] = "", + ["dye:white"] = "", + ["dye:blue"] = "", + ["dye:brown"] = "", ["dye:cyan"] = "", ["dye:yellow"] = "", ["dye:grey"] = "", ["dye:orange"] = "", - ["dye:black"] = "", - ["techage:basalt_glass_thin"] = "", - ["group:stone"] = "techage:sieved_gravel", - ["basic_materials:plastic_sheet"] = "", - ["group:wood"] = "default:stick 5", - ["techage:basalt_glass"] = "", - ["default:junglewood"] = "default:stick 5", - ["techage:ta4_silicon_wafer"] = "", - ["default:cobble"] = "techage:sieved_gravel", - ["default:pick_diamond"] = "default:stick", - ["techage:hammer_steel"] = "default:stick", - ["default:paper"] = "", - ["stairs:slab_basalt_glass2"] = "", - ["techage:basalt_stone"] = "techage:sieved_gravel", - ["techage:ta4_ramchip"] = "", + ["dye:black"] = "", + ["techage:basalt_glass_thin"] = "", + ["group:stone"] = "techage:sieved_gravel", + ["basic_materials:plastic_sheet"] = "", + ["group:wood"] = "default:stick 5", + ["techage:basalt_glass"] = "", + ["default:junglewood"] = "default:stick 5", + ["techage:ta4_silicon_wafer"] = "", + ["default:cobble"] = "techage:sieved_gravel", + ["default:pick_diamond"] = "default:stick", + ["techage:hammer_steel"] = "default:stick", + ["default:paper"] = "", + ["stairs:slab_basalt_glass2"] = "", + ["techage:basalt_stone"] = "techage:sieved_gravel", + ["techage:ta4_ramchip"] = "", ["protector:chest"] = "default:chest", ["techage:ta4_rotor_blade"] = "", ["techage:ta4_carbon_fiber"] = "", + ["techage:ta4_round_ceramic"] = "", + ["techage:ta4_furnace_ceramic"] = "", + ["techage:ta5_aichip"] = "", + ["techage:ta4_leds"] = "", } local function formspec(self, pos, nvm) diff --git a/basis/assemble.lua b/basis/assemble.lua index 8437c1f..b176495 100644 --- a/basis/assemble.lua +++ b/basis/assemble.lua @@ -124,3 +124,118 @@ function techage.assemble.remove(pos, AssemblyPlan, player_name) remove(pos, node.param2, AssemblyPlan, #AssemblyPlan) nvm.assemble_build = false end + +-------------------------------------------------------------------------------- +-- Assembly functions based on nodes from node inventory +-------------------------------------------------------------------------------- +local function play_sound(pos, sound) + minetest.sound_play(sound, { + pos = pos, + gain = 1, + max_hear_distance = 10, + }) +end + +local function build_inv(pos, inv, param2, AssemblyPlan, player_name, idx) + local item = AssemblyPlan[idx] + if item ~= nil then + local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4] + local pos1 = dest_pos(pos, param2, path, y) + if not minetest.is_protected(pos1, player_name) then + local node = minetest.get_node(pos1) + if techage.is_air_like(node.name) then + local stack = inv:remove_item("src", ItemStack(node_name)) + if stack:get_count() == 1 then + minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4}) + play_sound(pos, "default_place_node_hard") + local ndef = minetest.registered_nodes[node_name] + if ndef and ndef.after_place_node then + local placer = minetest.get_player_by_name(player_name) + ndef.after_place_node(pos1, placer, ItemStack(node_name)) + end + end + end + end + minetest.after(0.5, build_inv, pos, inv, param2, AssemblyPlan, player_name, idx + 1) + else + local nvm = techage.get_nvm(pos) + nvm.assemble_locked = false + end +end + +local function remove_inv(pos, inv, param2, AssemblyPlan, player_name, idx) + local item = AssemblyPlan[idx] + if item ~= nil then + local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4] + local pos1 = dest_pos(pos, param2, path, y) + if not minetest.is_protected(pos1, player_name) then + local stack = ItemStack(node_name) + if inv:room_for_item("src", stack) then + local node = minetest.get_node(pos1) + if node.name == node_name then + minetest.remove_node(pos1) + inv:add_item("src", stack) + play_sound(pos, "default_dig_cracky") + local ndef = minetest.registered_nodes[node_name] + if ndef and ndef.after_dig_node then + local digger = minetest.get_player_by_name(player_name) + ndef.after_dig_node(pos1, pos, ItemStack(node_name), {}, digger) + end + end + end + end + minetest.after(0.5, remove_inv, pos, inv, param2, AssemblyPlan, player_name, idx - 1) + else + local nvm = techage.get_nvm(pos) + nvm.assemble_locked = false + end +end + +function techage.assemble.build_inv(pos, inv, AssemblyPlan, player_name) + -- check protection + if minetest.is_protected(pos, player_name) then + return + end + local nvm = techage.get_nvm(pos) + if nvm.assemble_locked then + return + end + local node = minetest.get_node(pos) + nvm.assemble_locked = true + build_inv(pos, inv, node.param2, AssemblyPlan, player_name, 1) +end + +function techage.assemble.remove_inv(pos, inv, AssemblyPlan, player_name) + -- check protection + if minetest.is_protected(pos, player_name) then + return + end + local nvm = techage.get_nvm(pos) + if nvm.assemble_locked then + return + end + local node = minetest.get_node(pos) + nvm.assemble_locked = true + remove_inv(pos, inv, node.param2, AssemblyPlan, player_name, #AssemblyPlan) +end + +function techage.assemble.count_items(AssemblyPlan) + local t = {} + for _, item in ipairs(AssemblyPlan) do + local node_name = item[4] + local ndef = minetest.registered_nodes[node_name] + local name = ndef.description + if not t[name] then + t[name] = 1 + else + t[name] = t[name] + 1 + end + end + return t +end + +-- Determine the destination position based on the given route +-- param2, and a route table like : {0,3} +-- 0 = forward, 1 = right, 2 = backward, 3 = left +-- techage.assemble.get_pos(pos, param2, route, y_offs) +techage.assemble.get_pos = dest_pos diff --git a/basis/fly_lib.lua b/basis/fly_lib.lua index 15784d8..7ea3156 100644 --- a/basis/fly_lib.lua +++ b/basis/fly_lib.lua @@ -81,15 +81,23 @@ end function flylib.to_path(s, max_dist) local tPath + local dist = 0 for _, line in ipairs(strsplit(s)) do line = trim(line) line = string.split(line, "--", true, 1)[1] or "" if line ~= "" then local v = flylib.to_vector(line) - if v and (not max_dist or flylib.distance(v) <= max_dist) then - tPath = tPath or {} - tPath[#tPath + 1] = v + if v then + --dist = dist + flylib.distance(v) + --if not max_dist or dist <= max_dist then + tPath = tPath or {} + tPath[#tPath + 1] = v + --else + -- return tPath, S("Error: Max. length of the flight route exceeded !!") + --end + else + return tPath, S("Error: Invalid path !!") end end end @@ -614,10 +622,13 @@ end function flylib.move_to_other_pos(pos, move2to1) local meta = M(pos) local nvm = techage.get_nvm(pos) - local lpath = flylib.to_path(meta:get_string("path")) or {} + local lpath, err = flylib.to_path(meta:get_string("path")) or {} local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED local height = meta:contains("height") and meta:get_float("height") or 1 local handover + + if err then return false end + height = techage.in_range(height, 0, 1) max_speed = techage.in_range(max_speed, MIN_SPEED, MAX_SPEED) nvm.lpos1 = nvm.lpos1 or {} diff --git a/basis/laser_lib.lua b/basis/laser_lib.lua index 802b3c3..135fe61 100644 --- a/basis/laser_lib.lua +++ b/basis/laser_lib.lua @@ -42,7 +42,7 @@ local function get_positions(pos, mem, dir) return true -- no new values end --- return both both laser entities the pos and length +-- return for both laser entities the pos and length local function get_laser_length_and_pos(pos1, pos2, dir) local dist = vector.distance(pos1, pos2) @@ -133,5 +133,14 @@ function techage.renew_laser(pos, force) return res end +function techage.add_laser(pos, pos1, pos2) + local dir = vector.direction(pos1, pos2) + local param2 = minetest.dir_to_facedir(dir) + local size, pos3, pos4 = get_laser_length_and_pos(pos1, pos2, dir) + if size then + add_laser(pos, pos3, pos4, size, param2) + end +end + -- techage.del_laser(pos) techage.del_laser = del_laser diff --git a/basis/lib.lua b/basis/lib.lua index e3d2219..201d270 100644 --- a/basis/lib.lua +++ b/basis/lib.lua @@ -456,6 +456,42 @@ function techage.wrench_tooltip(x, y) "tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]" end +------------------------------------------------------------------------------- +-- Terminal history buffer +------------------------------------------------------------------------------- +local BUFFER_DEPTH = 10 + +function techage.historybuffer_add(pos, s) + local mem = techage.get_mem(pos) + mem.hisbuf = mem.hisbuf or {} + + if #s > 2 then + table.insert(mem.hisbuf, s) + if #mem.hisbuf > BUFFER_DEPTH then + table.remove(mem.hisbuf, 1) + end + mem.hisbuf_idx = #mem.hisbuf + 1 + end +end + +function techage.historybuffer_priv(pos) + local mem = techage.get_mem(pos) + mem.hisbuf = mem.hisbuf or {} + mem.hisbuf_idx = mem.hisbuf_idx or 1 + + mem.hisbuf_idx = math.max(1, mem.hisbuf_idx - 1) + return mem.hisbuf[mem.hisbuf_idx] +end + +function techage.historybuffer_next(pos) + local mem = techage.get_mem(pos) + mem.hisbuf = mem.hisbuf or {} + mem.hisbuf_idx = mem.hisbuf_idx or 1 + + mem.hisbuf_idx = math.min(#mem.hisbuf, mem.hisbuf_idx + 1) + return mem.hisbuf[mem.hisbuf_idx] +end + ------------------------------------------------------------------------------- -- Player TA5 Experience Points ------------------------------------------------------------------------------- @@ -468,12 +504,30 @@ function techage.get_expoints(player) end end -function techage.add_expoint(player) +-- Can only be used from one collider +function techage.add_expoint(player, number) if player and player.get_meta then local meta = player:get_meta() if meta then - meta:set_int("techage_ex_points", meta:get_int("techage_ex_points") + 1) - return true + if not meta:contains("techage_collider_number") then + meta:set_string("techage_collider_number", number) + end + if meta:get_string("techage_collider_number") == number then + meta:set_int("techage_ex_points", meta:get_int("techage_ex_points") + 1) + return true + else + minetest.chat_send_player(player:get_player_name(), "[techage] More than one collider is not allowed!") + return false + end + end + end +end + +function techage.on_remove_collider(player) + if player and player.get_meta then + local meta = player:get_meta() + if meta then + meta:set_string("techage_collider_number", "") end end end diff --git a/basis/oggfiles.lua b/basis/oggfiles.lua new file mode 100644 index 0000000..ffe7c82 --- /dev/null +++ b/basis/oggfiles.lua @@ -0,0 +1,103 @@ +techage.OggFileList = { + "autobahn_motor", -- ./mods/autobahn/sounds + "signs_bot_ping", -- ./mods/signs_bot/sounds + "signs_bot_go_away", -- ./mods/signs_bot/sounds + "signs_bot_step", -- ./mods/signs_bot/sounds + "signs_bot_pong", -- ./mods/signs_bot/sounds + "signs_bot_error", -- ./mods/signs_bot/sounds + "normal2", -- ./mods/hyperloop/sounds + "up2", -- ./mods/hyperloop/sounds + "down2", -- ./mods/hyperloop/sounds + "ele_norm", -- ./mods/hyperloop/sounds + "door", -- ./mods/hyperloop/sounds + "hyperloop_crowbar", -- ./mods/hyperloop/sounds + "ele_door", -- ./mods/hyperloop/sounds + "techage_watermill", -- ./mods/techage/sounds + "techage_button", -- ./mods/techage/sounds + "techage_steamengine", -- ./mods/techage/sounds + "techage_generator", -- ./mods/techage/sounds + "techage_gasflare", -- ./mods/techage/sounds + "techage_explore", -- ./mods/techage/sounds + "techage_mill", -- ./mods/techage/sounds + "techage_reactor", -- ./mods/techage/sounds + "techage_valve", -- ./mods/techage/sounds + "techage_oildrill", -- ./mods/techage/sounds + "techage_turbine", -- ./mods/techage/sounds + "techage_booster", -- ./mods/techage/sounds + "techage_quarry", -- ./mods/techage/sounds + "techage_reboiler", -- ./mods/techage/sounds + "jetpack_loop", -- ./mods/jetpack/sounds + "paperflip1", -- ./mods/unified_inventory/sounds + "teleport", -- ./mods/unified_inventory/sounds + "electricity", -- ./mods/unified_inventory/sounds + "owl", -- ./mods/unified_inventory/sounds + "click", -- ./mods/unified_inventory/sounds + "birds", -- ./mods/unified_inventory/sounds + "paperflip2", -- ./mods/unified_inventory/sounds + "dingdong", -- ./mods/unified_inventory/sounds + "trash", -- ./mods/unified_inventory/sounds + "trash_all", -- ./mods/unified_inventory/sounds + "ta4_jetpack", -- ./mods/ta4_jetpack/sounds + "ta4_jetpack_alarm", -- ./mods/ta4_jetpack/sounds + "ta4_jetpack_on", -- ./mods/ta4_jetpack/sounds + "player_damage", -- ./games/minetest_game/mods/player_api/sounds + "env_sounds_water", -- ./games/minetest_game/mods/env_sounds/sounds + "env_sounds_lava", -- ./games/minetest_game/mods/env_sounds/sounds + "doors_door_close", -- ./games/minetest_game/mods/doors/sounds + "doors_steel_door_close", -- ./games/minetest_game/mods/doors/sounds + "doors_door_open", -- ./games/minetest_game/mods/doors/sounds + "doors_fencegate_close", -- ./games/minetest_game/mods/doors/sounds + "doors_glass_door_close", -- ./games/minetest_game/mods/doors/sounds + "doors_fencegate_open", -- ./games/minetest_game/mods/doors/sounds + "doors_glass_door_open", -- ./games/minetest_game/mods/doors/sounds + "doors_steel_door_open", -- ./games/minetest_game/mods/doors/sounds + "fire_flint_and_steel", -- ./games/minetest_game/mods/fire/sounds + "fire_large", -- ./games/minetest_game/mods/fire/sounds + "fire_fire", -- ./games/minetest_game/mods/fire/sounds + "fire_extinguish_flame", -- ./games/minetest_game/mods/fire/sounds + "fire_small", -- ./games/minetest_game/mods/fire/sounds + "tnt_ignite", -- ./games/minetest_game/mods/tnt/sounds + "tnt_gunpowder_burning", -- ./games/minetest_game/mods/tnt/sounds + "tnt_explode", -- ./games/minetest_game/mods/tnt/sounds + "carts_cart_new", -- ./games/minetest_game/mods/carts/sounds + "carts_cart_moving", -- ./games/minetest_game/mods/carts/sounds + "xpanes_steel_bar_door_open", -- ./games/minetest_game/mods/xpanes/sounds + "xpanes_steel_bar_door_close", -- ./games/minetest_game/mods/xpanes/sounds + "default_break_glass", -- ./games/minetest_game/mods/default/sounds + "default_dig_dig_immediate", -- ./games/minetest_game/mods/default/sounds + "default_dig_cracky", -- ./games/minetest_game/mods/default/sounds + "default_dig_choppy", -- ./games/minetest_game/mods/default/sounds + "default_water_footstep", -- ./games/minetest_game/mods/default/sounds + "player_damage", -- ./games/minetest_game/mods/default/sounds + "default_gravel_footstep", -- ./games/minetest_game/mods/default/sounds + "default_dig_metal", -- ./games/minetest_game/mods/default/sounds + "default_gravel_dug", -- ./games/minetest_game/mods/default/sounds + "default_hard_footstep", -- ./games/minetest_game/mods/default/sounds + "default_sand_footstep", -- ./games/minetest_game/mods/default/sounds + "default_grass_footstep", -- ./games/minetest_game/mods/default/sounds + "default_chest_close", -- ./games/minetest_game/mods/default/sounds + "default_cool_lava", -- ./games/minetest_game/mods/default/sounds + "default_place_node_hard", -- ./games/minetest_game/mods/default/sounds + "default_ice_dug", -- ./games/minetest_game/mods/default/sounds + "default_dig_crumbly", -- ./games/minetest_game/mods/default/sounds + "default_tool_breaks", -- ./games/minetest_game/mods/default/sounds + "default_ice_footstep", -- ./games/minetest_game/mods/default/sounds + "default_dig_cracky", -- ./games/minetest_game/mods/default/sounds + "default_chest_open", -- ./games/minetest_game/mods/default/sounds + "default_gravel_dig", -- ./games/minetest_game/mods/default/sounds + "default_dig_oddly_breakable_by_hand", -- ./games/minetest_game/mods/default/sounds + "default_dug_metal", -- ./games/minetest_game/mods/default/sounds + "default_dirt_footstep", -- ./games/minetest_game/mods/default/sounds + "default_dig_choppy", -- ./games/minetest_game/mods/default/sounds + "default_glass_footstep", -- ./games/minetest_game/mods/default/sounds + "default_snow_footstep", -- ./games/minetest_game/mods/default/sounds + "default_place_node", -- ./games/minetest_game/mods/default/sounds + "default_dig_snappy", -- ./games/minetest_game/mods/default/sounds + "default_dug_node", -- ./games/minetest_game/mods/default/sounds + "default_metal_footstep", -- ./games/minetest_game/mods/default/sounds + "default_ice_dig", -- ./games/minetest_game/mods/default/sounds + "default_place_node_metal", -- ./games/minetest_game/mods/default/sounds + "default_wood_footstep", -- ./games/minetest_game/mods/default/sounds + "default_furnace_active", -- ./games/minetest_game/mods/default/sounds + "default_item_smoke", -- ./games/minetest_game/mods/default/sounds +} diff --git a/collider/cooler.lua b/collider/cooler.lua new file mode 100644 index 0000000..4161b08 --- /dev/null +++ b/collider/cooler.lua @@ -0,0 +1,91 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Cooler as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local Pipe = techage.LiquidPipe + +minetest.register_node("techage:ta4_collider_cooler", { + description = S("TA4 Collider Cooler"), + tiles = { + -- up, down, right, left, back, front + { + image = "techage_appl_cooler4.png^techage_frame4_ta4_top.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + { + image = "techage_appl_cooler4.png^techage_frame4_ta4_top.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 0.4, + }, + }, + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_hole_pipe.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_hole_pipe.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_cooler.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_cooler.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + networks = { + pipe2 = {}, + }, + + after_place_node = function(pos, placer, itemstack) + Pipe:after_place_node(pos) + end, + + after_dig_node = function(pos, oldnode) + Pipe:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +Pipe:add_secondary_node_names({"techage:ta4_collider_cooler"}) +Pipe:set_valid_sides("techage:ta4_collider_cooler", {"R", "L"}) + +techage.register_node({"techage:ta4_collider_cooler"}, { + on_transfer = function(pos, in_dir, topic, payload) + if topic == "cooler" then + return true + else + return false + end + end, +}) + +minetest.register_craft({ + output = "techage:ta4_collider_cooler", + recipe = { + {'', 'dye:blue', ''}, + {'', 'techage:cooler', ''}, + {'', 'techage:aluminum', ''}, + }, +}) + diff --git a/collider/detector.lua b/collider/detector.lua new file mode 100644 index 0000000..7470858 --- /dev/null +++ b/collider/detector.lua @@ -0,0 +1,379 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Detector as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S +local S2P = minetest.string_to_pos +local P2S = minetest.pos_to_string +local getpos = techage.assemble.get_pos + +local CYCLE_TIME = 2 +local TNO_MAGNETS = 22 +local PROBABILITY = 90 -- every 30 min. + +local function tube_damage_check(pos, node, meta, nvm) + local resp = techage.tube_inlet_command(pos, "check") + if resp then + return true + end + return false, "Tube defect" +end + +local Schedule = {[0] = + -- Route: 0 = forward, 1 = right, 2 = backward, 3 = left + -- Gas left/right + {name = "techage:ta4_collider_pipe_inlet", yoffs = 1, route = {3,3,3,2}, check = techage.gas_inlet_check}, + {name = "techage:ta4_collider_pipe_inlet", yoffs = 1, route = {1,1,1,2}, check = techage.gas_inlet_check}, + -- Power left/right + {name = "techage:ta4_collider_cable_inlet", yoffs = 2, route = {3,3,3}, check = techage.power_inlet_check}, + {name = "techage:ta4_collider_cable_inlet", yoffs = 2, route = {1,1,1}, check = techage.power_inlet_check}, + -- Tube left/right + --{name = "techage:ta4_collider_tube_inlet", yoffs = 1, route = {3,3,3}, check = techage.tube_inlet_check, no_vacuum=true}, + --{name = "techage:ta4_collider_tube_inlet", yoffs = 1, route = {1,1,1}, check = techage.tube_inlet_check, no_vacuum=true}, + -- Cooler + {name = "techage:ta4_collider_pipe_inlet", yoffs = 0, route = {0}, check = techage.cooler_check}, + {name = "techage:ta4_collider_pipe_inlet", yoffs = 2, route = {0}, check = techage.cooler_check}, + -- Air outlet + {name = "techage:ta4_collider_pipe_outlet", yoffs = 2, route = {}, check = techage.air_outlet_check}, +} + +local TIME_SLOTS = #Schedule + 2 + +local function play_sound(pos) + minetest.sound_play("techage_hum", { + pos = pos, + gain = 0.5, + max_hear_distance = 10, + }) +end + +local function terminal_message(pos, msg) + local term_num = M(pos):contains("term_num") and M(pos):get_string("term_num") + local own_num = M(pos):get_string("node_number") + + if term_num and own_num then + techage.send_single(own_num, term_num, "text", msg) + end +end + +local function experience_points(pos) + if math.random(PROBABILITY) == 1 then + local owner = M(pos):get_string("owner") + local own_num = M(pos):get_string("node_number") + local player = minetest.get_player_by_name(owner) + if player then + if techage.add_expoint(player, own_num) then + terminal_message(pos, "Experience point reached!") + end + end + end +end + +local function check_state(pos) + -- Cyclically check all connections + local param2 = minetest.get_node(pos).param2 + local nvm = techage.get_nvm(pos) + nvm.ticks = (nvm.ticks or 0) + 1 + local idx = nvm.ticks % TIME_SLOTS + local item = Schedule[idx] + + if idx == 1 then + nvm.result = true + end + + if item then + local pos2 = getpos(pos, param2, item.route, item.yoffs) + local nvm2 = techage.get_nvm(pos2) + local meta2 = M(pos2) + local node2 = minetest.get_node(pos2) + if item.name == node2.name then + local res, err = item.check(pos2, node2, meta2, nvm2) + --print("check_state", idx, res, err) + if not res then + nvm.result = false + nvm.runnning = false + terminal_message(pos, (err or "unknown") .. "!!!") + return nvm.result + end + else + nvm.result = false + nvm.runnning = false + terminal_message(pos, "Detector defect!!!") + end + elseif idx == #Schedule + 1 then + return nvm.result + end +end + +local function add_laser(pos) + local param2 = minetest.get_node(pos).param2 + local pos1 = getpos(pos, param2, {3,3}, 1) + local pos2 = getpos(pos, param2, {1,1,1}, 1) + techage.del_laser(pos) + techage.add_laser(pos, pos1, pos2) +end + +local function create_task(pos, task) + local mem = techage.get_mem(pos) + if not mem.co then + mem.co = coroutine.create(task) + end + + local _, err = coroutine.resume(mem.co, pos) + if err then + mem.co = nil + --print(err) + return + end + minetest.after(0.4, create_task, pos) +end + +-- Call on_cyclic_check of all magents so that the magnets don't need a FLB. +local function magnet_on_cyclic_check(pos, nvm) + local ndef = minetest.registered_nodes["techage:ta4_magnet"] + for idx,pos2 in ipairs(nvm.magnet_positions or {}) do + local res = ndef.on_cyclic_check(pos2) + if res == -2 then + terminal_message(pos, "Magnet #" .. idx .. " defect!!!") + return false + elseif res == -1 then + terminal_message(pos, "Vacuum defect!!!") + techage.air_outlet_reset({x=pos.x, y=pos.y + 2, z=pos.z}) + return false + end + end + return true +end + +minetest.register_node("techage:ta4_detector_core", { + description = S("TA4 Collider Detector Core"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png^techage_collider_detector_core.png", + "default_steel_block.png^techage_collider_detector_core.png", + "default_steel_block.png^techage_collider_detector_core.png", + "default_steel_block.png^techage_collider_detector_core.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + after_place_node = function(pos, placer, itemstack) + local nvm = techage.get_nvm(pos) + local meta = M(pos) + local own_num = techage.add_node(pos, "techage:ta4_detector_core") + meta:set_string("node_number", own_num) + meta:set_string("owner", placer:get_player_name()) + M({x=pos.x, y=pos.y - 1, z=pos.z}):set_string("infotext", S("TA4 Collider Detector " .. own_num)) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, + + on_timer = function(pos, elapsed) + local nvm = techage.get_nvm(pos) + if not magnet_on_cyclic_check(pos, nvm) then + techage.del_laser(pos) + if nvm.running then + terminal_message(pos, "Detector stopped.") + nvm.running = false + end + nvm.magnet_positions = nil + elseif nvm.running then + local res = check_state(pos) + if res == true then + experience_points(pos) + add_laser(pos) + if nvm.ticks <= TIME_SLOTS then -- only once + terminal_message(pos, "Detector running.") + end + elseif res == false then + techage.del_laser(pos) + nvm.running = false + nvm.magnet_positions = nil + terminal_message(pos, "Detector stopped.") + end + if nvm.running then + play_sound(pos) + end + end + return true + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + techage.on_remove_collider(digger) + techage.remove_node(pos, oldnode, oldmetadata) + techage.del_mem(pos) + end, +}) + +local function check_expr(own_num, term_num, text, expr) + techage.send_single(own_num, term_num, "text", text .. "..." .. (expr and "ok" or "error!!!")) + return expr +end + +local function start_task(pos) + local term_num = M(pos):contains("term_num") and M(pos):get_string("term_num") + local param2 = minetest.get_node(pos).param2 + local pos2 = getpos(pos, param2, {3,3,3}, 1) + local own_num = M(pos):get_string("node_number") + local nvm = techage.get_nvm(pos) + nvm.magnet_positions = {} + + if term_num and param2 and pos2 then + techage.send_single(own_num, term_num, "text", "#### Start ####") + + coroutine.yield() + local resp = techage.tube_inlet_command(pos2, "enumerate", 1) + if not check_expr(own_num, term_num, "- Check number of magnets", resp == TNO_MAGNETS) then + return + end + + coroutine.yield() + techage.send_single(own_num, term_num, "text", "- Check position of magnets...") + resp = techage.tube_inlet_command(pos2, "distance") + if resp ~= true then + techage.send_single(own_num, term_num, "append", "#" .. resp .. " defect!!!") + return + end + techage.send_single(own_num, term_num, "append", "ok") + + coroutine.yield() + techage.send_single(own_num, term_num, "text", "- Start magnets...") + for num = 1, TNO_MAGNETS do + local resp = techage.tube_inlet_command(pos2, "pos", num) + if not resp or type(resp) ~= "table" then + techage.send_single(own_num, term_num, "append", "#" .. num .. " defect!!!") + nvm.magnet_positions = nil + return + else + nvm.magnet_positions[#nvm.magnet_positions + 1] = resp + end + coroutine.yield() + end + techage.send_single(own_num, term_num, "append", "ok") + + coroutine.yield() + techage.send_single(own_num, term_num, "text", "- Check magnets...") + -- The check will be performed by the timer, so wait 5 sec. + for i = 1,14 do + coroutine.yield() + end + if nvm.magnet_positions then + techage.send_single(own_num, term_num, "append", "ok") + else + return + end + + coroutine.yield() + techage.send_single(own_num, term_num, "text", "- Check detector...") + for _,item in ipairs(Schedule)do + local pos2 = getpos(pos, param2, item.route, item.yoffs) + local nvm2 = techage.get_nvm(pos2) + local meta2 = M(pos2) + local node2 = minetest.get_node(pos2) + if item.name == node2.name then + local res, err = item.check(pos2, node2, meta2, nvm2) + if not res then + techage.send_single(own_num, term_num, "append", err .. "!!!") + nvm.magnet_positions = nil + return + end + else + techage.send_single(own_num, term_num, "append", "defect!!!") + nvm.magnet_positions = nil + return + end + coroutine.yield() + end + techage.send_single(own_num, term_num, "append", "ok") + + coroutine.yield() + techage.send_single(own_num, term_num, "text", "Ready.") + nvm.ticks = 0 + nvm.running = true + end +end + +local function test_magnet(pos, payload) + local term_num = M(pos):contains("term_num") and M(pos):get_string("term_num") + local param2 = minetest.get_node(pos).param2 + local pos2 = getpos(pos, param2, {3,3,3}, 1) + local own_num = M(pos):get_string("node_number") + local magnet_num = tonumber(payload) + local res, err = techage.tube_inlet_command(pos2, "test", magnet_num) + if res then + techage.send_single(own_num, term_num, "text", "magnet #" .. magnet_num .. ": ok") + else + techage.send_single(own_num, term_num, "text", "magnet #" .. magnet_num .. ": " .. err .. "!!!") + end +end + +techage.register_node({"techage:ta4_detector_core"}, { + on_recv_message = function(pos, src, topic, payload) + local nvm = techage.get_nvm(pos) + if topic == "connect" then + M(pos):set_string("term_num", src) + return true + elseif topic == "start" then + create_task(pos, start_task) + return true + elseif topic == "stop" then + nvm.running = false + techage.del_laser(pos) + return "Detector stopped." + elseif topic == "status" then + if nvm.running == true then + return "running" + elseif nvm.result == false then + return "fault" + else + return "stopped" + end + elseif topic == "test"then + if payload and tonumber(payload) then + test_magnet(pos, payload) + return true + else + return "Invalid magnet number" + end + elseif topic == "points" then + local owner = M(pos):get_string("owner") + local player = minetest.get_player_by_name(owner) + if player then + local points = techage.get_expoints(player) + return "Ex. Points = " .. points + end + else + return "unsupported" + end + end, + on_node_load = function(pos) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, +}) + + +minetest.register_craft({ + output = "techage:ta4_detector_core", + recipe = { + {'techage:aluminum', 'basic_materials:heating_element', 'default:steel_ingot'}, + {'default:diamond', 'techage:ta4_wlanchip', 'techage:electric_cableS'}, + {'default:steel_ingot', '', 'techage:aluminum'}, + }, +}) + diff --git a/collider/inlets.lua b/collider/inlets.lua new file mode 100644 index 0000000..baf7c35 --- /dev/null +++ b/collider/inlets.lua @@ -0,0 +1,364 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Tube/Pipe Inlets as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local PWR_NEEDED = 15 +local CYCLE_TIME = 2 +local GAS_CAPA = 20 +local AIR_CAPA = 1000 + +local VTube = techage.VTube +local Pipe = techage.LiquidPipe +local Cable = techage.ElectricCable +local power = networks.power +local liquid = networks.liquid + +-------------------------------------------------------------------------------- +-- Tube Inlet +-------------------------------------------------------------------------------- +minetest.register_node("techage:ta4_collider_tube_inlet", { + description = S("TA4 Collider Tube Inlet"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png^techage_collider_tube_open.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-4/8, -4/8, -4/8, -1/8, 4/8, 4/8}, + { 1/8, -4/8, -4/8, 4/8, 4/8, 4/8}, + {-4/8, 1/8, -4/8, 4/8, 4/8, 4/8}, + {-4/8, -4/8, -4/8, 4/8, -1/8, 4/8}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-4/8, -4/8, -4/8, 4/8, 4/8, 4/8}, + }, + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + after_place_node = function(pos, placer, itemstack) + VTube:after_place_node(pos) + end, + + after_dig_node = function(pos, oldnode) + VTube:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +VTube:add_secondary_node_names({"techage:ta4_collider_tube_inlet"}) +VTube:set_valid_sides("techage:ta4_collider_tube_inlet", {"F"}) + +-- Called from the detector via tube ring +techage.register_node({"techage:ta4_collider_tube_inlet"}, { + on_transfer = function(pos, in_dir, topic, payload) + if topic == "distance" then + return pos + elseif topic == "enumerate" and payload then + return payload - 1 + elseif topic == "check" then + local nvm = techage.get_nvm(pos) + nvm.check_received = true + return true + end + end, +}) + +-- Used by the detector to check the tube connection +function techage.tube_inlet_command(pos, command, payload) + if command == "distance" then + local pos2 = techage.transfer(pos, "F", command, payload, VTube, {"techage:ta4_magnet"}) + if type(pos2) == "table" then + local dist = math.abs(pos.x - pos2.x) + math.abs(pos.z - pos2.z) + if pos.y == pos2.y and dist == VTube.max_tube_length + 1 then + return true + end + return 0 + else + return pos2 + end + end + return techage.transfer(pos, "F", command, payload, VTube, {"techage:ta4_magnet"}) +end + +minetest.register_craft({ + output = "techage:ta4_collider_tube_inlet", + recipe = { + {'', '', ''}, + {'techage:ta4_vtubeS', 'default:steelblock', ''}, + {'', '', ''}, + }, +}) + +-------------------------------------------------------------------------------- +-- Pipe Inlet (gas) +-------------------------------------------------------------------------------- +minetest.register_node("techage:ta4_collider_pipe_inlet", { + description = S("TA4 Collider Pipe Inlet"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png^techage_appl_hole_pipe.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + networks = { + pipe2 = {}, + }, + + after_place_node = function(pos, placer, itemstack) + local nvm = techage.get_nvm(pos) + Pipe:after_place_node(pos) + nvm.liquid = {} + end, + + after_dig_node = function(pos, oldnode) + Pipe:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +liquid.register_nodes({"techage:ta4_collider_pipe_inlet"}, Pipe, "tank", {"F"}, { + capa = GAS_CAPA, + peek = function(pos, indir) + local nvm = techage.get_nvm(pos) + return liquid.srv_peek(nvm) + end, + put = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_put(nvm, name, amount, GAS_CAPA) + end, + take = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_take(nvm, name, amount) + end, + untake = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + liquid.srv_put(nvm, name, amount, GAS_CAPA) + end, +}) + +techage.register_node({"techage:ta4_collider_pipe_inlet"}, { + on_transfer = function(pos, in_dir, topic, payload) + -- called from heatexchanger + if topic == "detector" then + local nvm = techage.get_nvm(pos) + nvm.detector_received = true + return true + end + end, +}) + +-- Used by the detector to check for gas pressure +function techage.gas_inlet_check(pos, node, meta, nvm) + nvm.liquid = nvm.liquid or {} + if nvm.liquid.amount == GAS_CAPA and nvm.liquid.name == "techage:hydrogen" then + return true + end + return false, "no gas" +end + +-- Used by the detector to check for cooler connection +function techage.cooler_check(pos, node, meta, nvm) + if nvm.detector_received then + nvm.detector_received = nil + return true + end + return false, "Cooler defect" +end + +minetest.register_craft({ + output = "techage:ta4_collider_pipe_inlet", + recipe = { + {'', '', ''}, + {'techage:ta3_pipeS', 'default:steelblock', ''}, + {'', '', ''}, + }, +}) + + +-------------------------------------------------------------------------------- +-- Pipe Outlet (air) +-------------------------------------------------------------------------------- +local function init_air(nvm) + nvm.liquid = { + amount = AIR_CAPA, + name = "air", + } + return nvm.liquid +end + +minetest.register_node("techage:ta4_collider_pipe_outlet", { + description = S("TA4 Collider Pipe Outlet"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png^techage_appl_hole_pipe.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + networks = { + pipe2 = {}, + }, + + after_place_node = function(pos, placer, itemstack) + local nvm = techage.get_nvm(pos) + init_air(nvm) + Pipe:after_place_node(pos) + end, + + after_dig_node = function(pos, oldnode) + Pipe:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +liquid.register_nodes({"techage:ta4_collider_pipe_outlet"}, Pipe, "tank", {"U"}, { + capa = AIR_CAPA, + peek = function(pos, indir) + local nvm = techage.get_nvm(pos) + return liquid.srv_peek(nvm) + end, + put = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_put(nvm, name, amount, AIR_CAPA) + end, + take = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_take(nvm, name, amount) + end, + untake = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + liquid.srv_put(nvm, name, amount, AIR_CAPA) + end, +}) + +-- Used by the detector to check the vacuum +function techage.air_outlet_check(pos, node, meta, nvm) + nvm.liquid = nvm.liquid or {} + if nvm.liquid.amount == 0 then + return true + end + return false, "no vacuum" +end + +function techage.air_outlet_reset(pos) + local nvm = techage.get_nvm(pos) + init_air(nvm) +end + +minetest.register_craft({ + output = "techage:ta4_collider_pipe_outlet", + recipe = { + {'', 'techage:ta3_pipeS', ''}, + {'', 'default:steelblock', ''}, + {'', '', ''}, + }, +}) + +-------------------------------------------------------------------------------- +-- Cable Inlet (power) +-------------------------------------------------------------------------------- +minetest.register_node("techage:ta4_collider_cable_inlet", { + description = S("TA4 Collider Cable Inlet"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png^techage_appl_hole_electric.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + networks = { + pipe2 = {}, + }, + + after_place_node = function(pos, placer, itemstack) + Cable:after_place_node(pos) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, + + on_timer = function(pos, elapsed) + local nvm = techage.get_nvm(pos) + nvm.consumed = power.consume_power(pos, Cable, nil, PWR_NEEDED) + return true + end, + + after_dig_node = function(pos, oldnode) + Cable:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +-- Used by the detector to check for power +function techage.power_inlet_check(pos, node, meta, nvm) + if nvm.consumed == PWR_NEEDED then + return true + end + return false, "no power" +end + +power.register_nodes({"techage:ta4_collider_cable_inlet"}, Cable, "con", {"F"}) + +techage.register_node({"techage:ta4_collider_cable_inlet"}, { + on_node_load = function(pos) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, +}) + +minetest.register_craft({ + output = "techage:ta4_collider_cable_inlet", + recipe = { + {'', '', ''}, + {'techage:electric_cableS', 'default:steelblock', ''}, + {'', '', ''}, + }, +}) diff --git a/collider/magnet.lua b/collider/magnet.lua new file mode 100644 index 0000000..c84c95c --- /dev/null +++ b/collider/magnet.lua @@ -0,0 +1,271 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Magnet as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S +local S2P = minetest.string_to_pos +local P2S = minetest.pos_to_string + +local PWR_NEEDED = 5 +local CYCLE_TIME = 2 +local CAPACITY = 10 + +local Cable = techage.ElectricCable +local Pipe = techage.LiquidPipe +local VTube = techage.VTube +local power = networks.power +local liquid = networks.liquid + +minetest.register_node("techage:ta4_detector_magnet", { + description = S("TA4 Collider Detector Magnet"), + tiles = { + -- up, down, right, left, back, front + "techage_collider_magnet.png^techage_collider_magnet_appl.png", + "techage_collider_magnet.png^techage_collider_magnet_appl.png", + "techage_collider_magnet.png", + "techage_collider_magnet.png", + "techage_collider_magnet.png^techage_collider_magnet_appl.png", + "techage_collider_magnet.png^techage_collider_magnet_appl.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_node("techage:ta4_magnet", { + description = S("TA4 Collider Magnet"), + inventory_image = minetest.inventorycube( + "techage_collider_magnet.png^techage_appl_hole_electric.png", + "techage_collider_magnet.png^techage_appl_hole_pipe.png", + "techage_collider_magnet.png^techage_collider_magnet_tube.png"), + tiles = { + -- up, down, right, left, back, front + "techage_collider_magnet.png^techage_appl_hole_electric.png", + "techage_collider_magnet.png", + "techage_collider_magnet.png^techage_collider_magnet_tube.png", + "techage_collider_magnet.png^techage_collider_magnet_tube.png", + "techage_collider_magnet.png^techage_collider_magnet_appl.png^techage_appl_hole_pipe.png^techage_collider_magnet_sign.png", + "techage_collider_magnet.png^techage_collider_magnet_appl.png^techage_appl_hole_pipe.png^techage_collider_magnet_sign.png", + }, + drawtype = "nodebox", + use_texture_alpha = techage.CLIP, + node_box = { + type = "fixed", + fixed = { + {-11/16, -11/16, -11/16, 11/16, 11/16, -2/16}, + {-11/16, -11/16, 2/16, 11/16, 11/16, 11/16}, + {-11/16, 2/16, -11/16, 11/16, 11/16, 11/16}, + {-11/16, -11/16, -11/16, 11/16, -2/16, 11/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-4/8, -4/8, -4/8, 4/8, 4/8, 4/8}, + }, + collision_box = { + type = "fixed", + fixed = {-11/16, -11/16, -11/16, 11/16, 11/16, 11/16}, + }, + wield_scale = {x = 0.8, y = 0.8, z = 0.8}, + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + after_place_node = function(pos, placer, itemstack) + if pos.y > techage.collider_min_depth then + minetest.remove_node(pos) + minetest.add_item(pos, ItemStack("techage:ta4_magnet")) + return + end + local nvm = techage.get_nvm(pos) + nvm.liquid = {} + Pipe:after_place_node(pos) + Cable:after_place_node(pos) + VTube:after_place_node(pos) + M(pos):set_string("infotext", S("TA4 Collider Magnet") .. " #0") + end, + + -- To be called by the detector + on_cyclic_check = function(pos) + local nvm = techage.get_nvm(pos) + nvm.liquid = nvm.liquid or {} + nvm.consumed = power.consume_power(pos, Cable, 6, PWR_NEEDED) + if nvm.tube_damage then + nvm.tube_damage = nil + return -1 + elseif nvm.liquid.amount == CAPACITY and + nvm.liquid.name == "techage:hydrogen" and + nvm.consumed == PWR_NEEDED then + return 0 + end + return -2 + end, + + tubelib2_on_update2 = function(pos, outdir, tlib2, node) + if tlib2.tube_type == "vtube" then + local nvm = techage.get_nvm(pos) + nvm.tube_damage = true + elseif tlib2.tube_type == "pipe2" then + local nvm = techage.get_nvm(pos) + nvm.liquid = nvm.liquid or {} + nvm.liquid.amount = 0 + end + end, + + after_dig_node = function(pos, oldnode) + Pipe:after_dig_node(pos) + Cable:after_dig_node(pos) + VTube:after_dig_node(pos) + techage.del_mem(pos) + end, +}) + +power.register_nodes({"techage:ta4_magnet"}, Cable, "con", {"U"}) +liquid.register_nodes({"techage:ta4_magnet"}, Pipe, "tank", {"F", "B"}, { + capa = CAPACITY, + peek = function(pos, indir) + local nvm = techage.get_nvm(pos) + return liquid.srv_peek(nvm) + end, + put = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_put(nvm, name, amount, CAPACITY) + end, + take = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + return liquid.srv_take(nvm, name, amount) + end, + untake = function(pos, indir, name, amount) + local nvm = techage.get_nvm(pos) + liquid.srv_put(nvm, name, amount, CAPACITY) + end, +}) + +VTube:add_secondary_node_names({"techage:ta4_magnet"}) +VTube:set_valid_sides("techage:ta4_magnet", {"R", "L"}) + +local function send_to_next(pos, in_dir, topic, payload) + return techage.transfer(pos, in_dir, topic, payload, VTube, + {"techage:ta4_magnet", "techage:ta4_collider_tube_inlet"}) +end + +--[[ +Commands +-------- + +distance : Check distance between all magnets. + Returns pos of next magnet or the number of the defect magnet. +enumerate : Give each magnet a unique number (1...n) +pos : Read the position +test : Test all magnet attributs. + Returns true or false, err +]]-- +techage.register_node({"techage:ta4_magnet"}, { + on_transfer = function(pos, in_dir, topic, payload) + local nvm = techage.get_nvm(pos) + if topic == "distance" then + local pos2 = send_to_next(pos, in_dir, topic, payload) + if type(pos2) == "table" then + local dist = math.abs(pos.x - pos2.x) + math.abs(pos.z - pos2.z) + if pos.y == pos2.y and dist == VTube.max_tube_length + 1 then + return pos + end + return nvm.number or 0 + else + return pos2 + end + elseif topic == "enumerate" and payload then + payload = tonumber(payload) or 1 + nvm.number = payload + M(pos):set_string("infotext", S("TA4 Collider Magnet") .. " #" .. payload) + return send_to_next(pos, in_dir, topic, payload + 1) + elseif topic == "pos" then + if payload and tonumber(payload) == nvm.number then + return pos + else + return send_to_next(pos, in_dir, topic, payload) + end + elseif topic == "test" then + if payload and tonumber(payload) == nvm.number then + if not nvm.liquid or nvm.liquid.amount < CAPACITY then + return false, "no gas" + elseif nvm.liquid.name ~= "techage:hydrogen" then + return false, "wrong gas" + elseif nvm.consumed ~= PWR_NEEDED then + return false, "no power" + elseif nvm.tube_damage then + nvm.tube_damage = nil + return false, "no vacuum" + end + return true + else + return send_to_next(pos, in_dir, topic, payload) + end + end + end, +}) + +minetest.register_node("techage:ta4_magnet_base", { + description = S("TA4 Collider Magnet Base"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-6/16, -8/16, -6/16, 6/16, 5/16, 6/16}, + }, + }, + paramtype2 = "facedir", + groups = {cracky = 1}, + is_ground_content = false, + paramtype = "light", + use_texture_alpha = techage.CLIP, + sounds = default.node_sound_metal_defaults(), +}) + + +minetest.register_craft({ + output = "techage:ta4_detector_magnet 2", + recipe = { + {'default:steel_ingot', '', 'techage:aluminum'}, + {'dye:red', 'basic_materials:gold_wire', 'dye:brown'}, + {'techage:aluminum', '', 'default:steel_ingot'}, + }, +}) + +minetest.register_craft({ + output = "techage:ta4_magnet", + recipe = { + {'techage:ta3_pipeS', '', 'techage:electric_cableS'}, + {'techage:ta4_round_ceramic', 'techage:ta4_detector_magnet', 'techage:ta4_round_ceramic'}, + {'', '', ''}, + }, +}) + +minetest.register_craft({ + output = "techage:ta4_magnet_base 4", + recipe = { + {'techage:aluminum', 'default:steel_ingot', ''}, + {'techage:aluminum', 'default:steel_ingot', ''}, + {'techage:aluminum', 'default:steel_ingot', ''}, + }, +}) diff --git a/collider/terminal.lua b/collider/terminal.lua new file mode 100644 index 0000000..3d1c7ea --- /dev/null +++ b/collider/terminal.lua @@ -0,0 +1,208 @@ +--[[ + + Techage + ======= + + Copyright (C) 2020-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Terminal + +]]-- + +local M = minetest.get_meta +local S = techage.S + +local STR_LEN = 80 +local HELP = [[#### TA4 Terminal #### + +Send commands to the connected machine +and output text messages from the +machine. + +Commands can have up to 80 characters. +Local commands: +- clear = clear screen +- help = this message +- pub = switch to public use +- priv = switch to private use +- connect = connect the machine + +All other commands are machine dependent. +]] + +local function get_string(meta, num, default) + local s = meta:get_string("bttn_text"..num) + if not s or s == "" then + return default + end + return s +end + +local function formspec2(mem) + mem.command = mem.command or "" + mem.output = mem.output or "" + local output = minetest.formspec_escape(mem.output) + output = output:gsub("\n", ",") + local command = minetest.formspec_escape(mem.command) + local bttn_text1 = get_string(meta, 1, "User1") + local bttn_text2 = get_string(meta, 2, "User2") + local bttn_text3 = get_string(meta, 3, "User3") + local bttn_text4 = get_string(meta, 4, "User4") + local bttn_text5 = get_string(meta, 5, "User5") + local bttn_text6 = get_string(meta, 6, "User6") + local bttn_text7 = get_string(meta, 7, "User7") + local bttn_text8 = get_string(meta, 8, "User8") + local bttn_text9 = get_string(meta, 9, "User9") + return "size[10,8]".. + "style_type[table,field;font=mono]".. + "button[0,0;3.3,1;bttn1;"..bttn_text1.."]button[3.3,0;3.3,1;bttn2;"..bttn_text2.."]button[6.6,0;3.3,1;bttn3;"..bttn_text3.."]".. + "button[0,0.8;3.3,1;bttn4;"..bttn_text4.."]button[3.3,0.8;3.3,1;bttn5;"..bttn_text5.."]button[6.6,0.8;3.3,1;bttn6;"..bttn_text6.."]".. + "button[0,1.6;3.3,1;bttn7;"..bttn_text7.."]button[3.3,1.6;3.3,1;bttn8;"..bttn_text8.."]button[6.6,1.6;3.3,1;bttn9;"..bttn_text9.."]".. + "table[0,2.5;9.8,4.7;output;"..output..";200]".. + "field[0.4,7.7;7.6,1;cmnd;;"..mem.command.."]" .. + "field_close_on_enter[cmnd;false]".. + "button[7.9,7.4;2,1;enter;"..S("Enter").."]" +end + +local function output(pos, text) + local mem = techage.get_mem(pos) + mem.output = mem.output .. "\n" .. (text or "") + mem.output = mem.output:sub(-500,-1) + M(pos):set_string("formspec", formspec2(mem)) +end + +local function command(pos, mem, player) + local meta = minetest.get_meta(pos) + local owner = meta:get_string("owner") + + if mem.command == "clear" then + mem.output = "" + mem.command = "" + meta:set_string("formspec", formspec2(mem)) + elseif mem.command == "" then + output(pos, ">") + mem.command = "" + meta:set_string("formspec", formspec2(mem)) + elseif mem.command == "help" then + local meta = minetest.get_meta(pos) + mem.output = HELP + mem.command = "" + meta:set_string("formspec", formspec2(mem)) + elseif mem.command == "pub" and owner == player then + meta:set_int("public", 1) + output(pos, "> "..mem.command) + mem.command = "" + output(pos, "Switched to public use!") + elseif mem.command == "priv" and owner == player then + meta:set_int("public", 0) + output(pos, "> "..mem.command) + mem.command = "" + output(pos, "Switched to private use!") + elseif meta:get_int("public") == 1 or owner == player then + if mem.command == "clear" then + mem.output = + mem.command = "" + meta:set_string("formspec", formspec2(mem)) + end + end +end + +minetest.register_node("techage:ta4_terminal", { + description = "TA4 Collider Terminal", + tiles = { + -- up, down, right, left, back, front + 'techage_terminal1_top.png', + 'techage_terminal1_bottom.png', + 'techage_terminal1_side.png', + 'techage_terminal1_side.png', + 'techage_terminal1_bottom.png', + "techage_terminal1_front.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-12/32, -16/32, -8/32, 12/32, -14/32, 12/32}, + {-12/32, -14/32, 12/32, 12/32, 6/32, 14/32}, + }, + }, + selection_box = { + type = "fixed", + fixed = { + {-12/32, -16/32, -8/32, 12/32, -14/32, 12/32}, + {-12/32, -14/32, 12/32, 12/32, 6/32, 14/32}, + }, + }, + + after_place_node = function(pos, placer) + local number = techage.add_node(pos, minetest.get_node(pos).name) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec1()) + meta:set_string("owner", placer:get_player_name()) + meta:set_string("infotext", S("TA4 Collider Terminal") .. ": " .. S("not connected") + end, + + on_receive_fields = function(pos, formname, fields, player) + local meta = minetest.get_meta(pos) + local mem = techage.get_mem(pos) + if fields.number and fields.number ~= "" then + local owner = meta:get_string("owner") + if techage.check_numbers(fields.number, owner) then + local own_number = meta:get_string("own_number") + if techage.send_single(own_number, fields.number, "connect") == true then + meta:set_string("number", fields.number) + meta:set_string("infotext", S("TA4 Collider Terminal") .. ": " .. S("connected with") .. " " .. fields.number) + meta:set_string("formspec", formspec2(mem)) + end + end + elseif (fields.enter or fields.key_enter_field) and fields.cmnd then + mem.command = string.sub(fields.cmnd, 1, STR_LEN) + command(pos, mem, player:get_player_name()) + elseif fields.key_up then + mem.command = pdp13.historybuffer_priv(pos) + meta:set_string("formspec", formspec2(mem)) + elseif fields.key_down then + mem.command = pdp13.historybuffer_next(pos) + meta:set_string("formspec", formspec2(mem)) + end + end, + + after_dig_node = function(pos, oldnode, oldmetadata) + techage.remove_node(pos, oldnode, oldmetadata) + end, + + paramtype = "light", + use_texture_alpha = techage.CLIP, + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_craft({ + output = "techage:ta4_terminal", + recipe = { + {"", "techage:ta4_display", ""}, + {"dye:black", "techage:ta4_wlanchip", "default:copper_ingot"}, + {"", "techage:aluminum", ""}, + }, +}) + +techage.register_node({"techage:ta4_terminal"}, { + on_recv_message = function(pos, src, topic, payload) + if topic == "term" then + output(pos, payload) + return true + elseif topic == "clear" then + local mem = techage.get_mem(pos) + mem.output = "" + mem.command = "" + M(pos):set_string("formspec", formspec2(mem)) + return true + end + end, +}) diff --git a/collider/vacuumtube.lua b/collider/vacuumtube.lua new file mode 100644 index 0000000..ad5b2be --- /dev/null +++ b/collider/vacuumtube.lua @@ -0,0 +1,130 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Vacuum Tube as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local VTube = tubelib2.Tube:new({ + dirs_to_check = {1,2,3,4}, + max_tube_length = 5, + tube_type = "vtube", + show_infotext = false, + primary_node_names = {"techage:ta4_vtubeS", "techage:ta4_vtubeA"}, + secondary_node_names = {"techage:ta4_magnet"}, + after_place_tube = function(pos, param2, tube_type, num_tubes) + minetest.swap_node(pos, {name = "techage:ta4_vtube"..tube_type, param2 = param2}) + end, +}) + +techage.VTube = VTube + +minetest.register_node("techage:ta4_vtubeS", { + description = S("TA4 Vacuum Tube"), + drawtype = "nodebox", + tiles = { + -- up, down, right, left, back, front + "techage_collider_tube.png^[transformR90", + "techage_collider_tube.png^[transformR90", + "techage_collider_tube.png", + "techage_collider_tube.png", + 'techage_collider_tube_open.png', + 'techage_collider_tube_open.png', + }, + node_box = { + type = "fixed", + fixed = { + {-8/16, -8/16, -8/16, -6/16, 8/16, 8/16}, + { 6/16, -8/16, -8/16, 8/16, 8/16, 8/16}, + {-8/16, 6/16, -8/16, 8/16, 8/16, 8/16}, + {-8/16, -8/16, -8/16, 8/16, -6/16, 8/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, + }, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + if not VTube:after_place_tube(pos, placer, pointed_thing) then + minetest.remove_node(pos) + return true + end + return false + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + VTube:after_dig_tube(pos, oldnode, oldmetadata) + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + use_texture_alpha = techage.CLIP, + sunlight_propagates = true, + is_ground_content = false, + groups = {cracky = 2}, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_node("techage:ta4_vtubeA", { + description = S("TA4 Vacuum Tube"), + drawtype = "nodebox", + tiles = { + -- up, down, right, left, back, front + "techage_collider_tube.png^[transformR90", + 'techage_collider_tube.png^techage_collider_tube_open.png', + "techage_collider_tube.png", + "techage_collider_tube.png", + "techage_collider_tube.png^[transformR90", + 'techage_collider_tube.png^techage_collider_tube_open.png', + }, + node_box = { + type = "fixed", + fixed = { + {-8/16, -8/16, -8/16, -6/16, 8/16, 8/16}, + { 6/16, -8/16, -8/16, 8/16, 8/16, 8/16}, + {-8/16, 6/16, -8/16, 8/16, 8/16, 8/16}, + {-8/16, -8/16, 6/16, 8/16, 8/16, 8/16}, + {-8/16, -8/16, -8/16, 8/16, -6/16, -6/16}, + }, + }, + selection_box = { + type = "fixed", + fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, + }, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + VTube:after_dig_tube(pos, oldnode, oldmetadata) + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + use_texture_alpha = techage.CLIP, + sunlight_propagates = true, + is_ground_content = false, + groups = {cracky = 1, not_in_creative_inventory=1}, + drop = "techage:ta4_vtubeS", + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_craft({ + output = "techage:ta4_vtubeS 4", + recipe = { + {'', 'default:steel_ingot', ''}, + {'techage:aluminum', 'dye:blue', 'techage:aluminum'}, + {'', 'default:steel_ingot', ''}, + }, +}) diff --git a/collider/worker.lua b/collider/worker.lua new file mode 100644 index 0000000..a5b3b63 --- /dev/null +++ b/collider/worker.lua @@ -0,0 +1,205 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 Detector Worlker as part of the Collider + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local AssemblyPlan = { + -- y-offs, path, facedir-offs, name + -- 0 = forward, 1 = right, 2 = backward, 3 = left + + -- level 1 + -- left/right + { 1, {3,3,3,2}, 0, "default:steelblock"}, + { 1, {3,3,3}, 0, "default:steelblock"}, + { 1, {3,3,3,0}, 0, "default:steelblock"}, + { 1, {1,1,1,2}, 0, "default:steelblock"}, + { 1, {1,1,1}, 0, "default:steelblock"}, + { 1, {1,1,1,0}, 0, "default:steelblock"}, + -- front + { 1, {3,3,2}, 0, "default:steelblock"}, + { 1, {3,2}, 0, "default:steelblock"}, + { 1, {2}, 0, "default:steelblock"}, + { 1, {1,2}, 0, "default:steelblock"}, + { 1, {1,1,2}, 0, "default:steelblock"}, + -- back + { 1, {3,3,0}, 0, "default:steelblock"}, + { 1, {3,0}, 0, "default:steelblock"}, + { 1, {0}, 2, "techage:ta4_collider_pipe_inlet"}, + { 1, {1,0}, 0, "default:steelblock"}, + { 1, {1,1,0}, 0, "default:steelblock"}, + -- middle + { 1, {3,3}, 0, "techage:ta4_detector_magnet"}, + { 1, {3}, 0, "techage:ta4_detector_magnet"}, + { 1, {}, 0, "techage:ta4_detector_core"}, + { 1, {1}, 0, "techage:ta4_detector_magnet"}, + { 1, {1,1}, 0, "techage:ta4_detector_magnet"}, + + -- level 2 + -- left/right + { 2, {3,3,3,2}, 1, "techage:ta4_collider_pipe_inlet"}, + { 2, {3,3,3}, 1, "techage:ta4_collider_tube_inlet"}, + { 2, {3,3,3,0}, 0, "default:steelblock"}, + { 2, {1,1,1,2}, 3, "techage:ta4_collider_pipe_inlet"}, + { 2, {1,1,1}, 3, "techage:ta4_collider_tube_inlet"}, + { 2, {1,1,1,0}, 0, "default:steelblock"}, + -- front + { 2, {3,3,2}, 0, "techage:ta4_detector_magnet"}, + { 2, {3,2}, 0, "techage:ta4_detector_magnet"}, + { 2, {2}, 0, "default:obsidian_glass"}, + { 2, {1,2}, 0, "techage:ta4_detector_magnet"}, + { 2, {1,1,2}, 0, "techage:ta4_detector_magnet"}, + -- back + { 2, {3,3,0}, 0, "techage:ta4_detector_magnet"}, + { 2, {3,0}, 0, "techage:ta4_detector_magnet"}, + { 2, {0}, 0, "default:steelblock"}, + { 2, {1,0}, 0, "techage:ta4_detector_magnet"}, + { 2, {1,1,0}, 0, "techage:ta4_detector_magnet"}, + + -- level 3 + -- left/right + { 3, {3,3,3,2}, 0, "default:steelblock"}, + { 3, {3,3,3}, 1, "techage:ta4_collider_cable_inlet"}, + { 3, {3,3,3,0}, 0, "default:steelblock"}, + { 3, {1,1,1,2}, 0, "default:steelblock"}, + { 3, {1,1,1}, 3, "techage:ta4_collider_cable_inlet"}, + { 3, {1,1,1,0}, 0, "default:steelblock"}, + -- front + { 3, {3,3,2}, 0, "default:steelblock"}, + { 3, {3,2}, 0, "default:steelblock"}, + { 3, {2}, 0, "default:steelblock"}, + { 3, {1,2}, 0, "default:steelblock"}, + { 3, {1,1,2}, 0, "default:steelblock"}, + -- back + { 3, {3,3,0}, 0, "default:steelblock"}, + { 3, {3,0}, 0, "default:steelblock"}, + { 3, {0}, 2, "techage:ta4_collider_pipe_inlet"}, + { 3, {1,0}, 0, "default:steelblock"}, + { 3, {1,1,0}, 0, "default:steelblock"}, + -- middle + { 3, {3,3}, 0, "techage:ta4_detector_magnet"}, + { 3, {3}, 0, "techage:ta4_detector_magnet"}, + { 3, {}, 0, "techage:ta4_collider_pipe_outlet"}, + { 3, {1}, 0, "techage:ta4_detector_magnet"}, + { 3, {1,1}, 0, "techage:ta4_detector_magnet"}, +} + +local t = {} +for name, cnt in pairs(techage.assemble.count_items(AssemblyPlan)) do + t[#t + 1] = " - " .. cnt .. " " .. name +end +local LABEL = table.concat(t, "\n") + +local function build(pos, player_name) + minetest.chat_send_player(player_name, S("[TA4] Detector is being built!")) + local inv = M(pos):get_inventory() + techage.assemble.build_inv(pos, inv, AssemblyPlan, player_name) +end + +local function remove(pos, player_name) + minetest.chat_send_player(player_name, S("[TA4] Detector is being removed!")) + local inv = M(pos):get_inventory() + techage.assemble.remove_inv(pos, inv, AssemblyPlan, player_name) +end + + +local function formspec() + return "size[8,8.2]".. + "list[context;src;5,0;3,3;]".. + "label[0.2,-0.2;" .. S("Item list") .. ":\n" .. LABEL .. "]" .. + "button_exit[0,3.5;4,1;build;" .. S("Build detector") .. "]" .. + "button_exit[4,3.5;4,1;remove;" .. S("Remove detector") .. "]" .. + "list[current_player;main;0,4.5;8,4;]".. + "listring[context;src]".. + "listring[current_player;main]" +end + +minetest.register_node("techage:ta4_collider_detector_worker", { + description = S("TA4 Collider Detector Worker"), + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png^techage_collider_detector_appl.png^techage_collider_detector_banner.png", + "default_steel_block.png^techage_collider_detector_banner.png", + "default_steel_block.png^techage_collider_detector_banner.png", + "default_steel_block.png^techage_collider_detector_banner.png", + "default_steel_block.png^techage_collider_detector_banner.png", + "default_steel_block.png^techage_collider_detector_appl.png^techage_collider_detector_banner.png", + }, + drawtype = "nodebox", + paramtype2 = "facedir", + groups = {cracky = 1}, + on_rotate = screwdriver.disallow, + is_ground_content = false, + sounds = default.node_sound_metal_defaults(), + + after_place_node = function(pos, placer, itemstack) + if pos.y > (techage.collider_min_depth - 2) then + minetest.remove_node(pos) + minetest.add_item(pos, ItemStack("techage:ta4_collider_detector_worker")) + return + end + local inv = M(pos):get_inventory() + inv:set_size("src", 9) + M(pos):set_string("formspec", formspec()) + end, + + on_receive_fields = function(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + + local nvm = techage.get_nvm(pos) + if fields.build then + if not nvm.assemble_locked then + build(pos, player:get_player_name()) + end + elseif fields.remove then + if not nvm.assemble_locked then + remove(pos, player:get_player_name()) + end + end + end, + + after_dig_node = function(pos, oldnode) + techage.del_mem(pos) + end, + + can_dig = function(pos, player) + if minetest.is_protected(pos, player:get_player_name()) then + return false + end + local nvm = techage.get_nvm(pos) + if nvm.assemble_locked or nvm.assemble_build then + minetest.after(30, function(pos) + local nvm = techage.get_nvm(pos) + nvm.assemble_locked = false + end, pos) + return false + end + local inv = M(pos):get_inventory() + return inv:is_empty("src") + end, +}) + + +minetest.register_craft({ + output = "techage:ta4_collider_detector_worker", + recipe = { + {'techage:aluminum', 'default:chest', 'default:steel_ingot'}, + {'', 'basic_materials:gear_steel', ''}, + {'default:steel_ingot', 'default:mese_crystal', 'techage:aluminum'}, + }, +}) + diff --git a/energy_storage/heatexchanger1.lua b/energy_storage/heatexchanger1.lua index 78f562b..198f587 100644 --- a/energy_storage/heatexchanger1.lua +++ b/energy_storage/heatexchanger1.lua @@ -25,12 +25,12 @@ local power = networks.power local function turbine_cmnd(pos, topic, payload) return techage.transfer(pos, "R", topic, payload, Pipe, - {"techage:ta4_turbine", "techage:ta4_turbine_on"}) + {"techage:ta4_turbine", "techage:ta4_turbine_on", "techage:ta4_collider_cooler"}) end local function inlet_cmnd(pos, topic, payload) return techage.transfer(pos, "L", topic, payload, Pipe, - {"techage:ta4_pipe_inlet"}) + {"techage:ta4_pipe_inlet", "techage:ta4_collider_pipe_inlet"}) end minetest.register_node("techage:heatexchanger1", { @@ -83,7 +83,7 @@ techage.register_node({"techage:heatexchanger1"}, { on_transfer = function(pos, indir, topic, payload) local nvm = techage.get_nvm(pos) -- used by heatexchanger2 - if topic == "diameter" or topic == "volume" or topic == "window" then + if topic == "diameter" or topic == "volume" or topic == "window" or topic == "detector" then return inlet_cmnd(pos, topic, payload) else return turbine_cmnd(pos, topic, payload) diff --git a/energy_storage/heatexchanger2.lua b/energy_storage/heatexchanger2.lua index 591acc8..b57bf94 100644 --- a/energy_storage/heatexchanger2.lua +++ b/energy_storage/heatexchanger2.lua @@ -9,6 +9,7 @@ See LICENSE.txt for more information TA4 Heat Exchanger2 (middle part) + (alternatively used as cooler for the TA4 collider) ]]-- @@ -31,6 +32,7 @@ local PWR_CAPA = { [9] = GRVL_CAPA * 7 * 7 * 7, -- 286 kuh } local DOWN = 5 +local PWR_NEEDED = 5 local function heatexchanger1_cmnd(pos, topic, payload) return techage.transfer({x = pos.x, y = pos.y - 1, z = pos.z}, @@ -75,7 +77,25 @@ local function stop_sound(pos) end end +local function cooler_formspec(self, pos, nvm) + return "size[4,2]".. + "box[0,-0.1;3.8,0.5;#c6e8ff]" .. + "label[0.2,-0.1;" .. minetest.colorize( "#000000", S("TA4 Heat Exchanger")) .. "]" .. + "image_button[1.5,1;1,1;".. self:get_state_button_image(nvm) ..";state_button;]".. + "tooltip[1.5,1;1,1;"..self:get_state_tooltip(nvm).."]" +end + local function can_start(pos, nvm) + -- Used as cooler for the collider? + if heatexchanger1_cmnd(pos, "detector") then + if power.power_available(pos, Cable, DOWN) then + nvm.used_as_cooler = true + return true + else + return S("No power") + end + end + -- Used as heat exchanger local netID = networks.determine_netID(pos, Cable, DOWN) if heatexchanger1_cmnd(pos, "netID") ~= netID then return S("Power network connection error") @@ -97,21 +117,32 @@ local function can_start(pos, nvm) end local function start_node(pos, nvm) - nvm.win_pos = heatexchanger1_cmnd(pos, "window") - power.start_storage_calc(pos, Cable, DOWN) - play_sound(pos) - heatexchanger1_cmnd(pos, "start") + if nvm.used_as_cooler then + play_sound(pos) + else + nvm.win_pos = heatexchanger1_cmnd(pos, "window") + power.start_storage_calc(pos, Cable, DOWN) + play_sound(pos) + heatexchanger1_cmnd(pos, "start") + end end local function stop_node(pos, nvm) - power.start_storage_calc(pos, Cable, DOWN) - stop_sound(pos) - heatexchanger1_cmnd(pos, "stop") + if nvm.used_as_cooler then + stop_sound(pos) + else + power.start_storage_calc(pos, Cable, DOWN) + stop_sound(pos) + heatexchanger1_cmnd(pos, "stop") + end end local function formspec(self, pos, nvm) local data + if nvm.used_as_cooler then + return cooler_formspec(self, pos, nvm) + end if techage.is_running(nvm) then data = power.get_network_data(pos, Cable, DOWN) end @@ -135,11 +166,9 @@ local function check_TES_integrity(pos, nvm) end if (nvm.ticks % 30) == 0 then -- every minute return heatexchanger1_cmnd(pos, "volume") - end - if (nvm.ticks % 30) == 10 then -- every minute - return heatexchanger3_cmnd(pos, "diameter") ~= nil or S("inlet/pipe error") - end - if (nvm.ticks % 30) == 20 then -- every minute + elseif (nvm.ticks % 30) == 10 then -- every minute + return heatexchanger1_cmnd(pos, "diameter") ~= nil or S("inlet/pipe error") + elseif (nvm.ticks % 30) == 20 then -- every minute return heatexchanger3_cmnd(pos, "diameter") ~= nil or S("inlet/pipe error") end local netID = networks.determine_netID(pos, Cable, DOWN) @@ -166,8 +195,38 @@ local State = techage.NodeStates:new({ formspec_func = formspec, }) +local function cooler_timer(pos, nvm) + local err = false + if power.consume_power(pos, Cable, DOWN, PWR_NEEDED) ~= PWR_NEEDED then + State:fault(pos, nvm, "No power") + stop_sound(pos) + return true + end + + -- Cyclically check pipe connections + nvm.ticks = (nvm.ticks or 0) + 1 + if (nvm.ticks % 5) == 0 then -- every 10 s + err = heatexchanger1_cmnd(pos, "detector") ~= true + elseif (nvm.ticks % 5) == 1 then -- every 10 s + err = heatexchanger3_cmnd(pos, "detector") ~= true + elseif (nvm.ticks % 5) == 2 then -- every 10 s + err = heatexchanger1_cmnd(pos, "cooler") ~= true + elseif (nvm.ticks % 5) == 3 then -- every 10 s + err = heatexchanger3_cmnd(pos, "cooler") ~= true + end + if err then + State:fault(pos, nvm, "Pipe connection error") + stop_sound(pos) + end + return true +end + local function node_timer(pos, elapsed) local nvm = techage.get_nvm(pos) + if nvm.used_as_cooler then + cooler_timer(pos, nvm) + return true + end local res = check_TES_integrity(pos, nvm) if res ~= true then State:fault(pos, nvm, res) diff --git a/energy_storage/heatexchanger3.lua b/energy_storage/heatexchanger3.lua index 0fa9688..70f3713 100644 --- a/energy_storage/heatexchanger3.lua +++ b/energy_storage/heatexchanger3.lua @@ -42,9 +42,14 @@ local function after_dig_node(pos, oldnode) Pipe:after_dig_node(pos) end +local function cooler_cmnd(pos, topic, payload) + return techage.transfer(pos, "R", topic, payload, Pipe, + {"techage:ta4_collider_cooler"}) +end + local function inlet_cmnd(pos, topic, payload) return techage.transfer(pos, "L", topic, payload, Pipe, - {"techage:ta4_pipe_inlet"}) + {"techage:ta4_pipe_inlet", "techage:ta4_collider_pipe_inlet"}) end minetest.register_node("techage:heatexchanger3", { @@ -77,7 +82,11 @@ Pipe:add_secondary_node_names({"techage:heatexchanger3"}) -- command interface, used by heatexchanger2 techage.register_node({"techage:heatexchanger3"}, { on_transfer = function(pos, indir, topic, payload) - return inlet_cmnd(pos, topic, payload) + if topic == "cooler" then + return cooler_cmnd(pos, topic, payload) + else + return inlet_cmnd(pos, topic, payload) + end end, }) diff --git a/init.lua b/init.lua index 231e738..f482367 100644 --- a/init.lua +++ b/init.lua @@ -55,6 +55,7 @@ techage.max_num_forceload_blocks = tonumber(minetest.settings:get("techage_max_n techage.basalt_stone_enabled = minetest.settings:get_bool("techage_basalt_stone_enabled") ~= false techage.ore_rarity = tonumber(minetest.settings:get("techage_ore_rarity")) or 1 techage.modified_recipes_enabled = minetest.settings:get_bool("techage_modified_recipes_enabled") ~= false +techage.collider_min_depth = tonumber(minetest.settings:get("techage_collider_min_depth")) or -30 -- allow to load marshal and sqlite3 techage.IE = minetest.request_insecure_environment() @@ -98,6 +99,7 @@ dofile(MP.."/basis/windturbine_lib.lua") dofile(MP.."/basis/laser_lib.lua") dofile(MP.."/basis/legacy.lua") dofile(MP.."/basis/hyperloop.lua") +dofile(MP.."/basis/oggfiles.lua") -- Main doc dofile(MP.."/doc/manual_DE.lua") @@ -294,6 +296,7 @@ dofile(MP.."/move_controller/doorcontroller2.lua") -- new dofile(MP.."/move_controller/movecontroller.lua") dofile(MP.."/move_controller/turncontroller.lua") dofile(MP.."/move_controller/flycontroller.lua") +dofile(MP.."/move_controller/soundblock.lua") -- Test @@ -377,6 +380,14 @@ dofile(MP.."/items/moreblocks.lua") dofile(MP.."/carts/tank_cart.lua") dofile(MP.."/carts/chest_cart.lua") +-- Collider +dofile(MP.."/collider/vacuumtube.lua") +dofile(MP.."/collider/magnet.lua") +dofile(MP.."/collider/inlets.lua") +dofile(MP.."/collider/cooler.lua") +dofile(MP.."/collider/detector.lua") +dofile(MP.."/collider/worker.lua") + -- Prevent other mods from using IE techage.IE = nil diff --git a/items/ceramic.lua b/items/ceramic.lua index 62aa79b..357fb00 100644 --- a/items/ceramic.lua +++ b/items/ceramic.lua @@ -52,4 +52,18 @@ techage.furnace.register_recipe({ "techage:ta4_ceramic_material", }, time = 16, +}) + +minetest.register_craftitem("techage:ta4_round_ceramic", { + description = S("TA4 Round Ceramic"), + inventory_image = "techage_round_ceramic.png", +}) + +techage.furnace.register_recipe({ + output = "techage:ta4_round_ceramic 2", + recipe = { + "techage:ta4_ceramic_material", "techage:ta4_ceramic_material", + "techage:ta4_ceramic_material", "techage:ta4_ceramic_material", + }, + time = 16, }) \ No newline at end of file diff --git a/liquids/pump.lua b/liquids/pump.lua index 258ea53..e50b245 100644 --- a/liquids/pump.lua +++ b/liquids/pump.lua @@ -56,6 +56,11 @@ local function pumping(pos, nvm, state, capa) if taken > 0 then local leftover = liquid.put(pos, Pipe, outdir, name, taken, mem.dbg_cycles > 0) if leftover and leftover > 0 then + -- air needs no tank + if name == "air" then + state:keep_running(pos, nvm, COUNTDOWN_TICKS) + return 0 + end liquid.untake(pos, Pipe, Flip[outdir], name, leftover) if leftover == taken then state:blocked(pos, nvm) diff --git a/logic/terminal.lua b/logic/terminal.lua index 7bf483f..cf63084 100644 --- a/logic/terminal.lua +++ b/logic/terminal.lua @@ -3,7 +3,7 @@ Terminal ======== - Copyright (C) 2018-2020 Joachim Stolberg + Copyright (C) 2018-2021 Joachim Stolberg AGPL v3 See LICENSE.txt for more information @@ -15,22 +15,36 @@ local M = minetest.get_meta local S = techage.S -local HELP_TA3 = "Syntax:\n".. -" cmd \n".. -"\n".. -"like: cmd 181 on\n".. -"or: cmd 4573 state\n".. -"\n".. -"Local commands:\n".. -"- clear = clear screen\n".. -"- help = this message\n".. -"- pub = switch to public use of buttons\n".. -"- priv = switch to private use of buttons\n".. -"To program a user button with a command:\n".. -" set \n".. -"e.g.: set 1 ON cmd 123 on" +local HELP_TA3 = [[ #### TA3 Terminal #### +Send commands to machines and output the results. +Local commands: +- Clear screen with 'clear' +- Output this message with 'help' +- Switch to public use of buttons with 'pub' +- Switch to private use of buttons with 'priv' +- Program a user button with + 'set ' + Example: 'set 1 ON cmd 1234 on' +- send a command with 'cmd ' + Example: 'cmd 1234 on']] -local CMNDS_TA3 = S("Syntax error, try help") +local HELP_TA4 = [[ #### TA4 Terminal #### +Send commands to machines and output the results. +Local commands: +- Clear screen with 'clear' +- Output this message with 'help' +- Switch to public use of buttons with 'pub' +- Switch to private use of buttons with 'priv' +- Program a user button with + 'set ' + Example: 'set 1 ON cmd 1234 on' +- send a command with 'cmd ' + Example: 'cmd 1234 on' +- Connect to a machine with 'connect ' +If connected, compact commands like 'status' +are possible.]] + +local SYNTAX_ERR = S("Syntax error, try help") local function get_string(meta, num, default) local s = meta:get_string("bttn_text"..num) @@ -54,24 +68,28 @@ local function formspec2(meta) local bttn_text7 = get_string(meta, 7, "User7") local bttn_text8 = get_string(meta, 8, "User8") local bttn_text9 = get_string(meta, 9, "User9") - return "size[10,8]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "style_type[table,field;font=mono]".. - "button[0,0;3.3,1;bttn1;"..bttn_text1.."]button[3.3,0;3.3,1;bttn2;"..bttn_text2.."]button[6.6,0;3.3,1;bttn3;"..bttn_text3.."]".. - "button[0,0.8;3.3,1;bttn4;"..bttn_text4.."]button[3.3,0.8;3.3,1;bttn5;"..bttn_text5.."]button[6.6,0.8;3.3,1;bttn6;"..bttn_text6.."]".. - "button[0,1.6;3.3,1;bttn7;"..bttn_text7.."]button[3.3,1.6;3.3,1;bttn8;"..bttn_text8.."]button[6.6,1.6;3.3,1;bttn9;"..bttn_text9.."]".. - "table[0,2.5;9.8,4.7;output;"..output..";200]".. - "field[0.4,7.7;7.6,1;cmnd;;"..command.."]" .. + return "size[10,8.5]".. + --"style_type[table,field;font=mono]".. + "button[0,-0.2;3.3,1;bttn1;"..bttn_text1.."]button[3.3,-0.2;3.3,1;bttn2;"..bttn_text2.."]button[6.6,-0.2;3.3,1;bttn3;"..bttn_text3.."]".. + "button[0,0.6;3.3,1;bttn4;"..bttn_text4.."]button[3.3,0.6;3.3,1;bttn5;"..bttn_text5.."]button[6.6,0.6;3.3,1;bttn6;"..bttn_text6.."]".. + "button[0,1.4;3.3,1;bttn7;"..bttn_text7.."]button[3.3,1.4;3.3,1;bttn8;"..bttn_text8.."]button[6.6,1.4;3.3,1;bttn9;"..bttn_text9.."]".. + "table[0,2.3;9.8,5.6;output;"..output..";200]".. + "field[0.4,8.2;7.6,1;cmnd;;"..command.."]" .. "field_close_on_enter[cmnd;false]".. - "button[7.9,7.4;2,1;ok;"..S("Enter").."]" + "button[7.9,7.9;2,1;ok;"..S("Enter").."]" end local function output(pos, text) local meta = minetest.get_meta(pos) text = meta:get_string("output") .. "\n" .. (text or "") - text = text:sub(-500,-1) + text = text:sub(-1000,-1) + meta:set_string("output", text) + meta:set_string("formspec", formspec2(meta)) +end + +local function append(pos, text) + local meta = minetest.get_meta(pos) + text = meta:get_string("output") .. (text or "") meta:set_string("output", text) meta:set_string("formspec", formspec2(meta)) end @@ -84,37 +102,146 @@ local function get_line_text(pos, num) return line:gsub("^[%s$]*(.-)%s*$", "%1") end +local function server_debug(pos, command, player) + local cmnd, payload = command:match('^pipe%s+([%w_]+)%s*(.*)$') + if cmnd then + if not minetest.check_player_privs(player, "server") then + output(pos, "server privs missing") + return + end + local resp = techage.transfer( + pos, + "B", -- outdir + cmnd, -- topic + payload, -- payload + techage.LiquidPipe, -- network + nil) -- valid nodes + output(pos, dump(resp)) + return true + end + + cmnd, payload = command:match('^axle%s+([%w_]+)%s*(.*)$') + if cmnd then + if not minetest.check_player_privs(player, "server") then + output(pos, "server privs missing") + return + end + local resp = techage.transfer( + pos, + "B", -- outdir + cmnd, -- topic + payload, -- payload + techage.TA1Axle, -- network + nil) -- valid nodes + output(pos, dump(resp)) + return true + end + + cmnd, payload = command:match('^vtube%s+([%w_]+)%s*(.*)$') + if cmnd then + if not minetest.check_player_privs(player, "server") then + output(pos, "server privs missing") + return + end + local resp = techage.transfer( + pos, + "B", -- outdir + cmnd, -- topic + payload, -- payload + techage.VTube, -- network + nil) -- valid nodes + output(pos, dump(resp)) + return true + end +end -local function command(pos, command, player) +local function command(pos, command, player, is_ta4) local meta = minetest.get_meta(pos) local owner = meta:get_string("owner") or "" - if command then - command = command:sub(1,80) - command = string.trim(command) - - if command == "clear" then - meta:set_string("output", "") - meta:set_string("formspec", formspec2(meta)) - elseif command == "help" then - local meta = minetest.get_meta(pos) - meta:set_string("output", HELP_TA3) - meta:set_string("formspec", formspec2(meta)) - elseif command == "pub" then - meta:set_int("public", 1) - output(pos, player..":$ "..command) - output(pos, S("Switched to public use!")) - elseif command == "priv" then - meta:set_int("public", 0) - output(pos, player..":$ "..command) - output(pos, S("Switched to private use!")) + command = command:sub(1,80) + command = string.trim(command) + local cmnd, data = command:match('^(%w+)%s*(.*)$') + + if cmnd == "clear" then + meta:set_string("output", "") + meta:set_string("formspec", formspec2(meta)) + elseif cmnd == "" then + output(pos, "$") + elseif cmnd == "help" then + if is_ta4 then + output(pos, HELP_TA4) else - output(pos, "$ "..command) + output(pos, HELP_TA3) + end + elseif cmnd == "pub" then + meta:set_int("public", 1) + output(pos, "$ "..command) + output(pos, "Switched to public buttons!") + elseif cmnd == "priv" then + meta:set_int("public", 0) + output(pos, "$ "..command) + output(pos, "Switched to private buttons!") + elseif cmnd == "connect" and data then + output(pos, "$ "..command) + if techage.not_protected(data, owner, owner) then local own_num = meta:get_string("node_number") - local num, cmnd, payload = command:match('^cmd%s+([0-9]+)%s+(%w+)%s*(.*)$') - if num and cmnd then - if techage.not_protected(num, owner, owner) then - local resp = techage.send_single(own_num, num, cmnd, payload) + local resp = techage.send_single(own_num, data, cmnd) + if resp then + meta:set_string("connected_to", data) + output(pos, "Connected.") + else + meta:set_string("connected_to", "") + output(pos, "Not connected!") + end + else + output(pos, "Protection error!") + end + else + output(pos, "$ "..command) + local own_num = meta:get_string("node_number") + local connected_to = meta:contains("connected_to") and meta:get_string("connected_to") + local bttn_num, label, num, cmnd, payload + + num, cmnd, payload = command:match('^cmd%s+([0-9]+)%s+(%w+)%s*(.*)$') + if num and cmnd then + if techage.not_protected(num, owner, owner) then + local resp = techage.send_single(own_num, num, cmnd, payload) + if type(resp) == "string" then + output(pos, resp) + else + output(pos, dump(resp)) + end + end + return + end + + num, cmnd = command:match('^turn%s+([0-9]+)%s+([onf]+)$') + if num and (cmnd == "on" or cmnd == "off") then + if techage.not_protected(num, owner, owner) then + local resp = techage.send_single(own_num, num, cmnd) + output(pos, dump(resp)) + end + return + end + + bttn_num, label, cmnd = command:match('^set%s+([1-9])%s+([%w_]+)%s+(.+)$') + if bttn_num and label and cmnd then + meta:set_string("bttn_text"..bttn_num, label) + meta:set_string("bttn_cmnd"..bttn_num, cmnd) + meta:set_string("formspec", formspec2(meta)) + return + end + + if server_debug(pos, command, player) then + return + end + + if connected_to then + local cmnd, payload = command:match('^(%w+)%s*(.*)$') + if cmnd then + local resp = techage.send_single(own_num, connected_to, cmnd, payload) + if resp ~= true then if type(resp) == "string" then output(pos, resp) else @@ -123,59 +250,10 @@ local function command(pos, command, player) end return end - num, cmnd = command:match('^turn%s+([0-9]+)%s+([onf]+)$') - if num and (cmnd == "on" or cmnd == "off") then - if techage.not_protected(num, owner, owner) then - local resp = techage.send_single(own_num, num, cmnd) - output(pos, dump(resp)) - end - return - end - local bttn_num, label, cmnd = command:match('^set%s+([1-9])%s+([%w_]+)%s+(.+)$') - if bttn_num and label and cmnd then - meta:set_string("bttn_text"..bttn_num, label) - meta:set_string("bttn_cmnd"..bttn_num, cmnd) - meta:set_string("formspec", formspec2(meta)) - return - end - - local cmnd, payload = command:match('^pipe%s+([%w_]+)%s*(.*)$') - if cmnd then - if not minetest.check_player_privs(player, "server") then - output(pos, "server privs missing") - return - end - local resp = techage.transfer( - pos, - "B", -- outdir - cmnd, -- topic - payload, -- payload - techage.LiquidPipe, -- network - nil) -- valid nodes - output(pos, dump(resp)) - return - end - - local cmnd, payload = command:match('^axle%s+([%w_]+)%s*(.*)$') - if cmnd then - if not minetest.check_player_privs(player, "server") then - output(pos, "server privs missing") - return - end - local resp = techage.transfer( - pos, - "B", -- outdir - cmnd, -- topic - payload, -- payload - techage.TA1Axle, -- network - nil) -- valid nodes - output(pos, dump(resp)) - return - end - - if command ~= "" then - output(pos, CMNDS_TA3) - end + end + + if command ~= "" then + output(pos, SYNTAX_ERR) end end end @@ -186,9 +264,9 @@ local function send_cmnd(pos, meta, num) command(pos, cmnd, owner) end -local function register_terminal(num, tiles, node_box, selection_box) - minetest.register_node("techage:terminal"..num, { - description = S("TA3 Terminal"), +local function register_terminal(name, description, tiles, node_box, selection_box) + minetest.register_node("techage:"..name, { + description = description, tiles = tiles, drawtype = "nodebox", node_box = node_box, @@ -201,7 +279,7 @@ local function register_terminal(num, tiles, node_box, selection_box) meta:set_string("command", S("commands like: help")) meta:set_string("formspec", formspec2(meta)) meta:set_string("owner", placer:get_player_name()) - meta:set_string("infotext", S("TA3 Terminal") .. " " .. number) + meta:set_string("infotext", description .. " " .. number) end, on_receive_fields = function(pos, formname, fields, player) @@ -216,11 +294,21 @@ local function register_terminal(num, tiles, node_box, selection_box) meta:set_string("command", s) meta:set_string("formspec", formspec2(meta)) return - elseif (fields.key_enter == "true" or fields.ok) and fields.cmnd ~= "" then - command(pos, fields.cmnd, player:get_player_name()) + elseif (fields.ok or fields.key_enter_field) and fields.cmnd then + local is_ta4 = string.find(description, "TA4") + command(pos, fields.cmnd, player:get_player_name(), is_ta4) + techage.historybuffer_add(pos, fields.cmnd) meta:set_string("command", "") meta:set_string("formspec", formspec2(meta)) return + elseif fields.key_up then + meta:set_string("command", techage.historybuffer_priv(pos)) + meta:set_string("formspec", formspec2(meta)) + return + elseif fields.key_down then + meta:set_string("command", techage.historybuffer_next(pos)) + meta:set_string("formspec", formspec2(meta)) + return end end if public or not protected then @@ -251,7 +339,7 @@ local function register_terminal(num, tiles, node_box, selection_box) }) end -register_terminal("2", { +register_terminal("terminal2", S("TA3 Terminal"), { -- up, down, right, left, back, front 'techage_terminal2_top.png', 'techage_terminal2_side.png', @@ -277,7 +365,33 @@ register_terminal("2", { fixed = { {-12/32, -16/32, -4/32, 12/32, 6/32, 16/32}, }, - }) + } +) + +register_terminal("terminal3", S("TA4 Terminal"), { + -- up, down, right, left, back, front + 'techage_terminal1_top.png', + 'techage_terminal1_bottom.png', + 'techage_terminal1_side.png', + 'techage_terminal1_side.png', + 'techage_terminal1_bottom.png', + "techage_terminal1_front.png", + }, + { + type = "fixed", + fixed = { + {-12/32, -16/32, -8/32, 12/32, -14/32, 12/32}, + {-12/32, -14/32, 12/32, 12/32, 6/32, 14/32}, + }, + }, + { + type = "fixed", + fixed = { + {-12/32, -16/32, -8/32, 12/32, -14/32, 12/32}, + {-12/32, -14/32, 12/32, 12/32, 6/32, 14/32}, + }, + } +) minetest.register_craft({ output = "techage:terminal2", @@ -288,6 +402,15 @@ minetest.register_craft({ }, }) +minetest.register_craft({ + output = "techage:terminal3", + recipe = { + {"techage:basalt_glass_thin", "", ""}, + {"techage:ta4_leds", "", ""}, + {"techage:aluminum", "techage:ta4_wlanchip", "techage:ta4_ramchip"}, + }, +}) + techage.register_node({"techage:terminal2"}, { on_recv_message = function(pos, src, topic, payload) output(pos, "src="..src..", cmd="..dump(topic)..", data="..dump(payload)) @@ -302,3 +425,16 @@ techage.register_node({"techage:terminal2"}, { end end, }) + +techage.register_node({"techage:terminal3"}, { + on_recv_message = function(pos, src, topic, payload) + if topic == "text" then + output(pos, payload) + elseif topic == "append" then + append(pos, payload) + else + output(pos, "src="..src..", cmd="..dump(topic)..", data="..dump(payload)) + end + return true + end, +}) diff --git a/move_controller/flycontroller.lua b/move_controller/flycontroller.lua index efe34db..45cc1d9 100644 --- a/move_controller/flycontroller.lua +++ b/move_controller/flycontroller.lua @@ -47,18 +47,20 @@ local WRENCH_MENU = { local function formspec(nvm, meta) local status = meta:get_string("status") local path = meta:contains("path") and meta:get_string("path") or "0,3,0" - return "size[8,7]" .. + return "size[8,6.7]" .. "style_type[textarea;font=mono;textcolor=#FFFFFF;border=true]" .. "box[0,-0.1;7.2,0.5;#c6e8ff]" .. "label[0.2,-0.1;" .. minetest.colorize( "#000000", S("TA5 Fly Controller")) .. "]" .. techage.wrench_image(7.4, -0.05) .. "button[0.1,0.7;3.8,1;record;" .. S("Record") .. "]" .. "button[4.1,0.7;3.8,1;done;" .. S("Done") .. "]" .. - "textarea[0.4,2.1;3.8,3.8;path;" .. S("Move path (A to B)") .. ";"..path.."]" .. - "button[4.1,3.2;3.8,1;store;" .. S("Store") .. "]" .. - "button[0.1,5.5;3.8,1;moveAB;" .. S("Move A-B") .. "]" .. - "button[4.1,5.5;3.8,1;moveBA;" .. S("Move B-A") .. "]" .. - "label[0.3,6.5;" .. status .. "]" + "textarea[0.4,2.1;3.8,4.4;path;" .. S("Move path (A to B)") .. ";"..path.."]" .. + "button[4.1,1.8;3.8,1;store;" .. S("Store") .. "]" .. + "button[4.1,2.6;3.8,1;test;" .. S("Test") .. "]" .. + "button[4.1,3.4;3.8,1;moveAB;" .. S("Move A-B") .. "]" .. + "button[4.1,4.2;3.8,1;moveBA;" .. S("Move B-A") .. "]" .. + "button[4.1,5.0;3.8,1;move;" .. S("Move") .. "]" .. + "label[0.3,6.3;" .. status .. "]" end @@ -110,17 +112,46 @@ minetest.register_node("techage:ta5_flycontroller", { mark.stop(name) meta:set_string("formspec", formspec(nvm, meta)) elseif fields.store then - if fly.to_path(fields.path, MAX_DIST) then + local _, err = fly.to_path(fields.path, MAX_DIST) + if not err then meta:set_string("path", fields.path) meta:set_string("status", S("Stored")) else - meta:set_string("status", S("Error: Invalid path !!")) + meta:set_string("status", err) end meta:set_string("formspec", formspec(nvm, meta)) local name = player:get_player_name() mark.stop(name) nvm.moveBA = false nvm.running = true + elseif fields.test then + local path, err = fly.to_path(fields.path, MAX_DIST) + if err then + meta:set_string("status", err) + elseif path and nvm.lpos1 then + local pos = table.copy(nvm.lpos1[1]) + if pos then + for _, offs in ipairs(path) do + local pos2 = vector.add(pos, offs) + local s = string.format("[TA4 Fly Controller] %s + %s = %s", P2S(pos), P2S(offs), P2S(pos2)) + minetest.chat_send_player(player:get_player_name(), s) + pos = pos2 + end + meta:set_string("status", S("See chat output")) + else + minetest.chat_send_player(player:get_player_name(), S("[TA4 Fly Controller] Recording is missing!")) + meta:set_string("status", S("Error: Recording is missing !!")) + end + else + if nvm.lpos1 then + minetest.chat_send_player(player:get_player_name(), S("[TA4 Fly Controller] Invalid path!")) + meta:set_string("status", S("Error: Invalid path !!")) + else + minetest.chat_send_player(player:get_player_name(), S("[TA4 Fly Controller] Recording is missing!")) + meta:set_string("status", S("Error: Recording is missing !!")) + end + end + meta:set_string("formspec", formspec(nvm, meta)) elseif fields.moveAB then meta:set_string("status", "") if fly.move_to_other_pos(pos, false) then @@ -139,6 +170,16 @@ minetest.register_node("techage:ta5_flycontroller", { mark.stop(name) end meta:set_string("formspec", formspec(nvm, meta)) + elseif fields.move then + meta:set_string("status", "") + nvm.moveBA = nvm.moveBA == false + if fly.move_to_other_pos(pos, nvm.moveBA == false) then + nvm.running = true + meta:set_string("formspec", formspec(nvm, meta)) + local name = player:get_player_name() + mark.stop(name) + end + meta:set_string("formspec", formspec(nvm, meta)) end end, diff --git a/move_controller/soundblock.lua b/move_controller/soundblock.lua new file mode 100644 index 0000000..87982bc --- /dev/null +++ b/move_controller/soundblock.lua @@ -0,0 +1,128 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2021 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + Sound Block + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local t = {} +for idx, ogg in ipairs(techage.OggFileList) do + t[idx] = idx .. "," .. ogg +end +local OGG_FILES = table.concat(t, ",") + +local logic = techage.logic + +local GAIN = {0.05 ,0.1, 0.2, 0.5, 1.0} + + +local function play_sound(pos, ogg, gain) + minetest.sound_play(ogg, { + pos = pos, + gain = GAIN[gain or 1] or 1, + max_hear_distance = 15}) +end + +local function formspec(meta) + local idx = meta:contains("idx") and meta:get_int("idx") or 1 + local gain = meta:contains("gain") and meta:get_int("gain") or 1 + return "size[8,8]".. + "tablecolumns[text,width=5;text]".. + "table[0,0;8,6;oggfiles;" .. OGG_FILES .. ";" .. idx .. "]" .. + "dropdown[0,6.5;5.5,1.4;gain;1,2,3,4,5;" .. gain .. "]" .. + "button[2.5,7.2;3,1;play;" .. S("Play") .. "]" +end + +minetest.register_node("techage:ta3_soundblock", { + description = S("TA3 Sound Block"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta3.png^techage_frame_ta3_top.png", + "techage_filling_ta3.png^techage_frame_ta3_top.png", + "techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_sound.png", + }, + + after_place_node = function(pos, placer) + local meta = M(pos) + logic.after_place_node(pos, placer, "techage:ta3_soundblock", S("TA3 Sound Block")) + logic.infotext(meta, S("TA3 Sound Block")) + meta:set_string("formspec", formspec(meta)) + end, + + on_receive_fields = function(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + + if fields.oggfiles then + local mem = techage.get_mem(pos) + local t = minetest.explode_table_event(fields.oggfiles) + mem.idx = t.row + end + if fields.gain then + M(pos):set_int("gain", tonumber(fields.gain) or 1) + end + if fields.play then + local mem = techage.get_mem(pos) + M(pos):set_int("idx", mem.idx or 1) + local ogg = techage.OggFileList[mem.idx or 1] or techage.OggFileList[1] + play_sound(pos, ogg, M(pos):get_int("gain")) + end + end, + + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + +local INFO = [[Commands: 'on', 'sound', 'gain']] + +techage.register_node({"techage:ta3_soundblock"}, { + on_recv_message = function(pos, src, topic, payload) + if topic == "info" then + return INFO + elseif topic == "on" then + local mem = techage.get_mem(pos) + if not mem.blocking_time or (mem.blocking_time < minetest.get_gametime()) then + local idx = M(pos):get_int("idx") + local ogg = techage.OggFileList[idx or 1] or techage.OggFileList[1] + local gain = M(pos):get_float("gain") + play_sound(pos, ogg, gain) + mem.blocking_time = minetest.get_gametime() + 2 + return true + end + elseif topic == "sound" then + M(pos):get_int("idx", tonumber(payload or 1) or 1) + elseif topic == "gain" then + M(pos):get_int("gain", tonumber(payload or 1) or 1) + else + return "unsupported" + end + end, + on_node_load = function(pos) + local meta = M(pos) + meta:set_string("formspec", formspec(meta)) + end +}) + +minetest.register_craft({ + output = "techage:ta3_soundblock", + recipe = { + {"", "group:wood",""}, + {"techage:vacuum_tube", "basic_materials:gold_wire", "techage:usmium_nuggets"}, + {"", "group:wood", ""}, + }, +}) + diff --git a/settingtypes.txt b/settingtypes.txt index ac0117b..377539a 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -29,4 +29,8 @@ techage_use_sqlite (use sqlite database) bool false # To reduce the server CPU load, the number of sent techage commands # per player and minute can be limited. -techage_command_limit (Max. number of commands sent per minute) int 1200 \ No newline at end of file +techage_command_limit (Max. number of commands sent per minute) int 1200 + +# Colliders are huge systems and should not be built on the surface. +# 'techage_collider_min_depth specifies' the min. depth (Y) to build a TA4 Collider +techage_collider_min_depth (Min. depth to build a TA4 Collider) int -30 \ No newline at end of file diff --git a/sounds/techage_hum.ogg b/sounds/techage_hum.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b90b66dff2ca4cd998735558f7c831a87bcd40aa GIT binary patch literal 11397 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 literal 0 HcmV?d00001 diff --git a/sounds/techage_watermill.ogg b/sounds/techage_watermill.ogg index 3e6486a4306442e5024f928d85a668a5557898ea..246479ba0c66539c717d0bed1b414a4d61b0e832 100644 GIT binary patch delta 16903 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 literal 23079 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 diff --git a/ta1_watermill/watermill.lua b/ta1_watermill/watermill.lua index d9b04a3..b029ae9 100644 --- a/ta1_watermill/watermill.lua +++ b/ta1_watermill/watermill.lua @@ -271,7 +271,7 @@ minetest.register_entity("techage:ta1_watermill_entity", { stop_wheel(pos, self) end trigger_consumer(pos, self.facedir) - minetest.sound_play("techage_watermill", {gain = 0.3, pos = pos, + minetest.sound_play("techage_watermill", {gain = 0.5, pos = pos, max_hear_distance = 10}, true) end end, diff --git a/textures/techage_appl_sound.png b/textures/techage_appl_sound.png new file mode 100644 index 0000000000000000000000000000000000000000..4efea6445812aae57e8ed2c6486c61b76f12d46d GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;*aCb)T-^(NOiWDFuAPeqiZGT0 z`2{mLJiCzwdsdb+xwq@}N35Ag;H zGL{7S1v5B2yO9Ru=ycxArhC9Be+<33fvf&B?=TJxD|N9Sdw?F>TF`NU}gvik$r5) S@}nB4n8DN4&t;ucLK6T6tQ=$j literal 0 HcmV?d00001 diff --git a/textures/techage_collider_detector_core.png b/textures/techage_collider_detector_core.png new file mode 100644 index 0000000000000000000000000000000000000000..b0edd8dc66eede7ceed43a681b8bae65e3d4fcc1 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e^#Gp`SN8%RAd?`d>(ycdD&i># z@(X6r42)@>E{-7{$IphI4rDguxjL!kB#%`9L%VJRGi!j8 z*8&zkkqOQ~%BwJ8vf_uXq}=11wtl;H#(?KdV!GvplocC4!@ar6JO;EzRJ!)=Be)i+f( zPFxXjBq82u<)SZ>rq!FhiD;NO;e%@ZzCclfInC}4>I~28)f-;UY5mkRgK3>J$6>?c zi{C9`mhf-(Q>>8IRlm=j9u#9eHQ>3OL~|aS>!+JWhLUlatkRiocJJ-{lzd`z++`2% z)v&jhx{%tC))6*o+nL{6&h&fEbX_DQ9=o8bLpLyFb=qB#S*pwvwpBNt=P8&aIb&+i zouym9-8_*R%$wEocOgfFW%T<+4!mfmaMEjEo^XXHG>OJ@DcCpXYyh{&zq6 zXUxF5U=t6Yk3`phLxU^E3U7WKK9JC#a@>)PEluTU{p0$#?vqk?teVTfz;RUn?sWUN R3xF0ec)I$ztaD0e0ssb~KfwS1 literal 0 HcmV?d00001 diff --git a/textures/techage_collider_magnet_appl.png b/textures/techage_collider_magnet_appl.png new file mode 100644 index 0000000000000000000000000000000000000000..2f748e1fe91d0a30bee8cccb4675d3784546938e GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{SN8%RAoKr!hDrVX$%%;v z_UyTF#OxqYh_NKdFPOpM*^M+H$JEosF+}5ha)N|HLQYScZ*%WN!{+YIo!mxg2?dUa wo!L3G7CNyhx`v!+SmrQ+(YNse!%i6nlf!~%D-syZfCeyly85}Sb4q9e0DB%JuY_q{4JN6Ox)twLuwSee=;vOb7AcWdh_ j^%)cQSO528V`F$diTC8Qdr3cm`WQT2{an^LB{Ts5&;lvK literal 0 HcmV?d00001 diff --git a/textures/techage_collider_magnet_tube.png b/textures/techage_collider_magnet_tube.png new file mode 100644 index 0000000000000000000000000000000000000000..040c3e5bae07270a7f34dc0ae81a08818f22892b GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;*aCb)T-^(N3D S<}w+moWax8&t;ucLK6VkmLNd@ literal 0 HcmV?d00001 diff --git a/textures/techage_collider_plan.png b/textures/techage_collider_plan.png new file mode 100644 index 0000000000000000000000000000000000000000..3cdc09d7d187b3b035942f20e08ddff559067072 GIT binary patch literal 585 zcmeAS@N?(olHy`uVBq!ia0y~yV6*^Y2Nq_a$SsB1uRw}Fz$e7@|Ns9$rcB(zbR);* z%aTVR7;h|i#o}?dUM*^pS!d6|FzFh>_e=gA z=vt(H_1~k*Z_B(^=7#MGoAcFG)^6V`+jGD8e@7j5)?B@O{r^YbmR~OXBP}p!pug^K}`s={qgQZ79?|hzD_xLW`xp~davu#3clh@zddB^zM z%2swhAXulY_vQKNwJdyo_gCe&Hcu`t@;i6%;7Lo%^z$%kFWM6Vof1vZd|GEybAY#$~^1{zk9;_^`eCwoYE}wE)efaZB}_=6zkSBKA%Q Qm>?KDUHx3vIVCg!0LRfFM*si- literal 0 HcmV?d00001 diff --git a/textures/techage_collider_tube.png b/textures/techage_collider_tube.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef480b2aaed997f4d7ba13fefbe54fb61a1a30f GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^S7q;cnn7z+d@2<@`WJsm z6ak9&dAc};NL)@0ILOIt$TRunS9xbE&+v}_yhpstx}+CeNVIauTDtS`DNdygBAn_K zeh2P$vnei6W!-)B#o0{W1>gBUyqoK_?(--9FK7QvRXGuIW!7~c-z_};5_LTxXWw)d ze-3^apr?KOL8!&@yIBU?7foHcG0Sp^Lbsm4gImv@9@hT#TJFfxsMN)|*Y549JuS|9 X-Sf}>%k4{nE@SX?^>bP0l+XkKUDI2w literal 0 HcmV?d00001 diff --git a/textures/techage_collider_tube_open.png b/textures/techage_collider_tube_open.png new file mode 100644 index 0000000000000000000000000000000000000000..21af706e86212443eefd16e3a630fb1f3005d523 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{SN8%R-|AiFnJaBeb{h(a zx#q86E#P_qlw~Xl@(X5gcy=QV$l2@Z;uxZFK6c_o-opkQEd!E7va3>a&+GcxJJ1vj3;eFE>u!*|{o@qrunqil(KIth=<0&zgN`CJpcdz0d!JMQvg8b*k%9#0Zd6mK~zY`?bN|e!!Qg6 z;DRd7xMaEGz|PPt4-m=P87btxb

J1Y##0qBQFr_Dhz$F2n?4?ox4o2HwTR*fJyd=g5P*w1uQqy#CK9e1gXeF=@j?( z0zTu8mg6(ntbk?{pK9PB;30lgfX;u~E~?8w2f7UQ+ZyOH@M;CLwRJzeA8KGff!Y>_ d>&y4Q