mods that hopefully work for a TechAge-based pack

This commit is contained in:
2026-09-09 06:27:51 -06:00
parent 4e6b08d194
commit c27db2a537
6234 changed files with 375578 additions and 0 deletions
@@ -0,0 +1,242 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Assemble routines
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
techage.assemble = {}
local Face2Dir = {[0]=
{x=0, y=0, z=1},
{x=1, y=0, z=0},
{x=0, y=0, z=-1},
{x=-1, y=0, z=0},
{x=0, y=-1, z=0},
{x=0, y=1, z=0}
}
-- Determine the destination position based on the base position,
-- param2, and a route table like : {0,3}
-- 0 = forward, 1 = right, 2 = backward, 3 = left
local function dest_pos(pos, param2, route, y_offs)
local p2 = param2
local pos1 = {x=pos.x, y=pos.y+y_offs, z=pos.z}
for _,dir in ipairs(route) do
p2 = (param2 + dir) % 4
pos1 = vector.add(pos1, Face2Dir[p2])
end
return pos1, p2
end
-- timer based function
local function build(pos, param2, AssemblyPlan, 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)
minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4})
minetest.after(0.5, build, pos, param2, AssemblyPlan, idx+1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
-- timer based function
local function remove(pos, param2, AssemblyPlan, idx)
local item = AssemblyPlan[idx]
if item ~= nil then
local y, path = item[1], item[2]
local pos1 = dest_pos(pos, param2, path, y)
minetest.remove_node(pos1)
minetest.after(0.5, remove, pos, param2, AssemblyPlan, idx-1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
local function check_space(pos, param2, AssemblyPlan, player_name)
for _,item in ipairs(AssemblyPlan) do
local y, path, node_name = item[1], item[2], item[4]
local pos1 = dest_pos(pos, param2, path, y)
if minetest.is_protected(pos1, player_name) then
minetest.chat_send_player(player_name, S("[TA] Area is protected!"))
return false
end
local node = techage.get_node_lvm(pos1)
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to and node.name ~= node_name then
minetest.chat_send_player(player_name, S("[TA] Not enough space!"))
return false
end
end
return true
end
-- Two important flags:
-- 1) nvm.assemble_locked is true while the object is being assembled/disassembled
-- 2) nvm.assemble_build is true if the object is assembled
function techage.assemble.build(pos, 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)
if check_space(pos, node.param2, AssemblyPlan, player_name) then
nvm.assemble_locked = true
build(pos, node.param2, AssemblyPlan, 1)
nvm.assemble_build = true
end
end
function techage.assemble.remove(pos, 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(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
local meta = M(pos1):to_table()
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, node, meta, 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
@@ -0,0 +1,150 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Boiler common functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
local HEAT_STEP = 10
local MAX_WATER = 10
local BLOCKING_TIME = 0.3 -- 300ms
techage.boiler = {}
local IsWater = {
["bucket:bucket_river_water"] = "bucket:bucket_empty",
}
local IsBucket = {}
local function node_description(name)
name = string.split(name, " ")[1]
local ndef = minetest.registered_nodes[name] or minetest.registered_items[name] or minetest.registered_craftitems[name]
if ndef and ndef.description then
return minetest.formspec_escape(ndef.description)
end
return ""
end
local function item_image(x, y, itemname)
return "box["..x..","..y..";0.85,0.9;#808080]"..
"item_image["..x..","..y..";1,1;"..itemname.."]"
end
function techage.boiler.formspec(pos, nvm)
local title = S("Water Boiler")
local temp = nvm.temperature or 20
local ratio = nvm.power_ratio or 0
local tooltip = S("To add water punch\nthe boiler\nwith a water bucket")
return "size[5,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;4.8,0.5;#c6e8ff]"..
"label[1.5,-0.1;"..minetest.colorize("#000000", title).."]"..
item_image(1, 1.5, "default:water_source "..(nvm.num_water or 0))..
"tooltip[1,1.5;1,1;"..tooltip..";#0C3D32;#FFFFFF]"..
"image[3,1.0;1,2;techage_form_temp_bg.png^[lowpart:"..
temp..":techage_form_temp_fg.png]"..
"tooltip[3,1;1,2;"..S("water temperature")..";#0C3D32;#FFFFFF]"
end
function techage.boiler.water_temperature(pos, nvm)
nvm.temperature = nvm.temperature or 20
nvm.num_water = nvm.num_water or 0
nvm.water_level = nvm.water_level or 0
if nvm.fire_trigger then
nvm.temperature = math.min(nvm.temperature + HEAT_STEP, 100)
else
nvm.temperature = math.max(nvm.temperature - HEAT_STEP, 20)
end
nvm.fire_trigger = false
if nvm.water_level == 0 then
if nvm.num_water > 0 then
nvm.num_water = nvm.num_water - 1
nvm.water_level = 100
else
nvm.temperature = 20
end
end
return nvm.temperature
end
function techage.boiler.on_rightclick(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
end
function techage.boiler.can_dig(pos, player)
local nvm = techage.get_nvm(pos)
nvm.num_water = nvm.num_water or 0
return nvm.num_water == 0
end
local function space_in_inventory(wielded_item, item_count, puncher)
-- check if holding more than 1 empty container
if item_count > 1 then
local inv = puncher:get_inventory()
local item = ItemStack({name=wielded_item, count = item_count - 1})
if inv:room_for_item("main", item) then
inv:add_item("main", item)
return true
end
return false
end
return true
end
function techage.boiler.on_punch(pos, node, puncher, pointed_thing)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
nvm.num_water = nvm.num_water or 0
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
if IsWater[wielded_item] and nvm.num_water < MAX_WATER then
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water + 1
puncher:set_wielded_item(ItemStack(IsWater[wielded_item]))
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
elseif IsBucket[wielded_item] and nvm.num_water > 0 then
if item_count > 1 then
local inv = puncher:get_inventory()
local item = ItemStack(IsBucket[wielded_item])
if inv:room_for_item("main", item) then
inv:add_item("main", item)
puncher:set_wielded_item({name=wielded_item, count = item_count - 1})
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water - 1
end
else
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water - 1
puncher:set_wielded_item(ItemStack(IsBucket[wielded_item]))
end
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
end
end
function techage.register_water_bucket(empty_bucket, full_bucket)
IsWater[full_bucket] = empty_bucket
IsBucket[empty_bucket] = full_bucket
end
@@ -0,0 +1,173 @@
--[[
TechAge
=======
Copyright (C) 2020-2025 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Color library
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = techage.S
local COLORED = minetest.get_modpath("unifieddyes") and minetest.global_exists("unifieddyes")
local color = {}
color.COLOR64 = {
"#000000",
"#005500",
"#000055",
"#005555",
"#0000AA",
"#0055AA",
"#0000FF",
"#0055FF",
"#550000",
"#555500",
"#550055",
"#555555",
"#5500AA",
"#5555AA",
"#5500FF",
"#5555FF",
"#AA0000",
"#AA5500",
"#AA0055",
"#AA5555",
"#AA00AA",
"#AA55AA",
"#AA00FF",
"#AA55FF",
"#FF0000",
"#FF5500",
"#FF0055",
"#FF5555",
"#FF00AA",
"#FF55AA",
"#FF00FF",
"#FF55FF",
"#55AA00",
"#55FF00",
"#55AA55",
"#55FF55",
"#55AAAA",
"#55FFAA",
"#55AAFF",
"#55FFFF",
"#AAAA00",
"#AAFF00",
"#AAAA55",
"#AAFF55",
"#AAAAAA",
"#AAFFAA",
"#AAAAFF",
"#AAFFFF",
"#FFAA00",
"#FFFF00",
"#FFAA55",
"#FFFF55",
"#FFAAAA",
"#FFFFAA",
"#FFAAFF",
"#FFFFFF",
"#000000",
"#232323",
"#464646",
"#696969",
"#8C8C8C",
"#AFAFAF",
"#D2D2D2",
"#F5F5F5",
}
local function move_item(item, pos, inv, digger)
if not (digger and digger:is_player()) then return end
local creative = minetest.is_creative_enabled(digger:get_player_name())
if inv:room_for_item("main", item)
and (not creative or not inv:contains_item("main", item, true)) then
inv:add_item("main", item)
elseif not creative then
minetest.item_drop(ItemStack(item), digger, pos)
end
minetest.remove_node(pos)
end
function color.on_dig(pos, node, digger)
if not digger then return end
local playername = digger:get_player_name()
if minetest.is_protected(pos, playername) then
return
end
local oldparam2 = minetest.get_node(pos).param2
local def = minetest.registered_items[node.name]
local inv = digger:get_inventory()
move_item(node.name, pos, inv, digger)
end
minetest.register_chatcommand("ta_color", {
description = minetest.formspec_escape(
"Output the color palette and the numbers for Lua/Beduino color commands"),
func = function(name, param)
local tbl = {}
if COLORED then
tbl[1] = "size[14,7]"
tbl[2] = "background[0,0;14,7;unifieddyes_palette_extended.png]"
for i = 0, 10 do
local y = i * 0.64
tbl[#tbl + 1] = "label[0," .. y .. ";" .. (i * 24 + 0) .. "]"
tbl[#tbl + 1] = "label[7," .. y .. ";" .. (i * 24 + 12) .. "]"
end
else
tbl[1] = "size[10,7.5]"
tbl[2] = "background[0,0;10,7.5;techage_palette256.png]"
for i = 0, 13 do
local y = i * 0.5
tbl[#tbl + 1] = "label[0," .. y .. ";" .. (i * 18 + 0) .. "]"
tbl[#tbl + 1] = "label[5," .. y .. ";" .. (i * 18 + 9) .. "]"
end
end
minetest.show_formspec(name, ";techage:color_form", table.concat(tbl, ""))
return true
end
})
minetest.register_chatcommand("ta_color64", {
description = minetest.formspec_escape(
"Output the color palette and the numbers for Techage displays"),
func = function(name, param)
local tbl = {}
tbl[1] = "size[8,8]"
tbl[2] = "background[0,0;8,8;techage_palette64.png]"
for i = 0, 7 do
local y = i + 0.2
tbl[#tbl + 1] = "label[0.2," .. y .. ";" .. (i * 8 + 0) .. "]"
tbl[#tbl + 1] = "label[4.2," .. y .. ";" .. (i * 8 + 4) .. "]"
end
minetest.show_formspec(name, ";techage:color_form", table.concat(tbl, ""))
return true
end
})
-- Register callback
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "techage:color_form" then
return false
end
return true
end)
techage.color = color
@@ -0,0 +1,722 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Basis functions for inter-node communication
]]--
--- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local has_mesecons = minetest.global_exists("mesecon")
local NodeInfoCache = {}
local NumbersToBeRecycled = {}
local CmndOnHold = {}
local MP = minetest.get_modpath("techage")
local techage_use_sqlite = minetest.settings:get_bool('techage_use_sqlite', false)
-- Localize functions to avoid table lookups (better performance)
local string_split = string.split
local NodeDef = techage.NodeDef
local Tube = techage.Tube
local is_cart_available = minecart.is_nodecart_available
local techage_counting_hit = techage.counting_hit
local tubelib2_side_to_dir = tubelib2.side_to_dir
-------------------------------------------------------------------
-- Database
-------------------------------------------------------------------
local backend
if techage_use_sqlite then
backend = dofile(MP .. "/basis/numbers_sqlite.lua")
else
backend = dofile(MP .. "/basis/numbers_storage.lua")
end
local function update_nodeinfo(number)
local pos = backend.get_nodepos(number)
if pos then
NodeInfoCache[number] = {pos = pos, name = techage.get_node_lvm(pos).name}
return NodeInfoCache[number]
end
end
local function delete_nodeinfo_entry(number)
if number and NodeInfoCache[number] then
number = next(NodeInfoCache, number)
if number then
NodeInfoCache[number] = nil
end
else
number = next(NodeInfoCache, nil)
end
return number
end
-- Keep the cache size small by deleting entries randomly
local function keep_small(number)
number = delete_nodeinfo_entry(number)
minetest.after(10, keep_small, number)
end
keep_small()
minetest.after(2, backend.delete_invalid_entries, NodeDef)
-------------------------------------------------------------------
-- Local helper functions
-------------------------------------------------------------------
local function in_list(list, x)
for _, v in ipairs(list) do
if v == x then return true end
end
return false
end
-- Determine position related node number for addressing purposes
local function get_number(pos, new)
local meta = minetest.get_meta(pos)
if meta:contains("node_number") then
return meta:get_string("node_number")
end
-- generate new number
if new then
local num = backend.add_nodepos(pos)
meta:set_string("node_number", num)
return num
end
end
local function register_lbm(name, nodenames)
minetest.register_lbm({
label = "[TechAge] Node update",
name = name.."update",
nodenames = nodenames,
run_at_every_load = true,
action = function(pos, node)
if NodeDef[node.name] and NodeDef[node.name].on_node_load then
NodeDef[node.name].on_node_load(pos, node)
end
end
})
end
local SideToDir = {B=1, R=2, F=3, L=4, D=5, U=6}
local function side_to_dir(side, param2)
return tubelib2_side_to_dir(side, param2)
end
techage.side_to_outdir = side_to_dir
function techage.side_to_indir(side, param2)
return tubelib2.Turn180Deg[side_to_dir(side, param2)]
end
local function get_next_node(pos, out_dir)
local res, npos, node = Tube:compatible_node(pos, out_dir)
local in_dir = tubelib2.Turn180Deg[out_dir]
return res, npos, in_dir, node.name
end
local function get_dest_node(pos, out_dir)
local spos, in_dir = Tube:get_connected_node_pos(pos, out_dir)
local _,node = Tube:get_node(spos)
return spos, in_dir, node.name
end
local function item_handling_node(name)
local node_def = name and NodeDef[name]
if node_def then
return node_def.on_pull_item or node_def.on_push_item or node_def.is_pusher
end
end
local function is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
techage.SystemTime = 0
minetest.register_globalstep(function(dtime)
techage.SystemTime = techage.SystemTime + dtime
end)
minetest.register_on_mods_loaded(function()
local nvm = techage.get_nvm0()
nvm.NormalShutdown = nvm.ServerCrashed == false
nvm.ServerCrashed = true
end)
-- used by TA1 hammer: dug_node[player_name] = pos
techage.dug_node = {}
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger then return end
-- store the position of the dug block for tools like the TA1 hammer
techage.dug_node[digger:get_player_name()] = pos
end)
-------------------------------------------------------------------
-- API helper functions
-------------------------------------------------------------------
-- Check if both strings are the same or one string starts with the other string.
function techage.string_compare(s1, s2)
if s1 and s2 then
local minLength = math.min(#s1, #s2)
return string.sub(s1, 1, minLength) == string.sub(s2, 1, minLength)
end
end
-- Function returns { pos, name } for the node referenced by number
function techage.get_node_info(dest_num)
return NodeInfoCache[dest_num] or update_nodeinfo(dest_num)
end
-- Function returns the node number from the given position or
-- nil, if no node number for this position is assigned.
function techage.get_node_number(pos)
return get_number(pos)
end
function techage.get_pos(pos, side)
local node = techage.get_node_lvm(pos)
local dir = nil
if node.name ~= "air" and node.name ~= "ignore" then
dir = side_to_dir(side, node.param2)
end
return tubelib2.get_pos(pos, dir)
end
-- Function is used for available nodes with lost numbers, only.
function techage.get_new_number(pos, name)
-- store position
return get_number(pos, true)
end
-- extract ident and value from strings like "ident=value"
function techage.ident_value(s)
local ident, value = unpack(string.split(s, "=", true, 1))
return (ident or ""):trim(), (value or ""):trim()
end
function techage.was_normal_shutdown()
local nvm = techage.get_nvm0()
return nvm.NormalShutdown
end
-------------------------------------------------------------------
-- Node construction/destruction functions
-------------------------------------------------------------------
-- Add node to the techage lists.
-- Function determines and returns the node position number,
-- needed for message communication.
-- If TA2 node, return '-' instead of a real number, because
-- TA2 nodes should not support number based commands.
function techage.add_node(pos, name, is_ta2)
if item_handling_node(name) then
Tube:after_place_node(pos)
end
if is_ta2 then
return "-"
end
local key = minetest.hash_node_position(pos)
local num = NumbersToBeRecycled[key]
if num then
NodeInfoCache[num] = nil
backend.set_nodepos(num, pos)
NumbersToBeRecycled[key] = nil
return num
end
return get_number(pos, true)
end
-- Function removes the node from the techage lists.
function techage.remove_node(pos, oldnode, oldmetadata)
local number = oldmetadata and oldmetadata.fields and (oldmetadata.fields.node_number or oldmetadata.fields.number)
number = number or get_number(pos)
if number and tonumber(number) then
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = number
NodeInfoCache[number] = nil
end
if oldnode and item_handling_node(oldnode.name) then
Tube:after_dig_node(pos)
end
end
-- Repairs the node number after it was erased by `backend.delete_invalid_entries`
function techage.repair_number(pos)
local number = techage.get_node_number(pos)
if number then
backend.set_nodepos(number, pos)
end
end
-------------------------------------------------------------------
-- Used by the assembly tool
-------------------------------------------------------------------
function techage.pre_add_node(pos, number)
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = number
end
function techage.post_remove_node(pos)
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = nil
end
-------------------------------------------------------------------
-- Node register function
-------------------------------------------------------------------
-- Register node for techage communication
-- Call this function only at load time!
-- Param names: List of node names like {"techage:pusher_off", "techage:pusher_on"}
-- Param node_definition: A table according to:
-- {
-- on_inv_request = func(pos, in_dir, access_type)
-- on_pull_item = func(pos, in_dir, num, (opt.) item_name),
-- on_push_item = func(pos, in_dir, item),
-- on_unpull_item = func(pos, in_dir, item),
-- on_recv_message = func(pos, src, topic, payload),
-- on_node_load = func(pos), -- LBM function
-- on_transfer = func(pos, in_dir, topic, payload),
-- }
function techage.register_node(names, node_definition)
-- store facedir table for all known node names
for _,n in ipairs(names) do
NodeDef[n] = node_definition
end
if node_definition.on_pull_item or node_definition.on_push_item or
node_definition.is_pusher then
Tube:add_secondary_node_names(names)
for _,n in ipairs(names) do
techage.KnownNodes[n] = true
end
end
-- register LBM
if node_definition.on_node_load then
register_lbm(names[1], names)
end
-- register mvps stopper
if has_mesecons and mesecon.register_mvps_stopper then
for _, name in ipairs(names) do
mesecon.register_mvps_stopper(name)
end
end
end
-------------------------------------------------------------------
-- Send message functions
-------------------------------------------------------------------
function techage.not_protected(number, player_name)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.pos then
return not minetest.is_protected(ninfo.pos, player_name)
end
return true -- node does not exist
end
-- Check the given number value.
-- Returns true if the number is valid, point to real node and
-- and the node is not protected for the given player_name.
function techage.check_number(number, placer_name)
if number then
if not techage.not_protected(number, placer_name) then
return false
end
return true
end
return false
end
-- Check the given list of numbers.
-- Returns true if number(s) is/are valid, point to real nodes and
-- and the nodes are not protected for the given player_name.
function techage.check_numbers(numbers, placer_name)
if numbers then
for _,num in ipairs(string_split(numbers, " ")) do
if not techage.not_protected(num, placer_name) then
return false
end
end
return true
end
return false
end
function techage.cmnd_hold(src)
CmndOnHold[src] = {}
end
function techage.cmnd_release(src)
local list = CmndOnHold[src]
if list then
for _,cmnd in ipairs(list) do
cmnd[1](cmnd[2], cmnd[3], cmnd[4], cmnd[5])
end
CmndOnHold[src] = nil
end
end
function techage.send_multi(src, numbers, topic, payload)
--print("send_multi", src, numbers, topic)
for _,num in ipairs(string_split(numbers, " ")) do
local ninfo = NodeInfoCache[num] or update_nodeinfo(num)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_recv_message then
techage_counting_hit()
if CmndOnHold[src] then
table.insert(CmndOnHold[src], {ndef.on_recv_message, ninfo.pos, src, topic, payload})
else
ndef.on_recv_message(ninfo.pos, src, topic, payload)
end
end
end
end
end
function techage.send_single(src, number, topic, payload)
--print("send_single", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_recv_message then
techage_counting_hit()
if CmndOnHold[src] then
table.insert(CmndOnHold[src], {ndef.on_recv_message, ninfo.pos, src, topic, payload})
return true
else
return ndef.on_recv_message(ninfo.pos, src, topic, payload)
end
end
end
return false
end
-- The destination node location is either:
-- A) a destination position, specified by pos
-- B) a neighbor position, specified by caller pos/outdir, or pos/side
-- C) a tubelib2 network connection, specified by caller pos/outdir, or pos/side
-- outdir is one of: 1..6
-- side is one of: "B", "R", "F", "L", "D", "U"
-- network is a tuebelib2 network instance
-- opt: nodenames is a table of valid the callee node names
function techage.transfer(pos, outdir, topic, payload, network, nodenames)
-- determine out-dir
if outdir and type(outdir) == "string" then
local param2 = techage.get_node_lvm(pos).param2
outdir = side_to_dir(outdir, param2)
end
-- determine destination pos
local dpos, indir
if network then
dpos, indir = network:get_connected_node_pos(pos, outdir)
else
dpos, indir = tubelib2.get_pos(pos, outdir), outdir
end
-- check node name
local name = techage.get_node_lvm(dpos).name
if nodenames and not in_list(nodenames, name) then
return false
end
-- call "on_transfer"
local ndef = NodeDef[name]
if ndef and ndef.on_transfer then
return ndef.on_transfer(dpos, indir, topic, payload)
end
return false
end
-------------------------------------------------------------------
-- Beduino functions (see "bep-005_ta_cmnd.md")
-------------------------------------------------------------------
function techage.beduino_send_cmnd(src, number, topic, payload)
--print("beduino_send_cmnd", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_beduino_receive_cmnd then
techage_counting_hit()
if CmndOnHold[src] then
table.insert(CmndOnHold[src], {ndef.on_beduino_receive_cmnd, ninfo.pos, src, topic, payload or {}})
return 0
else
return ndef.on_beduino_receive_cmnd(ninfo.pos, src, topic, payload or {})
end
end
end
return 1, ""
end
function techage.beduino_request_data(src, number, topic, payload)
--print("beduino_request_data", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_beduino_request_data then
techage_counting_hit()
if topic == 128 then
return 0, techage.get_node_lvm(ninfo.pos).name
else
return ndef.on_beduino_request_data(ninfo.pos, src, topic, payload or {})
end
else
if topic == 128 then
return 0, techage.get_node_lvm(ninfo.pos).name
end
end
end
return 1, ""
end
-------------------------------------------------------------------
-- Client side Push/Pull item functions
-------------------------------------------------------------------
function techage.get_inv_access(pos, out_dir, access_type)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_inv_request then
return NodeDef[name].on_inv_request(npos, in_dir, access_type)
end
end
function techage.pull_items(pos, out_dir, num, item_name)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_pull_item then
return NodeDef[name].on_pull_item(npos, in_dir, num, item_name)
end
end
function techage.push_items(pos, out_dir, stack, idx)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_push_item then
return NodeDef[name].on_push_item(npos, in_dir, stack, idx)
elseif is_air_like(name) or is_cart_available(npos) then
minetest.add_item(npos, stack)
return true
end
return stack
end
-- Check for recursion and too long distances
local start_pos
function techage.safe_push_items(pos, out_dir, stack, idx)
local mem = techage.get_mem(pos)
if not mem.pushing then
if not start_pos then
start_pos = pos
mem.pushing = true
local res = techage.push_items(pos, out_dir, stack, idx)
mem.pushing = nil
start_pos = nil
return res
else
local npos, in_dir, name = get_dest_node(pos, out_dir)
if vector.distance(start_pos, npos) < (Tube.max_tube_length or 100) then
mem.pushing = true
local res = techage.push_items(pos, out_dir, stack, idx)
mem.pushing = nil
return res
end
end
end
return stack
end
function techage.unpull_items(pos, out_dir, stack)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_unpull_item then
return NodeDef[name].on_unpull_item(npos, in_dir, stack)
end
return false
end
-------------------------------------------------------------------
-- Server side helper functions
-------------------------------------------------------------------
-- Get the given number of items from the inv. The position within the list
-- is incremented each time so that different item stacks will be considered.
-- Returns nil if ItemList is empty.
function techage.get_items(pos, inv, listname, num)
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
local mem = techage.get_mem(pos)
mem.ta_startpos = mem.ta_startpos or 0
for idx = mem.ta_startpos, mem.ta_startpos+size do
idx = (idx % size) + 1
local items = inv:get_stack(listname, idx)
if items:get_count() > 0 then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
mem.ta_startpos = idx
return taken
end
end
return nil
end
-- Put the given stack into the given ItemList/inventory.
-- Function returns:
-- - true, if all items are moved
-- - false, if no item is moved
-- - leftover, if less than all items are moved
-- (true/false is the legacy mode and can't be removed)
function techage.put_items(inv, listname, item, idx)
local leftover
if idx and inv and idx <= inv:get_size(listname) then
local stack = inv:get_stack(listname, idx)
leftover = stack:add_item(item)
inv:set_stack(listname, idx, stack)
elseif inv then
leftover = inv:add_item(listname, item)
else
return false
end
local cnt = leftover:get_count()
if cnt == item:get_count() then
return false
elseif cnt == 0 then
return true
else
return leftover
end
end
-- Return "full", "loaded", or "empty" depending
-- on the inventory load.
-- Full is returned, when no empty stack is available.
function techage.get_inv_state(inv, listname)
local state
if inv:is_empty(listname) then
state = "empty"
else
local list = inv:get_list(listname)
state = "full"
for _, item in ipairs(list) do
if item:is_empty() then
return "loaded"
end
end
end
return state
end
-- Beduino variant
function techage.get_inv_state_num(inv, listname)
local state
if inv:is_empty(listname) then
state = 0
else
local list = inv:get_list(listname)
state = 2
for _, item in ipairs(list) do
if item:is_empty() then
return 1
end
end
end
return state
end
-- Return the number of items in the given inventory list
-- that match the given item name.
-- The item name can be a full name or a substring of the name.
function techage.check_inv_item(inv, listname, item_name)
if inv:is_empty(listname) or item_name == nil then
return 0
else
local list = inv:get_list(listname)
for _, item in ipairs(list) do
if item:get_name():find(item_name, 1, true) then
return item:get_count()
end
end
end
return 0
end
minetest.register_chatcommand("ta_send", {
description = minetest.formspec_escape(
"Send a techage command to the block with the number given: /ta_send <number> <command> [<data>]"),
func = function(name, param)
local num, cmnd, payload = param:match('^([0-9]+)%s+(%w+)%s*(.*)$')
if num and cmnd then
if techage.not_protected(num, name) then
local resp = techage.send_single("0", num, cmnd, payload)
if type(resp) == "string" then
return true, resp
else
return true, dump(resp)
end
else
return false, "Destination block is protected"
end
end
return false, "Syntax: /ta_send <number> <command> [<data>]"
end
})
minetest.register_chatcommand("expoints", {
params = "<name> [<points>]",
description = "Get or set experience points of a player (server priv required)",
privs = {
server = true
},
func = function(name, param)
local player_name, points = param:match("^(%S+)%s*(%d*)$")
if player_name then
local player = minetest.get_player_by_name(player_name)
if player then
if points and points ~= "" then
if techage.set_expoints(player, tonumber(points)) then
return true, "The player "..player_name.." now has "..points.." experience points."
end
else
points = techage.get_expoints(player)
return true, "The player "..player_name.." has "..points.." experience points."
end
else
return false, "Unknown player "..player_name
end
end
return false, "Syntax error! Syntax: /expoints <name> [<points>]"
end
})
minetest.register_chatcommand("my_expoints", {
description = "Show your own experience points",
func = function(name, param)
local player = minetest.get_player_by_name(name)
if player then
local points = techage.get_expoints(player)
if points then
return true, "You have "..points.." experience points."
end
end
end
})
@@ -0,0 +1,51 @@
local OwnerCache = {}
local function is_owner(pos, meta)
local owner = meta:get_string("owner")
local key = minetest.hash_node_position(pos)
if OwnerCache[key] ~= owner then
if not minetest.is_protected(pos, owner) then
OwnerCache[key] = owner
end
end
return OwnerCache[key] == owner
end
-- Smartshop pulling and pushing support
minetest.register_on_mods_loaded(function()
if minetest.get_modpath("smartshop") then
techage.register_node({
"smartshop:shop",
"smartshop:shop_full",
"smartshop:shop_empty",
"smartshop:shop_used",
"smartshop:storage",
"smartshop:storage_lacks_refill",
"smartshop:storage_has_send"
}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
return meta:get_inventory(), "main"
end
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})
end
end)
@@ -0,0 +1,118 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Configured inventory lib
Assuming the inventory has the name "conf"
Otherwise the name has to be provided as argument
]]--
local StackName = ... or "conf"
-- for lazy programmers
local M = minetest.get_meta
local inv_lib = {}
function inv_lib.preassigned_stacks(pos, xsize, ysize)
local inv = M(pos):get_inventory()
local tbl = {}
for idx = 1, xsize * ysize do
local item_name = inv:get_stack(StackName, idx):get_name()
if item_name ~= "" then
local x = (idx - 1) % xsize
local y = math.floor((idx - 1) / xsize)
tbl[#tbl+1] = "item_image["..x..","..y..";1,1;"..item_name.."]"
end
end
return table.concat(tbl, "")
end
function inv_lib.item_filter(pos, size)
local inv = M(pos):get_inventory()
local filter = {}
for idx = 1, size do
local item_name = inv:get_stack(StackName, idx):get_name()
if item_name == "" then item_name = "unconfigured" end
if not filter[item_name] then
filter[item_name] = {}
end
table.insert(filter[item_name], idx)
end
return filter
end
function inv_lib.allow_conf_inv_put(pos, listname, index, stack, player)
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
if list[index]:get_count() == 0 then
stack:set_count(1)
inv:set_stack(listname, index, stack)
return 0
end
return 0
end
function inv_lib.allow_conf_inv_take(pos, listname, index, stack, player)
local inv = M(pos):get_inventory()
inv:set_stack(listname, index, nil)
return 0
end
function inv_lib.allow_conf_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = minetest.get_meta(pos):get_inventory()
local stack = inv:get_stack(to_list, to_index)
if stack:get_count() == 0 then
return 1
else
return 0
end
end
function inv_lib.put_items(pos, inv, listname, item, stacks, idx)
local name = item:get_name()
local count = item:get_count()
for _, i in ipairs(stacks or {}) do
if not idx or idx == i then
local stack = inv:get_stack(listname, i)
local leftover = stack:add_item({name = name, count = count})
count = leftover:get_count()
inv:set_stack(listname, i, stack)
if count == 0 then
return true
end
end
end
if count > 0 then
return ItemStack({name = name, count = count})
end
return false
end
function inv_lib.take_item(pos, inv, listname, num, stacks)
local mem = techage.get_mem(pos)
mem.ta_startpos = mem.ta_startpos or 1
local size = #(stacks or {})
for i = 1, size do
local idx = stacks[((i + mem.ta_startpos) % size) + 1]
local stack = inv:get_stack(listname, idx)
local taken = stack:take_item(num)
if taken:get_count() > 0 then
inv:set_stack(listname, idx, stack)
mem.ta_startpos = mem.ta_startpos + i
return taken
end
end
end
return inv_lib
@@ -0,0 +1,71 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Count techage commands player related
]]--
local PlayerName
local PlayerPoints = {}
local LastPlayerPoints = {}
local S = techage.S
local MAX_POINTS = tonumber(minetest.settings:get("techage_command_limit")) or 1200
function techage.counting_start(player_name)
PlayerName = player_name
PlayerPoints[PlayerName] = PlayerPoints[PlayerName] or 0
end
function techage.counting_stop()
PlayerName = nil
end
function techage.counting_hit()
if PlayerName then
PlayerPoints[PlayerName] = PlayerPoints[PlayerName] + 1
end
end
function techage.counting_add(player_name, points)
PlayerPoints[player_name] = (PlayerPoints[player_name] or 0) + points
end
local function output()
for name, val in pairs(PlayerPoints) do
if val > MAX_POINTS then
local obj = minetest.get_player_by_name(name)
if obj then
minetest.chat_send_player(name,
S("[techage] The limit for 'number of commands per minute' has been exceeded.") ..
" " .. string.format(MAX_POINTS .. " " .. S("is allowed. Current value is") .. " " .. val));
minetest.log("action", "[techage] " .. name ..
" exceeds the limit for commands per minute. value = " .. val)
local factor = 100 / (obj:get_armor_groups().fleshy or 100)
obj:punch(obj, 1.0, {full_punch_interval=1.0, damage_groups = {fleshy=factor * 5}})
end
end
end
LastPlayerPoints = table.copy(PlayerPoints)
PlayerPoints = {}
minetest.after(60, output)
end
minetest.after(60, output)
minetest.register_chatcommand("ta_limit", {
description = "Get your current techage command limit value",
func = function(name)
local num = LastPlayerPoints[name] or 0
return true, S("Your current value is") .. " " .. num .. " " .. S("per minute") .. ". " ..
MAX_POINTS .. " " .. S("is allowed")
end
})
@@ -0,0 +1,44 @@
--[[
TechAge
=======
Copyright (C) 2019-2025 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Credit-based CPU time distributing
]]--
local Credits = {}
local TimeSpent = {}
local S = techage.S
techage.credits = {}
function techage.credits.before_action(playername)
local t = minetest.get_us_time()
local dt = math.floor(minetest.get_us_time() - t)
TimeSpent[playername] = (TimeSpent[playername] or 0) + dt
return res
end
function techage.credits.after_action(playername)
local t = minetest.get_us_time()
local dt = math.floor(minetest.get_us_time() - t)
TimeSpent[playername] = (TimeSpent[playername] or 0) + dt
return res
end
local function evaluation()
Credits = {}
for playername, time in pairs(TimeSpent) do
Credits[playername] = time / 1000000 -- convert to seconds
end
TimeSpent = {}
minetest.after(1, evaluation)
end
minetest.after(1, evaluation)
@@ -0,0 +1,117 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2020 Thomas S.
AGPL v3
See LICENSE.txt for more information
Fake Player
]]--
-- Map method names to their return values
local methods = {
get_pos = { x = 0, y = 0, z = 0 },
set_pos = nil,
moveto = nil,
punch = nil,
right_click = nil,
get_hp = 20,
set_hp = nil,
get_inventory = nil,
get_wield_list = "",
get_wield_index = 0,
get_wielded_item = ItemStack(),
set_wielded_item = true,
set_armor_groups = nil,
get_armor_groups = {},
set_animation = nil,
get_animation = {},
set_animation_frame_speed = nil,
set_attach = nil,
get_attach = nil,
set_detach = nil,
get_bone_position = {},
set_properties = nil,
get_properties = {},
is_player = false,
get_nametag_attributes = {},
set_nametag_attributes = nil,
get_player_name = "",
get_player_velocity = nil,
add_player_velocity = nil,
get_look_dir = vector.new(0, 0, 1),
get_look_vertical = 0,
get_look_horizontal = 0,
set_look_vertical = nil,
set_look_horizontal = nil,
get_look_pitch = 0,
get_look_yaw = 0,
set_look_pitch = nil,
set_look_yaw = nil,
get_breath = 10,
set_breath = nil,
set_fov = nil,
get_fov = 0,
set_attribute = nil,
get_attribute = nil,
get_meta = nil,
set_inventory_formspec = nil,
get_inventory_formspec = "",
set_formspec_prepend = nil,
get_formspec_prepend = "",
get_player_control = {},
get_player_control_bits = 0,
set_physics_override = nil,
get_physics_override = {},
hud_add = 0,
hud_remove = nil,
hud_change = nil,
hud_get = {},
hud_set_flags = nil,
hud_get_flags = {},
hud_set_hotbar_itemcount = nil,
hud_get_hotbar_itemcount = 8,
hud_set_hotbar_image = nil,
hud_get_hotbar_image = "",
hud_set_hotbar_selected_image = nil,
hud_get_hotbar_selected_image = "",
set_sky = nil,
get_sky = {},
get_sky_color = {},
set_sun = nil,
get_sun = {},
set_moon = nil,
get_moon = {},
set_stars = nil,
get_stars = {},
set_clouds = nil,
get_clouds = {},
override_day_night_ratio = nil,
get_day_night_ratio = nil,
set_local_animation = nil,
get_local_animation = {},
set_eye_offset = nil,
get_eye_offset = {},
send_mapblock = nil,
}
techage.Fake_player = {}
techage.Fake_player.__index = techage.Fake_player
function techage.Fake_player:new()
local fake_player = {}
setmetatable(fake_player, techage.Fake_player)
return fake_player
end
for method_name, return_value in pairs(methods) do
techage.Fake_player[method_name] = function(self, ...)
return return_value
end
end
@@ -0,0 +1,150 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Firebox basic functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
techage.firebox = {}
techage.firebox.Burntime = {
["techage:charcoal"] = 1, -- will be replaced by burntime
["default:coal_lump"] = 1,
["default:coalblock"] = 1,
["techage:oil_source"] = 1,
["techage:gas"] = 1,
["techage:gasoline"] = 1,
["techage:naphtha"] = 1,
["techage:fueloil"] = 1,
}
techage.firebox.ValidOilFuels = {
["techage:gasoline"] = 1, -- category
["techage:naphtha"] = 2,
["techage:fueloil"] = 3,
["techage:oil_source"] = 4,
}
local function determine_burntimes()
for k,_ in pairs(techage.firebox.Burntime)do
local fuel,_ = minetest.get_craft_result({method = "fuel", width = 1, items = {k}})
techage.firebox.Burntime[k] = fuel.time
end
end
minetest.register_on_mods_loaded(determine_burntimes)
function techage.firebox.formspec(nvm)
local fuel_percent = 0
if nvm.running then
fuel_percent = ((nvm.burn_cycles or 1) * 100) / (nvm.burn_cycles_total or 1)
end
return "size[8,6]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[3,-0.1;"..minetest.colorize( "#000000", S("Firebox")).."]"..
"list[current_name;fuel;3,1;1,1;]"..
"image[4,1;1,1;default_furnace_fire_bg.png^[lowpart:"..
fuel_percent..":default_furnace_fire_fg.png]"..
"list[current_player;main;0,2.3;8,4;]"..
"listring[current_name;fuel]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 2.3)
end
function techage.firebox.can_dig(pos, player)
local inv = M(pos):get_inventory()
return inv:is_empty("fuel")
end
function techage.firebox.allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if techage.firebox.Burntime[stack:get_name()] then
return stack:get_count()
end
return 0
end
function techage.firebox.allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
function techage.firebox.on_rightclick(pos, node, clicker)
local nvm = techage.get_nvm(pos)
techage.set_activeformspec(pos, clicker)
M(pos):set_string("formspec", techage.firebox.formspec(nvm))
end
function techage.firebox.swap_node(pos, name)
local node = techage.get_node_lvm(pos)
if node.name == name then
return
end
node.name = name
minetest.swap_node(pos, node)
end
function techage.firebox.get_fuel(pos)
local inv = M(pos):get_inventory()
local items = inv:get_stack("fuel", 1)
if items:get_count() > 0 then
local taken = items:take_item(1)
inv:set_stack("fuel", 1, items)
return taken
end
end
function techage.firebox.has_fuel(pos)
local inv = M(pos):get_inventory()
local items = inv:get_stack("fuel", 1)
return items:get_count() > 0
end
function techage.firebox.is_free_position(pos, player_name)
local pos2 = techage.get_pos(pos, 'F')
if minetest.is_protected(pos2, player_name) then
minetest.chat_send_player(player_name, S("[TA] Area is protected!"))
return false
end
local node = techage.get_node_lvm(pos2)
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to then
minetest.chat_send_player(player_name, S("[TA] Not enough space!"))
return false
end
return true
end
function techage.firebox.set_firehole(pos, on)
local param2 = techage.get_node_lvm(pos).param2
local pos2 = techage.get_pos(pos, 'F')
if on == true then
minetest.swap_node(pos2, {name="techage:coalfirehole_on", param2 = param2})
elseif on == false then
minetest.swap_node(pos2, {name="techage:coalfirehole", param2 = param2})
else
local node = techage.get_node_lvm(pos2)
if node.name == "techage:coalfirehole" or node.name == "techage:coalfirehole_on" then
minetest.swap_node(pos2, {name="air"})
end
end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,273 @@
--[[
TechAge
=======
Copyright (C) 2020-2025 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Block fly/move library II
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = techage.S
local fly1 = techage.flylib
local flylib2 = {}
local MAX_SPEED = 8
local DelayedJobs = {}
local function call_after(func, ...)
DelayedJobs[#DelayedJobs + 1] = {func = func, args = {...}}
end
local function run_delayed_jobs()
for idx = 1, #DelayedJobs do
local job = DelayedJobs[idx]
job.func(unpack(job.args))
end
DelayedJobs = {}
end
local function slots(slot)
return function(slot, i)
if slot == 0 then
i = i + 1
if i <= 16 then
return i, i -- variabel, result
end
else
i = i + 1
if i == 1 then
return slot,slot
end
end
end, tonumber(slot), 0 -- param, start value
end
local function destinations(nvm, dest_pos, slot)
if slot == 0 then
local tbl = {dest_pos}
-- Calculate dest-pos for all nodes
local base_pos = nvm.lNodes[1].base_pos
for idx = 2,16 do
if nvm.lNodes[idx] then
tbl[idx] = vector.add(vector.subtract(nvm.lNodes[idx].base_pos, base_pos), dest_pos)
end
end
return tbl
end
return {[slot] = dest_pos}
end
local function distance(v)
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
end
local function remove_node(pos, node)
if minecart.is_cart(node.name) then
return minecart.remove_cart(pos)
else
local metadata = M(pos):to_table()
minetest.remove_node(pos)
return metadata
end
end
local function place_node(dest_pos, node, metadata)
local name = techage.get_node_lvm(dest_pos).name
local ndef = minetest.registered_nodes[name]
if minecart.is_cart(node.name) then
minecart.place_and_start_cart(dest_pos, {name = node.name, param2 = node.param2}, metadata)
return true
elseif ndef.buildable_to then
minetest.set_node(dest_pos, {name = node.name, param2 = node.param2})
local meta = M(dest_pos)
meta:from_table(metadata or {})
return true
end
end
local function get_node_from_inventory(inv, idx)
local stack = inv:get_stack("main", idx)
local param2 = stack:get_meta():get_int("param2")
inv:set_stack("main", idx, nil)
return {name = stack:get_name(), param2 = param2}
end
-- Move node from 'curr_pos' to 'dest_pos'
-- * pos is the controller block position
-- * max_speed is the maximum speed
-- * height is move block height as value between 0 and 1 and used to calculate the offset
-- for the attached object (player).
-- * yoffset is an additional offset for non-player objects
-- * idx is the node related movecontroller inventory slot (optional)
local function move_node(pos, curr_pos, dest_pos, max_speed, height, yoffset, idx)
local dir = fly1.determine_dir(curr_pos, dest_pos)
local obj, is_cart = fly1.node_to_entity(pos, curr_pos, dest_pos, idx)
if obj then
if is_cart then
fly1.attach_objects(curr_pos, 0, obj, yoffset, {x = 0, y = -0.4, z = 0})
else
local offs = {x=0, y=height or 1, z=0}
fly1.attach_objects(curr_pos, offs, obj, yoffset)
if dir.y == 0 then
if (dir.x ~= 0 and dir.z == 0) or (dir.x == 0 and dir.z ~= 0) then
fly1.attach_objects(curr_pos, dir, obj, yoffset)
end
end
end
local self = obj:get_luaentity()
self.path_idx = 2
self.lmove = {vector.subtract(dest_pos, curr_pos)}
self.max_speed = max_speed
self.yoffs = yoffset
self.idx = idx
fly1.move_entity(obj, dest_pos, dir)
return true
else
return false
end
end
-- Place node to start position, if necessary
local function correct_node(pos, idx, node, dest_pos)
local cnode = techage.get_node_lvm(node.curr_pos)
local inv = M(pos):get_inventory()
if inv:get_stack("main", idx):get_count() == 1 then
local ndef = minetest.registered_nodes[cnode.name]
if ndef.buildable_to then
local inode = get_node_from_inventory(inv, idx)
return place_node(node.curr_pos, inode)
else
local inode = get_node_from_inventory(inv, idx)
minetest.swap_node(node.curr_pos, inode)
call_after(minetest.set_node, node.curr_pos, {name = cnode.name, param2 = cnode.param2})
return true
end
end
if cnode.name == node.name then
return true
end
local dest_name = techage.get_node_lvm(dest_pos).name
if dest_name == node.name then
local metadata = remove_node(dest_pos, node)
return place_node(node.curr_pos, node, metadata)
end
return false
end
local function is_simple_node(node)
if not minecart.is_rail(pos, node.name) then
local ndef = minetest.registered_nodes[node.name]
return node.name ~= "air" and techage.can_dig_node(node.name, ndef) or minecart.is_cart(node.name)
end
end
function flylib2.get_pos(payload)
if payload then
local x,y,z = unpack(string.split(payload, ",", false, 2))
if z then
x = tonumber(x) or 0
y = tonumber(y) or 0
z = tonumber(z) or 0
return {x = x, y = y, z = z}
end
end
end
function flylib2.get_node_base_positions(lNodes)
local tbl = {}
for idx, item in ipairs(lNodes) do
tbl[idx] = item.base_pos
end
return tbl
end
function flylib2.get_nodes(pos_list)
local tbl = {}
for idx,pos in ipairs(pos_list) do
local node = techage.get_node_lvm(pos)
if is_simple_node(node) then
tbl[idx] = {base_pos = pos, curr_pos = pos, name = node.name, param2 = node.param2}
else
tbl[idx] = {base_pos = pos, curr_pos = pos, name = "techage:invalid_node", param2 = 0}
end
end
return tbl
end
function flylib2.valid_distance(nvm, destpos, slot, max_dist)
slot = slot > 0 and slot or 1
local basepos = nvm.lNodes[slot] and nvm.lNodes[slot].base_pos
if basepos and destpos then
local v = vector.subtract(destpos, basepos)
return distance(v) <= max_dist
end
return false
end
-- pos = movecontroller position
-- slot = inventory slot index (1..16) or 0 for all slots
function flylib2.reset_nodes(pos, nvm, slot)
local meta = M(pos)
if nvm.running then return false end
local max_speed = meta:contains("max_speed") and meta:get_float("max_speed") or MAX_SPEED
local height = techage.in_range(meta:contains("height") and meta:get_float("height") or 1, 0, 1) -- platform height
local yoffs = meta:get_float("offset") -- for non-player objects
for idx in slots(slot) do
nvm.lNodes = nvm.lNodes or {}
local node = nvm.lNodes[idx]
if node and node.curr_pos and node.curr_pos ~= node.base_pos then
if correct_node(pos, idx, node, node.base_pos) then
move_node(pos, node.curr_pos, node.base_pos, max_speed, height, yoffs, idx)
node.curr_pos = node.base_pos
end
end
end
run_delayed_jobs()
return true
end
-- pos = movecontroller position
-- slot = inventory slot index (1..16) or 0 for all slots
function flylib2.move_nodes(pos, nvm, dest_pos, slot)
local meta = M(pos)
if nvm.running then return false end
local max_speed = meta:contains("max_speed") and meta:get_float("max_speed") or MAX_SPEED
local height = techage.in_range(meta:contains("height") and meta:get_float("height") or 1, 0, 1) -- platform height
local yoffs = meta:get_float("offset") -- for non-player objects
local dests = destinations(nvm, dest_pos, slot)
for idx in slots(slot) do
local node = nvm.lNodes[idx]
local dest_pos = dests[idx]
if node and node.curr_pos and node.curr_pos ~= dest_pos then
if correct_node(pos, idx, node, dest_pos) then
move_node(pos, node.curr_pos, dest_pos, max_speed, height, yoffs, idx)
node.curr_pos = dest_pos
end
end
end
run_delayed_jobs()
return true
end
minetest.register_craftitem("techage:invalid_node", {
description = S("Invalid node"),
inventory_image = "techage_invalid_node.png",
groups = {not_in_creative_inventory = 1},
})
techage.flylib2 = flylib2
@@ -0,0 +1,52 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Keep only one formspec active per player
]]--
local P2S = minetest.pos_to_string
local ActiveFormspecs = {}
local ActivePlayer = {}
function techage.is_activeformspec(pos)
return ActiveFormspecs[P2S(pos)]
end
function techage.set_activeformspec(pos, player)
local name = player and player:get_player_name()
if name then
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
end
ActivePlayer[name] = P2S(pos)
ActiveFormspecs[P2S(pos)] = true
end
end
function techage.reset_activeformspec(pos, player)
local name = player and player:get_player_name()
if name then
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
ActivePlayer[name] = nil
end
end
end
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
ActivePlayer[name] = nil
end
end)
@@ -0,0 +1,197 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Oil fuel burning lib
]]--
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local S = techage.S
local Pipe = techage.LiquidPipe
local liquid = networks.liquid
local ValidOilFuels = techage.firebox.ValidOilFuels
local Burntime = techage.firebox.Burntime
techage.fuel = {}
local CAPACITY = techage.volume_barrel * 5
local BLOCKING_TIME = 0.3 -- 300ms
techage.fuel.CAPACITY = CAPACITY
-- fuel burning categories (equal or better than...)
techage.fuel.BT_BITUMEN = 5
techage.fuel.BT_OIL = 4
techage.fuel.BT_FUELOIL = 3
techage.fuel.BT_NAPHTHA = 2
techage.fuel.BT_GASOLINE = 1
function techage.fuel.fuel_container(x, y, nvm)
local itemname = ""
if nvm.liquid and nvm.liquid.name and nvm.liquid.amount and nvm.liquid.amount > 0 then
itemname = nvm.liquid.name.." "..nvm.liquid.amount
end
local fuel_percent = 0
if nvm.running or techage.is_running(nvm) then
fuel_percent = ((nvm.burn_cycles or 1) * 100) / (nvm.burn_cycles_total or 1)
end
return "container["..x..","..y.."]"..
"box[0,0;1.05,2.1;#000000]"..
"image[0.1,0.1;1,1;default_furnace_fire_bg.png^[lowpart:"..
fuel_percent..":default_furnace_fire_fg.png]"..
techage.item_image(0.1, 1.1, itemname)..
"container_end[]"
end
local function help(x, y)
local tooltip = S("To add fuel punch\nthis block\nwith a fuel container")
return "label["..x..","..y..";"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.fuel.formspec(nvm)
local title = S("Fuel Menu")
return "size[4,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;3.8,0.5;#c6e8ff]"..
"label[1,-0.1;"..minetest.colorize("#000000", title).."]"..
help(3.4, -0.1)..
techage.fuel.fuel_container(1.5, 1, nvm)
end
function techage.fuel.can_dig(pos, player)
if not player or minetest.is_protected(pos, player:get_player_name()) then
return false
end
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local inv = M(pos):get_inventory()
return not inv or inv:is_empty("fuel") and nvm.liquid.amount == 0
end
function techage.fuel.on_rightclick(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
-- name is the fuel item name
function techage.fuel.burntime(name)
if ValidOilFuels[name] then
return Burntime[name] or 0.01 -- not zero !
end
return 0.01 -- not zero !
end
-- equal or better than the given category (see 'techage.fuel.BT_BITUMEN,...')
function techage.fuel.valid_fuel(name, category)
return ValidOilFuels[name] and ValidOilFuels[name] <= category
end
function techage.fuel.on_punch(pos, node, puncher, pointed_thing)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
local new_item = techage.liquid.fill_on_punch(nvm, wielded_item, item_count, puncher)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
return
end
local ldef = techage.liquid.get_liquid_def(wielded_item)
if ldef and ValidOilFuels[ldef.inv_item] then
local lqd = (minetest.registered_nodes[node.name] or {}).liquid
if not lqd.fuel_cat or ValidOilFuels[ldef.inv_item] <= lqd.fuel_cat then
local new_item = techage.liquid.empty_on_punch(pos, nvm, wielded_item, item_count)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
end
end
end
end
function techage.fuel.get_fuel(nvm)
if nvm.liquid and nvm.liquid.name and nvm.liquid.amount then
if nvm.liquid.amount > 0 then
nvm.liquid.amount = nvm.liquid.amount - 1
return nvm.liquid.name
end
nvm.liquid.name = nil
end
return nil
end
function techage.fuel.has_fuel(nvm)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
return nvm.liquid.amount > 0
end
function techage.fuel.get_fuel_amount(nvm)
if nvm.liquid and nvm.liquid.amount then
return nvm.liquid.amount
end
return 0
end
function techage.fuel.get_liquid_table(valid_fuel, capacity, start_firebox)
return {
capa = capacity,
fuel_cat = valid_fuel,
peek = function(pos)
local nvm = techage.get_nvm(pos)
return liquid.srv_peek(nvm)
end,
put = function(pos, indir, name, amount)
if techage.fuel.valid_fuel(name, valid_fuel) then
local nvm = techage.get_nvm(pos)
local res = liquid.srv_put(nvm, name, amount, capacity)
start_firebox(pos, nvm)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return res
end
return amount
end,
take = function(pos, indir, name, amount)
local nvm = techage.get_nvm(pos)
amount, name = liquid.srv_take(nvm, name, amount)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return amount, name
end,
untake = function(pos, indir, name, amount)
local nvm = techage.get_nvm(pos)
local leftover = liquid.srv_put(nvm, name, amount, capacity)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return leftover
end
}
end
@@ -0,0 +1,118 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Gravel Sieve basis functions
]]--
-- Increase the probability over the natural occurrence
local PROBABILITY_FACTOR = 2
-- Ore probability table (1/n)
local ore_probability = {
}
local ProbabilityCorrections = {
["default:tin_lump"] = 0.3, -- extensively used
["default:coal_lump"] = 0.2, -- extensively used
["default:iron_lump"] = 0.5, -- extensively used
["techage:baborium_lump"] = 99999, -- mining required
}
local function wherein(item)
if type(item.wherein) == "table" then
for _,v in ipairs(item.wherein) do
if v == "default:stone" then
return true
end
if v == "everness:mineral_cave_stone" then
return true
end
end
return false
else
return item.wherein == "default:stone"
end
end
-- collect all registered ores and calculate the probability
local function add_ores()
for _,item in pairs(minetest.registered_ores) do
if not ore_probability[item.ore] and minetest.registered_nodes[item.ore] then
local drop = minetest.registered_nodes[item.ore].drop
if type(drop) == "string"
and drop ~= item.ore
and drop ~= ""
and item.ore_type == "scatter"
and wherein(item)
and item.clust_scarcity ~= nil and item.clust_scarcity > 0
and item.clust_num_ores ~= nil and item.clust_num_ores > 0
and item.y_max ~= nil and item.y_min ~= nil then
local factor = 0.5
if item.y_max < -250 then
factor = -250 / item.y_max
end
local probability = (techage.ore_rarity / PROBABILITY_FACTOR) * item.clust_scarcity /
(item.clust_num_ores * factor)
-- lower value means higher probability
ore_probability[drop] = math.min(ore_probability[drop] or 100000, probability)
end
end
end
-- some corrections
for key, correction in pairs(ProbabilityCorrections) do
if ore_probability[key] then
ore_probability[key] = ore_probability[key] * correction
-- consider upper and lower level
ore_probability[key] = techage.in_range(ore_probability[key], 10, 100000)
end
end
local overall_probability = 0.0
for name,probability in pairs(ore_probability) do
minetest.log("info", string.format("[techage] %-32s %u", name, probability))
overall_probability = overall_probability + 1.0/probability
end
minetest.log("info", string.format("[techage] Overall probability %g", overall_probability))
end
minetest.register_on_mods_loaded(add_ores)
--
-- Change the probability of ores or register new ores for sieving
--
function techage.register_ore_for_gravelsieve(ore_name, probability)
ore_probability[ore_name] = probability
end
-- determine ore based on the calculated probability
function techage.gravelsieve_get_random_gravel_ore()
for ore, probability in pairs(ore_probability) do
if math.random(probability) == 1 then
return ItemStack(ore)
end
end
if math.random(2) == 1 then
return ItemStack("default:gravel")
else
return ItemStack("techage:sieved_gravel")
end
end
function techage.gravelsieve_get_random_basalt_ore()
if math.random(techage.basalt_sieve_chance) == 1 then
return ItemStack("default:coal_lump")
elseif math.random(techage.basalt_sieve_chance) == 1 then
return ItemStack("default:iron_lump")
elseif math.random(2) == 1 then
return ItemStack("techage:basalt_gravel")
else
return ItemStack("techage:sieved_basalt_gravel")
end
end
@@ -0,0 +1,240 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
For chests and tanks with hyperloop support
]]--
-- for lazy programmers
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local N = techage.get_node_lvm
local S = techage.S
-- Will be initialized when mods are loaded
local Stations = nil
local Tube = nil
local HYPERLOOP = nil
techage.hyperloop = {}
--[[
tStations["(x,y,z)"] = {
conn = {dir = "(200,0,20)", ...},
name = <node_type>, -- chest/tank
owner = "singleplayer",
conn_name = <own name>,
single = true/nil,
}
]]--
minetest.register_on_mods_loaded(function()
if minetest.global_exists("hyperloop") then
Stations = hyperloop.Stations
Tube = hyperloop.Tube
HYPERLOOP = true
Tube:add_secondary_node_names({"techage:ta5_hl_chest", "techage:ta5_hl_tank"})
end
end)
local function get_remote_pos(pos, rmt_name)
local owner = M(pos):get_string("owner")
for key,item in pairs(Stations:get_node_table(pos)) do
if item.owner == owner and item.conn_name == rmt_name then
return S2P(key)
end
end
end
local function get_free_server_list(pos, owner)
if Stations and Stations.get_node_table then
local tbl = {M(pos):get_string("remote_name")}
for key,item in pairs(Stations:get_node_table(pos) or {}) do
if item.single and item.owner == owner then
if M(pos):get_string("node_type") == M(S2P(key)):get_string("node_type") then
tbl[#tbl+1] = item.conn_name
end
end
end
tbl[#tbl+1] = ""
return tbl
end
return {}
end
local function on_lose_connection(pos, node_type)
local name = techage.get_node_lvm(pos).name
local ndef = minetest.registered_nodes[name]
if ndef and ndef.on_lose_connection then
ndef.on_lose_connection(pos, node_type)
end
end
local function on_dropdown(pos)
if pos then
local owner = M(pos):get_string("owner")
return table.concat(get_free_server_list(pos, owner), ",") or ""
end
return ""
end
local function update_node_data(pos, state, conn_name, remote_name, rmt_pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
if state == "server_connected" then
Stations:update(pos, {conn_name=conn_name, single="nil"})
meta:set_string("status", "server")
meta:set_string("conn_name", conn_name)
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("connected to") .. " " .. P2S(rmt_pos))
nvm.rmt_pos = rmt_pos
elseif state == "client_connected" then
Stations:update(pos, {conn_name="nil", single="nil"})
meta:set_string("status", "client")
meta:set_string("conn_name", "")
meta:set_string("remote_name", remote_name)
meta:set_string("conn_status", S("connected to") .. " " .. P2S(rmt_pos))
nvm.rmt_pos = rmt_pos
elseif state == "server_not_connected" then
Stations:update(pos, {conn_name=conn_name, single=true})
meta:set_string("status", "server")
meta:set_string("conn_name", conn_name)
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("not connected"))
nvm.rmt_pos = nil
on_lose_connection(pos, "server")
elseif state == "client_not_connected" then
Stations:update(pos, {conn_name="nil", single=nil})
meta:set_string("status", "not connected")
meta:set_string("conn_name", "")
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("not connected"))
nvm.rmt_pos = nil
on_lose_connection(pos, "client")
end
end
techage.hyperloop.SUBMENU = {
{
type = "label",
label = S("Enter a block name or select an existing one"),
tooltip = "",
name = "l1",
},
{
type = "ascii",
name = "conn_name",
label = S("Block name"),
tooltip = S("Connection name for this block"),
default = "",
},
{
type = "dropdown",
choices = "",
on_dropdown = on_dropdown,
name = "remote_name",
label = S("Remote name"),
tooltip = S("Connection name of the remote block"),
},
}
function techage.hyperloop.is_client(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):get_string("status") == "client" then
return true
end
end
end
end
function techage.hyperloop.is_server(pos)
if HYPERLOOP then
if M(pos):get_string("status") == "server" then
return true
end
end
end
function techage.hyperloop.is_paired(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):get_string("status") ~= "not connected" then
return true
end
end
end
end
function techage.hyperloop.remote_pos(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):contains("remote_name") then
return nvm.rmt_pos or pos
end
end
end
return pos
end
function techage.hyperloop.after_place_node(pos, placer, node_type)
if HYPERLOOP then
Stations:set(pos, node_type, {owner=placer:get_player_name()})
M(pos):set_string("node_type", node_type)
Tube:after_place_node(pos)
end
end
function techage.hyperloop.after_dig_node(pos, oldnode, oldmetadata, digger)
if HYPERLOOP then
local conn_name = oldmetadata.fields.conn_name
local remote_name = oldmetadata.fields.remote_name
local loc_pos, rmt_pos = pos, techage.get_nvm(pos).rmt_pos
-- Close connections
if remote_name and rmt_pos then -- Connected client
update_node_data(rmt_pos, "server_not_connected", remote_name, "")
elseif conn_name and rmt_pos then -- Connected server
update_node_data(rmt_pos, "client_not_connected", "", conn_name)
end
Tube:after_dig_node(pos)
Stations:delete(pos)
end
end
function techage.hyperloop.after_formspec(pos, fields)
if HYPERLOOP and fields.save or fields.key_enter_field then
local meta = M(pos)
local conn_name = meta:get_string("conn_name")
local remote_name = meta:get_string("remote_name")
local status = meta:contains("status") and meta:get_string("status") or "not connected"
local loc_pos, rmt_pos = pos, techage.get_nvm(pos).rmt_pos
if status == "not connected" then
if fields.remote_name ~= "" then -- Client
local rmt_pos = get_remote_pos(pos, fields.remote_name)
if rmt_pos then
update_node_data(loc_pos, "client_connected", "", fields.remote_name, rmt_pos)
update_node_data(rmt_pos, "server_connected", fields.remote_name, "", loc_pos)
end
elseif fields.conn_name ~= "" then -- Server
update_node_data(loc_pos, "server_not_connected", fields.conn_name, "")
end
end
end
end
@@ -0,0 +1,146 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Laser basis functions
]]--
local Entities = {}
local SIZES = {1, 2, 3, 6, 12, 24, 48} -- for laser entities
local GAP_MIN = 1 -- between 2 blocks
local GAP_MAX = 2 * 48 -- between 2 blocks
-- Return the area (pos1,pos2) for a destination node
local function get_pos_range(pos, dir)
local pos1 = vector.add(pos, vector.multiply(dir, GAP_MIN + 1)) -- min
local pos2 = vector.add(pos, vector.multiply(dir, GAP_MAX + 1)) -- max
return pos1, pos2
end
-- Return first pos after start pos and the destination pos
local function get_positions(pos, mem, dir)
local pos1 = vector.add(pos, dir) -- start pos
local _, pos2 = get_pos_range(pos, dir) -- last pos
local _, pos3 = minetest.line_of_sight(pos1, pos2)
pos3 = pos3 or pos2 -- destination node pos
if not mem.peer_node_pos or not vector.equals(pos3, mem.peer_node_pos) then
mem.peer_node_pos = pos3
local dist = vector.distance(pos1, pos3)
if dist > GAP_MIN and dist <= GAP_MAX then
return true, pos1, pos3 -- new values
else
return false -- invalid values
end
end
return true -- no new values
end
-- 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)
for _, size in ipairs(SIZES) do
if dist <= (size * 2) then
pos1 = vector.add (pos1, vector.multiply(dir, (size / 2) - 0.5))
pos2 = vector.subtract(pos2, vector.multiply(dir, (size / 2) + 0.5))
return size, pos1, pos2
end
end
end
local function del_laser(pos)
local key = minetest.hash_node_position(pos)
local items = Entities[key]
if items then
local laser1, laser2 = items[1], items[2]
laser1:remove()
laser2:remove()
Entities[key] = nil
end
return key
end
local function add_laser(pos, pos1, pos2, size, param2)
local key = del_laser(pos)
local laser1 = minetest.add_entity(pos1, "techage:laser" .. size)
if laser1 then
local yaw = math.pi / 2 * (param2 + 1)
laser1:set_rotation({x = 0, y = yaw, z = 0})
end
local laser2 = minetest.add_entity(pos2, "techage:laser" .. size)
if laser2 then
param2 = (param2 + 2) % 4 -- flip dir
local yaw = math.pi / 2 * (param2 + 1)
laser2:set_rotation({x = 0, y = yaw, z = 0})
end
Entities[key] = {laser1, laser2}
end
for _, size in ipairs(SIZES) do
minetest.register_entity("techage:laser" .. size, {
initial_properties = {
visual = "cube",
textures = {
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
},
use_texture_alpha = true,
physical = false,
collide_with_objects = false,
pointable = false,
static_save = false,
visual_size = {x = size, y = 0.05, z = 0.05},
glow = 14,
shaded = true,
},
})
end
-------------------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------------------
-- if force is not true, do not redraw the laser if nothing has changed
function techage.renew_laser(pos, force)
local mem = techage.get_mem(pos)
if force then
mem.peer_node_pos = nil
mem.param2 = nil
end
mem.param2 = mem.param2 or minetest.get_node(pos).param2
local dir = minetest.facedir_to_dir(mem.param2)
local res, pos1, pos2 = get_positions(pos, mem, dir)
if pos1 then
local size, pos3, pos4 = get_laser_length_and_pos(pos1, pos2, dir)
if size then
add_laser(pos, pos3, pos4, size, mem.param2)
return res, pos1, pos2
end
end
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
@@ -0,0 +1,25 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
For the transition from v0.26 to v1.0
]]--
function techage.register_node_for_v1_transition(nodenames, on_node_load)
minetest.register_lbm({
label = "[TechAge] V1 transition",
name = nodenames[1].."transition",
nodenames = nodenames,
run_at_every_load = false,
action = function(pos, node)
on_node_load(pos, node)
end
})
end
+690
View File
@@ -0,0 +1,690 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Helper functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
-- Input data to generate the Param2ToDir table
local Input = {
8,9,10,11, -- 1
16,17,18,19, -- 2
4,5,6,7, -- 3
12,13,14,15, -- 4
0,1,2,3, -- 5
20,21,22,23, -- 6
}
-- Input data to turn a "facedir" block to the right/left
local ROTATION = {
{5,14,11,16}, -- x+
{7,12,9,18}, -- x-
{0,1,2,3}, -- y+
{22,21,20,23}, -- y-
{6,15,8,17}, -- z+
{4,13,10,19}, -- z-
}
local FACEDIR_TO_ROT = {[0] =
{x=0.000000, y=0.000000, z=0.000000},
{x=0.000000, y=4.712389, z=0.000000},
{x=0.000000, y=3.141593, z=0.000000},
{x=0.000000, y=1.570796, z=0.000000},
{x=4.712389, y=0.000000, z=0.000000},
{x=3.141593, y=1.570796, z=1.570796},
{x=1.570796, y=4.712389, z=4.712389},
{x=3.141593, y=4.712389, z=4.712389},
{x=1.570796, y=0.000000, z=0.000000},
{x=0.000000, y=4.712389, z=1.570796},
{x=4.712389, y=1.570796, z=4.712389},
{x=0.000000, y=1.570796, z=4.712389},
{x=0.000000, y=0.000000, z=1.570796},
{x=4.712389, y=0.000000, z=1.570796},
{x=0.000000, y=3.141593, z=4.712389},
{x=1.570796, y=3.141593, z=4.712389},
{x=0.000000, y=0.000000, z=4.712389},
{x=1.570796, y=0.000000, z=4.712389},
{x=0.000000, y=3.141593, z=1.570796},
{x=4.712389, y=0.000000, z=4.712389},
{x=0.000000, y=0.000000, z=3.141593},
{x=0.000000, y=1.570796, z=3.141593},
{x=0.000000, y=3.141593, z=3.141593},
{x=0.000000, y=4.712389, z=3.141593},
}
local RotationViaYAxis = {}
for _,row in ipairs(ROTATION) do
for i = 1,4 do
local val = row[i]
local left = row[i == 1 and 4 or i - 1]
local right = row[i == 4 and 1 or i + 1]
RotationViaYAxis[val] = {left, right}
end
end
function techage.facedir_to_rotation(facedir)
return FACEDIR_TO_ROT[facedir] or FACEDIR_TO_ROT[0]
end
function techage.param2_turn_left(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[2]
end
function techage.param2_turn_right(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[1]
end
-- Roll a block in north direction (south is vice versa)
local RollNorth = {
{0,4,22,8},
{1,5,23,9},
{2,6,20,10},
{3,7,21,11},
{12,13,14,15},
{16,19,18,17},
}
-- Roll a block in east direction (west is vice versa)
local RollEast = {
{0,12,20,16},
{1,13,21,17},
{2,14,22,18},
{3,15,23,19},
{4,7,6,5},
{8,9,10,11},
}
-- Generate a table for all facedir and param2 values:
-- TurnUp[facedir][param2] = new_param2
local TurnUp = {[0] = {}, {}, {}, {}}
for i = 1,6 do
for j = 1,4 do
local idx = RollNorth[i][j]
TurnUp[0][idx] = RollNorth[i][j == 4 and 1 or j + 1] -- north
TurnUp[2][idx] = RollNorth[i][j == 1 and 4 or j - 1] -- south
idx = RollEast[i][j]
TurnUp[1][idx] = RollEast[i][j == 4 and 1 or j + 1] -- east
TurnUp[3][idx] = RollEast[i][j == 1 and 4 or j - 1] -- west
end
end
-- facedir is from the players (0..3)
-- param2 is from the node (0..23)
function techage.param2_turn_up(facedir, param2)
return TurnUp[facedir % 4][param2 % 24]
end
-------------------------------------------------------------------------------
-- Rotate nodes around the center
-------------------------------------------------------------------------------
function techage.positions_center(lpos)
local c = {x=0, y=0, z=0}
for _,v in ipairs(lpos) do
c = vector.add(c, v)
end
c = vector.divide(c, #lpos)
c = vector.round(c)
c.y = 0
return c
end
function techage.rotate_around_axis(v, c, turn)
local dx, dz = v.x - c.x, v.z - c.z
if turn == "l" then
return {
x = c.x - dz,
y = v.y,
z = c.z + dx,
}
elseif turn == "r" then
return {
x = c.x + dz,
y = v.y,
z = c.z - dx,
}
elseif turn == "" then
return v
else -- turn 180 degree
return {
x = c.x - dx,
y = v.y,
z = c.z - dz,
}
end
end
-- Function returns a list ẃith the new node positions
-- turn is one of "l", "r", "2l", "2r"
-- cpos is the center pos (optional)
function techage.rotate_around_center(nodes1, turn, cpos)
cpos = cpos or techage.positions_center(nodes1)
local nodes2 = {}
for _,pos in ipairs(nodes1) do
nodes2[#nodes2 + 1] = techage.rotate_around_axis(pos, cpos, turn)
end
return nodes2
end
-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------
-- allowed for digging
local SimpleNodes = {}
-- translation from param2 to dir (out of the node upwards)
local Param2Dir = {}
for idx,val in ipairs(Input) do
Param2Dir[val] = math.floor((idx - 1) / 4) + 1
end
-- used by lamps and power switches
function techage.determine_node_bottom_as_dir(node)
return tubelib2.Turn180Deg[Param2Dir[node.param2] or 1]
end
function techage.determine_node_top_as_dir(node)
return Param2Dir[node.param2] or 1
end
-- rotation rules (screwdriver) for wallmounted "facedir" nodes
function techage.rotate_wallmounted(param2)
local offs = math.floor(param2 / 4) * 4
local rot = ((param2 % 4) + 1) % 4
return offs + rot
end
-- Returns true if ver1 is newer or equal to ver2
function techage.newer_or_equal(ver1, ver2)
local t1 = {}
local t2 = {}
for i in string.gmatch(ver1, "%d+") do
table.insert(t1, tonumber(i))
end
for i in string.gmatch(ver2, "%d+") do
table.insert(t2, tonumber(i))
end
for i = 1, math.max(#t1, #t2) do
if t1[i] == nil then
t1[i] = 0
end
if t2[i] == nil then
t2[i] = 0
end
if t1[i] > t2[i] then
return true
elseif t1[i] < t2[i] then
return false
end
end
return true
end
function techage.in_range(val, min, max)
val = tonumber(val)
if val < min then return min end
if val > max then return max end
return val
end
function techage.one_of(val, selection)
for _,v in ipairs(selection) do
if val == v then return val end
end
return selection[1]
end
function techage.index(list, x)
for idx, v in pairs(list) do
if v == x then return idx end
end
return nil
end
function techage.in_list(list, x)
for idx, v in pairs(list) do
if v == x then return true end
end
return false
end
function techage.add_to_set(set, x)
if not techage.index(set, x) then
table.insert(set, x)
end
end
-- techage.tbl_filter({"a", "b", "c", "d"}, function(v, k, t) return v >= "c" end) --> {"c","d"}
techage.tbl_filter = function(t, filterIter)
local out = {}
for k, v in pairs(t) do
if filterIter(v, k, t) then out[k] = v end
end
return out
end
function techage.get_node_lvm(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
local data = vm:get_data()
local param2_data = vm:get_param2_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
local idx = area:indexp(pos)
if data[idx] and param2_data[idx] then
return {
name = minetest.get_name_from_content_id(data[idx]),
param2 = param2_data[idx]
}
end
return {name="ignore", param2=0}
end
function techage.is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
-- everness oill drilling fix
local GroundNodes = {}
if minetest.get_modpath("everness") then
minetest.register_on_mods_loaded(function()
for _, item in pairs(core.registered_ores) do
GroundNodes[item.ore] = true
if item.wherein then
GroundNodes[item.wherein] = true
end
end
end)
end
-- returns true, if node can be dug, otherwise false
function techage.can_dig_node(name, ndef)
if not ndef then return false end
if SimpleNodes[name] ~= nil then
return SimpleNodes[name]
end
if ndef.groups and ndef.groups.techage_door == 1 then
SimpleNodes[name] = true
return true
end
if name == "ignore" then
SimpleNodes[name] = false
return false
end
if name == "air" then
SimpleNodes[name] = true
return true
end
if ndef.buildable_to == true then
SimpleNodes[name] = true
return true
end
-- everness oill drilling fix
if minetest.get_modpath("everness") then
if GroundNodes[name] then
SimpleNodes[name] = true
return true
end
end
-- underch oil drilling fix
if ndef.groups['jit_shadow'] == 1 then
SimpleNodes[name] = true
return true
end
-- don't remove nodes with some intelligence or undiggable nodes
if ndef.drop == "" then
SimpleNodes[name] = false
return false
end
if ndef.diggable == false then
SimpleNodes[name] = false
return false
end
if ndef.after_dig_node then
SimpleNodes[name] = false
return false
end
-- add it to the white list
SimpleNodes[name] = true
return true
end
-- Simple nodes
function techage.register_simple_nodes(node_names, is_valid)
if is_valid == nil then is_valid = true end
for _,name in ipairs(node_names or {}) do
SimpleNodes[name] = is_valid
end
end
function techage.node_is_ore(name)
return Ores[name]
end
techage.dig_states = {
NOT_DIGGABLE = 1,
INV_FULL = 2,
DUG = 3
}
-- Digs a node like a player would by utilizing a fake player object.
-- add_to_inv(itemstacks) is a method that should try to add the dropped stacks to an appropriate inventory.
-- The node will only be dug, if add_to_inv(itemstacks) returns true.
function techage.dig_like_player(pos, fake_player, add_to_inv)
local node = techage.get_node_lvm(pos)
local ndef = minetest.registered_nodes[node.name]
if not ndef or ndef.diggable == false or (ndef.can_dig and not ndef.can_dig(pos, fake_player)) then
return techage.dig_states.NOT_DIGGABLE
end
local drop_as_strings = minetest.get_node_drops(node)
local drop_as_stacks = {}
for _,itemstring in ipairs(drop_as_strings) do
drop_as_stacks[#drop_as_stacks+1] = ItemStack(itemstring)
end
local meta = M(pos)
if ndef.preserve_metadata then
ndef.preserve_metadata(pos, node, meta, drop_as_stacks)
end
if add_to_inv(drop_as_stacks) then
local oldmeta = meta:to_table()
minetest.remove_node(pos)
if ndef.after_dig_node then
ndef.after_dig_node(pos, node, oldmeta, fake_player)
end
return techage.dig_states.DUG
end
return techage.dig_states.INV_FULL
end
local function handle_drop(drop)
-- To keep it simple, return only the item with the lowest rarity
if drop.items then
local rarity = 9999
local name
for idx,item in ipairs(drop.items) do
if item.rarity and item.rarity < rarity then
rarity = item.rarity
name = item.items[1] -- take always the first item
else
return item.items[1] -- take always the first item
end
end
return name
end
return false
end
-- returns the node name, if node can be dropped, otherwise nil
function techage.dropped_node(node, ndef)
if node.name == "air" then return end
--if ndef.buildable_to == true then return end
if not ndef.diggable then return end
if ndef.drop == "" then return end
if type(ndef.drop) == "table" then
return handle_drop(ndef.drop)
end
return ndef.drop or node.name
end
-- needed for windmill plants
local function determine_ocean_ids()
techage.OceanIdTbl = {}
for name, _ in pairs(minetest.registered_biomes) do
if string.find(name, "ocean") then
local id = minetest.get_biome_id(name)
--print(id, name)
techage.OceanIdTbl[id] = true
end
end
end
determine_ocean_ids()
-- check if natural water is on given position (water placed by player has param2 = 1)
function techage.is_ocean(pos)
if pos.y > 1 then return false end
local node = techage.get_node_lvm(pos)
if node.name ~= "default:water_source" then return false end
if node.param2 == 1 then return false end
return true
end
function techage.item_image(x, y, itemname, count)
local name, size = unpack(string.split(itemname, " "))
size = count and count or size
size = tonumber(size) or 1
local label = ""
local text = minetest.formspec_escape(ItemStack(itemname):get_description())
local tooltip = "tooltip["..x..","..y..";1,1;"..text..";#0C3D32;#FFFFFF]"
if minetest.registered_tools[name] and size > 1 then
local offs = 0
if size < 10 then
offs = 0.65
elseif size < 100 then
offs = 0.5
elseif size < 1000 then
offs = 0.35
else
offs = 0.2
end
label = "label["..(x + offs)..","..(y + 0.45)..";"..tostring(size).."]"
end
return "box["..x..","..y..";0.85,0.9;#808080]"..
"item_image["..x..","..y..";1,1;"..itemname.."]"..
tooltip..
label
end
function techage.item_image_small(x, y, itemname, tooltip_prefix)
local name = unpack(string.split(itemname, " "))
local tooltip = ""
local ndef = minetest.registered_nodes[name] or minetest.registered_items[name] or minetest.registered_craftitems[name]
if ndef and ndef.description then
local text = minetest.formspec_escape(ndef.description)
tooltip = "tooltip["..x..","..y..";0.8,0.8;"..tooltip_prefix..": "..text..";#0C3D32;#FFFFFF]"
end
return "box["..x..","..y..";0.65,0.7;#808080]"..
"item_image["..x..","..y..";0.8,0.8;"..name.."]"..
tooltip
end
function techage.vector_dump(posses)
local t = {}
for _,pos in ipairs(posses) do
t[#t + 1] = minetest.pos_to_string(pos)
end
return table.concat(t, " ")
end
-- title bar help (width is the fornmspec width)
function techage.question_mark_help(width, tooltip)
local x = width- 0.6
return "label["..x..",-0.1;"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..",-0.1;0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.wrench_tooltip(x, y)
local tooltip = S("Block has an\nadditional wrench menu")
return "image["..x.."," .. y .. ";0.5,0.5;techage_inv_wrench.png]" ..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
techage.RegisteredMobsMods = {}
-- Register mobs mods for the move/fly controllers
function techage.register_mobs_mods(mod)
techage.RegisteredMobsMods[mod] = true
end
function techage.beduino_signed_var(val)
val = val or 0
return val >= 32768 and val - 0x10000 or val
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
-------------------------------------------------------------------------------
function techage.get_expoints(player)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
return meta:get_int("techage_ex_points")
end
end
end
-- 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
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
-- Delete number with: `//lua minetest.get_player_by_name("<name>"):get_meta():set_string("techage_collider_number", "")`
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
function techage.set_expoints(player, ex_points)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
meta:set_int("techage_ex_points", ex_points)
return true
end
end
end
-------------------------------------------------------------------------------
-- Scheduler for a table-based, cyclic call of functions
-------------------------------------------------------------------------------
local TABLE_SIZE = 256
techage.scheduler = {}
local function add_to_table(tbl, i, func)
while i < TABLE_SIZE do
if not tbl[i] then
tbl[i] = func
return i + 1
end
i = i + 1
end
return i
end
function techage.scheduler.init(pos)
local mem = techage.get_mem(pos)
mem.sched_idx = 0
end
-- tFunc : (empty) table of functions
-- call_rate : (2,4,8,16,32,64 or 128)
-- offset : 0-128
-- func : function to be called
function techage.scheduler.register(tFunc, call_rate, offset, func)
local i= 0
while i < TABLE_SIZE do
if (i % call_rate) == offset then
i = add_to_table(tFunc, i, func)
else
i = i + 1
end
end
return tFunc
end
-- tFunc : table of functions
-- default : default function (optional)
-- Returns a function to be called be the callee
function techage.scheduler.get(pos, tFunc, default)
local mem = techage.get_mem(pos)
mem.sched_idx = ((mem.sched_idx or 0) + 1) % TABLE_SIZE
return tFunc[mem.sched_idx] or default or function() end
end
@@ -0,0 +1,278 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Liquid lib
]]--
local M = minetest.get_meta
local S = techage.S
local P2S = minetest.pos_to_string
local LQD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).liquid end
local BLOCKING_TIME = 0.3 -- 300ms
techage.liquid = {}
local LiquidDef = {}
local IsLiquid = {}
local ContainerDef = {}
local function help(x, y)
local tooltip = S("To add liquids punch\nthe tank\nwith a liquid container")
return "label["..x..","..y..";"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.liquid.formspec(pos, nvm, title)
title = title or S("Liquid Tank")
local itemname = "techage:liquid"
if nvm.liquid and nvm.liquid.amount and nvm.liquid.amount > 0 and nvm.liquid.name then
itemname = nvm.liquid.name.." "..nvm.liquid.amount
end
local name = minetest.get_node(pos).name
if name == "techage:ta4_tank" then
local meta = M(pos)
local public = dump((meta:get_int("public") or 0) == 1)
local keep_assignment = dump((meta:get_int("keep_assignment") or 0) == 1)
return "size[8,3.5]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[0.2,-0.1;"..minetest.colorize("#000000", title).."]"..
help(7.4, -0.1)..
techage.item_image(3.5, 1, itemname)..
"checkbox[0.1,2.5;public;"..S("Allow public access to the tank")..";"..public.."]"..
"checkbox[0.1,3;keep_assignment;"..S("keep assignment")..";"..keep_assignment.."]"
else
return "size[8,2]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[0.2,-0.1;"..minetest.colorize("#000000", title).."]"..
help(7.4, -0.1)..
techage.item_image(3.5, 1, itemname)
end
end
function techage.liquid.is_empty(pos)
local nvm = techage.get_nvm(pos)
return not nvm.liquid or (nvm.liquid.amount or 0) <= 0
end
techage.liquid.recv_message = {
on_recv_message = function(pos, src, topic, payload)
if topic == "load" then
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
return techage.power.percent(LQD(pos).capa, nvm.liquid.amount), nvm.liquid.amount
elseif topic == "size" then
return LQD(pos).capa
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == 128 then
return 0, techage.get_node_lvm(pos).name
elseif topic == 134 then
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
if payload[1] == 1 then
local value = techage.power.percent(LQD(pos).capa, nvm.liquid.amount)
return 0, {math.floor(value + 0.5)}
else
return 0, {nvm.liquid.amount}
end
else
return 2, ""
end
end,
}
-- like: register_liquid("techage:ta3_barrel_oil", "techage:ta3_barrel_empty", 10, "techage:oil")
function techage.register_liquid(full_container, empty_container, container_size, inv_item)
LiquidDef[full_container] = {container = empty_container, size = container_size, inv_item = inv_item}
ContainerDef[empty_container] = ContainerDef[empty_container] or {}
ContainerDef[empty_container][inv_item] = full_container
IsLiquid[inv_item] = true
if inv_item == "techage:water" and container_size == 1 then
techage.register_water_bucket(empty_container, full_container)
end
end
local function get_liquid_def(full_container)
return LiquidDef[full_container]
end
local function get_container_def(container_name)
return ContainerDef[container_name]
end
local function is_container_empty(container_name)
return ContainerDef[container_name]
end
local function get_full_container(empty_container, inv_item)
return ContainerDef[empty_container] and ContainerDef[empty_container][inv_item]
end
-- used by filler
local function fill_container(pos, inv, empty_container)
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local full_container = get_full_container(empty_container, nvm.liquid.name)
if empty_container and full_container then
local ldef = get_liquid_def(full_container)
if ldef and nvm.liquid.amount - ldef.size >= 0 then
if inv:room_for_item("dst", {name = full_container}) then
inv:add_item("dst", {name = full_container})
nvm.liquid.amount = nvm.liquid.amount - ldef.size
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return true
end
end
end
-- undo
inv:add_item("src", {name = empty_container})
return false
end
-- used by filler
local function empty_container(pos, inv, full_container)
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local ndef_lqd = LQD(pos)
local tank_size = (ndef_lqd and ndef_lqd.capa) or 0
local ldef = get_liquid_def(full_container)
if ldef and (not nvm.liquid.name or ldef.inv_item == nvm.liquid.name) then
if nvm.liquid.amount + ldef.size <= tank_size then
if inv:room_for_item("dst", {name = ldef.container}) then
inv:add_item("dst", {name = ldef.container})
nvm.liquid.amount = nvm.liquid.amount + ldef.size
nvm.liquid.name = ldef.inv_item
return true
end
end
end
-- undo
inv:add_item("src", {name = full_container})
return false
end
-- check if the wielded empty container can be replaced by a full
-- container and added to the players inventory
local function fill_on_punch(nvm, empty_container, item_count, puncher)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local full_container = get_full_container(empty_container, nvm.liquid.name)
if empty_container and full_container then
local item = {name = full_container}
local ldef = get_liquid_def(full_container)
if ldef and nvm.liquid.amount - ldef.size >= 0 then
if item_count > 1 then -- can't be simply replaced?
-- check for extra free space
local inv = puncher:get_inventory()
if inv:room_for_item("main", {name = full_container}) then
-- add full container and return
-- the empty once - 1
inv:add_item("main", {name = full_container})
item = {name = empty_container, count = item_count - 1}
else
return -- no free space
end
end
nvm.liquid.amount = nvm.liquid.amount - ldef.size
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return item -- to be added to the players inv.
end
elseif nvm.liquid.name and not IsLiquid[nvm.liquid.name] then
if empty_container == "" then
local count = math.max(nvm.liquid.amount, 99)
local name = nvm.liquid.name
nvm.liquid.amount = nvm.liquid.amount - count
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return {name = name, count = count}
end
end
end
local function legacy_items(full_container, item_count)
if full_container == "techage:isobutane" then
return {container = "", size = item_count, inv_item = full_container}
elseif full_container == "techage:oil_source" then
return {container = "", size = item_count, inv_item = full_container}
end
end
-- check if the wielded full container can be emptied into the tank
local function empty_on_punch(pos, nvm, full_container, item_count)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local lqd_def = get_liquid_def(full_container) or legacy_items(full_container, item_count)
local ndef_lqd = LQD(pos)
if lqd_def and ndef_lqd then
local tank_size = ndef_lqd.capa or 0
if not nvm.liquid.name or lqd_def.inv_item == nvm.liquid.name then
if nvm.liquid.amount + lqd_def.size <= tank_size then
nvm.liquid.amount = nvm.liquid.amount + lqd_def.size
nvm.liquid.name = lqd_def.inv_item
return {name = lqd_def.container}
end
end
end
end
function techage.liquid.on_punch(pos, node, puncher, pointed_thing)
local public = M(pos):get_int("public") == 1
if not public and minetest.is_protected(pos, puncher:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
local new_item = fill_on_punch(nvm, wielded_item, item_count, puncher)
or empty_on_punch(pos, nvm, wielded_item, item_count)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
return
end
end
function techage.liquid.get_liquid_amount(nvm)
if nvm.liquid and nvm.liquid.amount then
return nvm.liquid.amount
end
return 0
end
techage.liquid.get_liquid_def = get_liquid_def
techage.liquid.get_container_def = get_container_def
techage.liquid.is_container_empty = is_container_empty
techage.liquid.get_full_container = get_full_container
techage.liquid.fill_container = fill_container
techage.liquid.empty_container = empty_container
techage.liquid.fill_on_punch = fill_on_punch
techage.liquid.empty_on_punch = empty_on_punch
@@ -0,0 +1,91 @@
techage.manual_DE = {}
techage.manual_DE.aTitel = {
"1,SaferLua Controller with Periphery",
"2,SaferLua Controller",
"3,Central Server",
"3,SaferLua Controller Terminal",
}
techage.manual_DE.aText = {
"",
"The SaferLua Controller is a small computer programmable in Lua to control your machinery.\n"..
"In contrast to the SmartLine Controller this controller allows to implement larger and smarter control and monitoring tasks.\n"..
"\n"..
"The controller can be programmed in SaferLua a subset of Lua for safe and secure Lua programs the Minetest server.\n"..
"\n",
"The Server node can be placed everywhere. It can also be used for communication purposes between several Controllers.\n"..
"The Server has a form to enter valid usernames for server access.\n"..
"\n"..
"The controller has a menu form with the following tabs:\n"..
"\n"..
" - the 'init' tab for the initialization code block\n"..
" - the 'func' tab for the Lua functions\n"..
" - the 'loop' tab for the main code block\n"..
" - the 'outp' tab for debugging outputs via '$print()'\n"..
" - the 'notes' tab for your code snippets or other notes\n"..
" - the 'help' tab with information to the available commands\n"..
"\n"..
"\n"..
"The controller needs battery power to work.\n"..
"\n",
"The Terminal is used to send command strings to the controller.\n"..
"In turn\\, the controller can send text strings to the terminal.\n"..
"The Terminal has a help system for internal commands. Its supports the following commands:\n"..
"\n"..
" - 'clear' = clear the screen\n"..
" - 'help' = output this message\n"..
" - 'pub' = switch terminal to public use (everybody can enter commands)\n"..
" - 'priv' = switch terminal to private use (only the owner can enter commands)\n"..
" - 'send <num> on/off' = send on/off event to e. g. lamps (for testing purposes)\n"..
" - 'msg <num> <text>' = send a text message to another Controller (for testing purposes)\n"..
"\n"..
"\n",
}
techage.manual_DE.aItemName = {
"",
"",
"",
"",
}
techage.manual_DE.aPlanTable = {
"",
"",
"",
"",
}
@@ -0,0 +1,98 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
mark.lua:
]]--
local marker_region = {}
function techage.unmark_region(name)
if marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(marker_region[name]) do
entity:remove()
end
marker_region[name] = nil
end
end
function techage.mark_region(name, pos1, pos2, owner, secs)
if not name or not pos1 or not pos2 then return end
techage.unmark_region(name)
local thickness = 0.2
local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
local markers = {}
--XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "techage:region_cube")
if marker ~= nil then
marker:set_properties({
visual_size={x=sizex * 2, y=sizey * 2},
--collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness},
collisionbox = {0,0,0, 0,0,0},
})
if owner then
marker:set_nametag_attributes({text = owner})
end
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
--YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "techage:region_cube")
if marker ~= nil then
marker:set_properties({
visual_size={x=sizez * 2, y=sizey * 2},
--collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
collisionbox = {0,0,0, 0,0,0},
})
marker:set_yaw(math.pi / 2)
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
marker_region[name] = markers
minetest.after(secs or 20, techage.unmark_region, name)
end
function techage.switch_region(name, pos1, pos2)
if marker_region[name] ~= nil then --marker already exists
techage.unmark_region(name)
else
techage.mark_region(name, pos1, pos2)
end
end
minetest.register_entity(":techage:region_cube", {
initial_properties = {
visual = "upright_sprite",
textures = {"techage_cube_mark.png"},
use_texture_alpha = true,
physical = false,
glow = 12,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_region(self.player_name)
end,
})
@@ -0,0 +1,128 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
mark.lua:
]]--
local marker_region = {}
function techage.unmark_position(name)
if marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(marker_region[name]) do
entity:remove()
end
marker_region[name] = nil
end
end
function techage.mark_position(name, pos, nametag, color, time)
local marker = minetest.add_entity(pos, "techage:position_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag})
marker:get_luaentity().player_name = name
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
function techage.mark_cube(name, pos1, pos2, nametag, color, time)
local new_x = pos1.x + ((pos2.x - pos1.x) / 2)
local new_y = pos1.y + ((pos2.y - pos1.y) / 2)
local new_z = pos1.z + ((pos2.z - pos1.z) / 2)
local size_x = math.abs(pos1.x - pos2.x) + 1
local size_y = math.abs(pos1.y - pos2.y) + 1
local size_z = math.abs(pos1.z - pos2.z) + 1
local marker = minetest.add_entity(
{x = new_x, y = new_y, z = new_z}, "techage:position_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag, visual_size = {x = size_x, y = size_y, z = size_z}})
marker:get_luaentity().player_name = name
marker:set_properties({visual_size = {x = size_x, y = size_y, z = size_z}})
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
minetest.register_entity(":techage:position_cube", {
initial_properties = {
visual = "cube",
textures = {
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
},
use_texture_alpha = true,
physical = false,
visual_size = {x = 1.1, y = 1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 8,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_position(self.player_name)
end,
})
function techage.mark_side(name, pos, dir, nametag, color, time)
local v = vector.multiply(tubelib2.Dir6dToVector[dir or 0], 0.7)
local pos2 = vector.add(pos, v)
local marker = minetest.add_entity(pos2, "techage:position_side")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag})
marker:get_luaentity().player_name = name
if dir == 2 or dir == 4 then
marker:setyaw(math.pi / 2)
end
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
minetest.register_entity(":techage:position_side", {
initial_properties = {
visual = "upright_sprite",
textures = {"techage_side_mark.png"},
physical = false,
visual_size = {x = 1.1, y = 1.1, z = 1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 12,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_position(self.player_name)
end,
})
@@ -0,0 +1,146 @@
--[[
TechAge
=======
Copyright (C) 2020-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Block marker lib for door/move/fly controller
]]--
local MAX_NUM = 128
local marker = {}
local MarkedNodes = {} -- t[player] = {{entity, pos},...}
local MaxNumber = {}
local CurrentPos -- to mark punched entities
local function unmark_position(name, pos)
pos = vector.round(pos)
for idx,item in ipairs(MarkedNodes[name] or {}) do
if vector.equals(pos, item.pos) then
item.entity:remove()
table.remove(MarkedNodes[name], idx)
CurrentPos = pos
return true
end
end
end
function marker.unmark_all(name)
for _,item in ipairs(MarkedNodes[name] or {}) do
item.entity:remove()
end
MarkedNodes[name] = nil
end
local function mark_position(name, pos)
pos = vector.round(pos)
if not CurrentPos or not vector.equals(pos, CurrentPos) then -- entity not punched?
if #MarkedNodes[name] < MaxNumber[name] then
local entity = minetest.add_entity(pos, "techage:block_marker")
if entity ~= nil then
entity:get_luaentity().player_name = name
table.insert(MarkedNodes[name], {pos = pos, entity = entity})
end
CurrentPos = nil
return true
end
end
CurrentPos = nil
end
function marker.get_poslist(name)
local idx = 0
local lst = {}
local hashlist = {}
for _,item in ipairs(MarkedNodes[name] or {}) do
local hash = minetest.hash_node_position(item.pos)
if not hashlist[hash] then
table.insert(lst, item.pos)
hashlist[hash] = true
idx = idx + 1
if idx >= MAX_NUM then break end
end
end
return lst
end
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if puncher and puncher:is_player() then
local name = puncher:get_player_name()
if not MarkedNodes[name] then
return
end
mark_position(name, pointed_thing.under)
end
end)
function marker.start(name, max_num)
MaxNumber[name] = max_num or 99
MarkedNodes[name] = {}
end
function marker.stop(name)
MarkedNodes[name] = nil
MaxNumber[name] = nil
end
function marker.mark_positions(name, lpos, ttl)
for _,pos in ipairs(lpos or {}) do
local entity = minetest.add_entity(pos, "techage:block_marker")
if entity ~= nil then
entity:get_luaentity().player_name = name
entity:get_luaentity().ttl = ttl
end
end
end
minetest.register_on_leaveplayer(function(ObjectRef, timed_out)
if ObjectRef and ObjectRef:is_player() then
local name = ObjectRef:get_player_name()
marker.unmark_all(name)
end
end)
minetest.register_entity(":techage:block_marker", {
initial_properties = {
visual = "cube",
textures = {
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
},
physical = false,
visual_size = {x=1.1, y=1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 8,
static_save = false,
},
on_step = function(self, dtime)
self.ttl = (self.ttl or 2400) - 1
if self.ttl <= 0 then
local pos = self.object:get_pos()
if not unmark_position(self.player_name, pos) then self.object:remove() end
end
end,
on_punch = function(self, hitter)
local pos = self.object:get_pos()
local name = hitter:get_player_name()
if name == self.player_name then
if not unmark_position(self.player_name, pos) then self.object:remove() end
end
end,
})
return marker
@@ -0,0 +1,561 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
A state model/class for TechAge nodes.
]]--
--[[
Node states:
+-----------------------------------+ +------------+
| | | |
| V V |
| +---------+ |
| | | |
| +---------| STOPPED | |
| | | | |
| button | +---------+ |
| | ^ |
button | V | button |
| +---------+ | | button
| +--------->| |---------+ |
| | power | RUNNING | |
| | +------| |---------+ |
| | | +---------+ | |
| | | ^ | | |
| | | | | | |
| | V | V V |
| +---------+ +----------+ +---------+ |
| | | | | | | |
+---| NOPOWER | | STANDBY/ | | FAULT |----------+
| | | BLOCKED | | |
+---------+ +----------+ +---------+
| cycle time operational needs power
+---------+------------+-------------+-------------
| RUNNING normal yes yes
| BLOCKED long yes no
| STANDBY long yes no
| NOPOWER long no no
| FAULT none no no
| STOPPED none no no
Node nvm data:
"techage_state" - node state, like "RUNNING"
"techage_countdown" - countdown to standby mode
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
local N = techage.get_node_lvm
local MAX_CYCLE_TIME = 20
--
-- TechAge machine states
--
techage.RUNNING = 1 -- in normal operation/turned on
techage.BLOCKED = 2 -- a pushing node is blocked due to a full destination inventory
techage.STANDBY = 3 -- nothing to do (e.g. no input items), or node (world) not loaded
techage.NOPOWER = 4 -- only for power consuming nodes, no operation
techage.FAULT = 5 -- any fault state (e.g. wrong source items), which can be fixed by the player
techage.STOPPED = 6 -- not operational/turned off
techage.UNLOADED = 7 -- Map block unloaded
techage.INACTIVE = 8 -- Map block loaded but node is not actively working
techage.StatesImg = {
"techage_inv_button_on.png",
"techage_inv_button_warning.png",
"techage_inv_button_standby.png",
"techage_inv_button_nopower.png",
"techage_inv_button_error.png",
"techage_inv_button_off.png",
}
local function error(pos, msg)
minetest.log("error", "[TA states] "..msg.." at "..S(pos).." "..N(pos).name)
end
-- Return state button image for the node inventory
function techage.state_button(state)
if state and state < 7 and state > 0 then
return techage.StatesImg[state]
end
return "techage_inv_button_off.png"
end
function techage.get_power_image(pos, nvm)
local node = techage.get_node_lvm(pos)
local s = "3" -- electrical power
if string.find(node.name, "techage:ta2") then
s = "2" -- axles power
end
return "techage_inv_powerT"..s..".png"
end
-- State string based on button states
techage.StateStrings = {"running", "blocked", "standby", "nopower", "fault", "stopped"}
--
-- Local States
--
local RUNNING = techage.RUNNING
local BLOCKED = techage.BLOCKED
local STANDBY = techage.STANDBY
local NOPOWER = techage.NOPOWER
local FAULT = techage.FAULT
local STOPPED = techage.STOPPED
--
-- NodeStates Class Functions
--
techage.NodeStates = {}
local NodeStates = techage.NodeStates
local function can_start(pos, nvm)
--if false, node goes in FAULT
return true
end
local function has_power(pos, nvm)
--if false, node goes in NOPOWER
return true
end
local function swap_node(pos, new_name, old_name)
local node = techage.get_node_lvm(pos)
if node.name == new_name then
return
end
if node.name == old_name then
node.name = new_name
minetest.swap_node(pos, node)
end
end
-- true if node_timer should be executed
function techage.is_operational(nvm)
local state = nvm.techage_state or STOPPED
return state < NOPOWER
end
function techage.is_running(nvm)
return (nvm.techage_state or STOPPED) == RUNNING
end
-- consumes power
function techage.needs_power(nvm)
local state = nvm.techage_state or STOPPED
return state == RUNNING or state == NOPOWER
end
-- consumes power
function techage.needs_power2(state)
state = state or STOPPED
return state == RUNNING or state == NOPOWER
end
function techage.get_state_string(nvm)
return techage.StateStrings[nvm.techage_state or STOPPED]
end
function NodeStates:new(attr)
local o = {
-- mandatory
cycle_time = attr.cycle_time, -- for running state
standby_ticks = attr.standby_ticks, -- for standby state
-- optional
countdown_ticks = attr.countdown_ticks or 1,
node_name_passive = attr.node_name_passive,
node_name_active = attr.node_name_active,
infotext_name = attr.infotext_name,
has_power = attr.has_power or has_power,
can_start = attr.can_start or can_start,
start_node = attr.start_node,
stop_node = attr.stop_node,
formspec_func = attr.formspec_func,
on_state_change = attr.on_state_change,
quick_start = attr.quick_start,
}
setmetatable(o, self)
self.__index = self
return o
end
function NodeStates:node_init(pos, nvm, number)
nvm.techage_state = STOPPED
M(pos):set_string("node_number", number)
if self.infotext_name then
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
end
if self.formspec_func then
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
end
-- to be used to re-start the timer outside of node_timer()
local function start_timer_delayed(pos, cycle_time)
local t = minetest.get_node_timer(pos)
t:stop()
if cycle_time > 0.9 then
minetest.after(0.1, t.start, t, cycle_time)
else
error(pos, "invalid cycle_time")
end
end
function NodeStates:stop(pos, nvm)
local state = nvm.techage_state or STOPPED
nvm.techage_state = STOPPED
if self.stop_node then
self.stop_node(pos, nvm, state)
end
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
end
if self.formspec_func then
nvm.ta_state_tooltip = "stopped"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if self.on_state_change then
self.on_state_change(pos, state, STOPPED)
end
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
end
return true
end
function NodeStates:start(pos, nvm)
local state = nvm.techage_state or STOPPED
if state ~= RUNNING and state ~= FAULT then
local res = self.can_start(pos, nvm, state)
if res ~= true then
self:fault(pos, nvm, res)
return false
end
if not self.has_power(pos, nvm, state) then
self:nopower(pos, nvm)
return false
end
nvm.techage_state = RUNNING
if self.start_node then
self.start_node(pos, nvm, state)
end
nvm.techage_countdown = self.countdown_ticks
if self.node_name_active then
swap_node(pos, self.node_name_active, self.node_name_passive)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": running")
end
if self.formspec_func then
nvm.ta_state_tooltip = "running"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
end
if self.on_state_change then
self.on_state_change(pos, state, RUNNING)
end
start_timer_delayed(pos, self.cycle_time)
if self.quick_start and state == STOPPED then
self.quick_start(pos, 0)
end
self:trigger_state(pos, nvm)
return true
end
return false
end
function NodeStates:standby(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
if state == RUNNING or state == BLOCKED then
nvm.techage_state = STANDBY
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "standby"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "standby"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if self.on_state_change then
self.on_state_change(pos, state, STANDBY)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
-- special case of standby for pushing nodes
function NodeStates:blocked(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
if state == RUNNING then
nvm.techage_state = BLOCKED
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "blocked"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "blocked"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if self.on_state_change then
self.on_state_change(pos, state, BLOCKED)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
function NodeStates:nopower(pos, nvm, err_string)
local state = nvm.techage_state or RUNNING
if state ~= NOPOWER then
nvm.techage_state = NOPOWER
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "no power"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "no power"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if self.on_state_change then
self.on_state_change(pos, state, NOPOWER)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
function NodeStates:fault(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
err_string = err_string or "fault"
if state == RUNNING or state == STOPPED then
nvm.techage_state = FAULT
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..err_string)
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "fault"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm) or "")
end
if self.on_state_change then
self.on_state_change(pos, state, FAULT)
end
minetest.get_node_timer(pos):stop()
return true
end
return false
end
function NodeStates:get_state(nvm)
return nvm.techage_state or techage.STOPPED
end
-- keep the timer running?
function NodeStates:is_active(nvm)
local state = nvm.techage_state or STOPPED
return state < FAULT
end
function NodeStates:start_if_standby(pos)
local nvm = techage.get_nvm(pos)
if nvm.techage_state == STANDBY then
self:start(pos, nvm)
end
end
-- To be called if node is idle.
-- If countdown reaches zero, the node is set to STANDBY.
function NodeStates:idle(pos, nvm)
local countdown = (nvm.techage_countdown or 0) - 1
nvm.techage_countdown = countdown
if countdown <= 0 then
self:standby(pos, nvm)
end
end
-- To be called after successful node action to raise the timer
-- and keep the node in state RUNNING
function NodeStates:keep_running(pos, nvm, val)
-- set to RUNNING if not already done
if nvm.techage_state ~= RUNNING then
self:start(pos, nvm)
end
nvm.techage_countdown = val or 4
nvm.last_active = minetest.get_gametime()
end
function NodeStates:trigger_state(pos, nvm)
nvm.last_active = minetest.get_gametime()
end
-- Start/stop node based on button events.
-- if function returns false, no button was pressed
function NodeStates:state_button_event(pos, nvm, fields)
if fields.state_button ~= nil then
local state = nvm.techage_state or STOPPED
if state == STOPPED or state == STANDBY or state == BLOCKED then
if not self:start(pos, nvm) and (state == STANDBY or state == BLOCKED) then
self:stop(pos, nvm)
end
elseif state == RUNNING or state == FAULT or state == NOPOWER then
self:stop(pos, nvm)
end
return true
end
return false
end
function NodeStates:get_state_button_image(nvm)
local state = nvm.techage_state or STOPPED
return techage.state_button(state)
end
function NodeStates:get_state_tooltip(nvm)
local tp = nvm.ta_state_tooltip or ""
return tp..";#0C3D32;#FFFFFF"
end
-- command interface
function NodeStates:on_receive_message(pos, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == "on" then
self:start(pos, techage.get_nvm(pos))
return true
elseif topic == "off" then
self:stop(pos, techage.get_nvm(pos))
return true
elseif topic == "state" then
local node = minetest.get_node(pos)
if node.name == "ignore" then -- unloaded node?
return "unloaded"
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return "inactive"
end
end
return techage.get_state_string(techage.get_nvm(pos))
elseif topic == "fuel" then
return techage.fuel.get_fuel_amount(nvm)
elseif topic == "load" then
return techage.liquid.get_liquid_amount(nvm)
else
return "unsupported"
end
end
function NodeStates:on_beduino_receive_cmnd(pos, topic, payload)
if topic == 1 then
if payload[1] == 0 then
self:stop(pos, techage.get_nvm(pos))
return 0
else
self:start(pos, techage.get_nvm(pos))
return 0
end
else
return 2 -- unknown or invalid topic
end
end
function NodeStates:on_beduino_request_data(pos, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == 128 then
return 0, techage.get_node_lvm(pos).name
elseif topic == 129 then
local node = minetest.get_node(pos)
if node.name == "ignore" then -- unloaded node?
return 0, {techage.UNLOADED}
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return 0, {techage.INACTIVE}
end
end
return 0, {nvm.techage_state or STOPPED}
else
return 2, "" -- topic is unknown or invalid
end
end
function NodeStates.get_beduino_state(pos)
local node = minetest.get_node(pos)
local nvm = techage.get_nvm(pos)
if node.name == "ignore" then -- unloaded node?
return 0, {techage.UNLOADED}
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return 0, {techage.INACTIVE}
end
end
return 0, {nvm.techage_state or STOPPED}
end
-- restart timer
function NodeStates:on_node_load(pos)
local nvm = techage.get_nvm(pos)
local state = nvm.techage_state or STOPPED
if state == RUNNING then
minetest.get_node_timer(pos):start(self.cycle_time)
elseif state < FAULT then
minetest.get_node_timer(pos):start(self.cycle_time * self.standby_ticks)
end
end
minetest.register_node("techage:defect_dummy", {
description = "Corrupted Node (to be replaced)",
tiles = {
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
},
drop = "",
groups = {cracky=2, crumbly=2, choppy=2, not_in_creative_inventory=1},
is_ground_content = false,
})
@@ -0,0 +1,199 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Data storage system for node related volatile and non-volatile data.
Non-volatile data is stored from time to time and at shutdown.
Volatile data is lost at every shutdown.
]]--
local NvmStore = {} -- non-volatile data cache
local MemStore = {} -- volatile data cache
local N = function(pos) print(minetest.pos_to_string(pos), minetest.get_node(pos).name) end
-------------------------------------------------------------------
-- Backend
-------------------------------------------------------------------
local MP = minetest.get_modpath("techage")
local techage_use_sqlite = minetest.settings:get_bool('techage_use_sqlite', false)
local backend
if techage_use_sqlite then
backend = dofile(MP .. "/basis/nodedata_sqlite.lua")
else
backend = dofile(MP .. "/basis/nodedata_meta.lua")
end
-- return keys for mapblock and inner-mapblock addressing based on the node position
local function get_keys(pos)
local kx1, kx2 = math.floor(pos.x / 16) + 2048, pos.x % 16
local ky1, ky2 = math.floor(pos.y / 16) + 2048, pos.y % 16
local kz1, kz2 = math.floor(pos.z / 16) + 2048, pos.z % 16
return kx1 * 4096 * 4096 + ky1 * 4096 + kz1, kx2 * 16 * 16 + ky2 * 16 + kz2
end
local function pos_from_key(key1, key2)
local x1 = (math.floor(key1 / (4096 * 4096)) - 2048) * 16
local y1 = ((math.floor(key1 / 4096) % 4096) - 2048) * 16
local z1 = ((key1 % 4096) - 2048) * 16
local x2 = math.floor(key2 / (16 * 16))
local y2 = math.floor(key2 / 16) % 16
local z2 = key2 % 16
return {x = x1 + x2, y = y1 + y2, z = z1 + z2}
end
local function debug(key1, item)
--local pos1 = pos_from_key(key1, 0)
--local pos2 = {x = pos1.x + 15, y = pos1.y + 15, z = pos1.z + 15}
--techage.mark_region("mapblock", pos1, pos2, "singleplayer", 5)
local cnt = 0
for key2, tbl in pairs(item) do
if key2 ~= "in_use" then
cnt = cnt + 1
--N(pos_from_key(key1, key2))
end
end
print("mapblock", string.format("%09X", key1), cnt.." nodes")
end
-------------------------------------------------------------------
-- Storage scheduler
-------------------------------------------------------------------
local CYCLE_TIME = 600 -- store data every 10 min
local JobQueue = {}
local first = 0
local last = -1
local SystemTime = 0
local function push(key)
last = last + 1
JobQueue[last] = {key = key, time = SystemTime + CYCLE_TIME}
end
local function pop()
if first > last then return end
local item = JobQueue[first]
if item.time <= SystemTime then
JobQueue[first] = nil -- to allow garbage collection
first = first + 1
return item.key
end
end
-- check every 100 msec if any data has to be stored
minetest.register_globalstep(function(dtime)
SystemTime = SystemTime + dtime
local key = pop()
if key and NvmStore[key] then
-- minetest.log("warning",
-- string.format("[TA Storage] SystemTime = %.3f, #JobQueue = %d, in_use = %s",
-- SystemTime, last - first, NvmStore[key].in_use))
local t = minetest.get_us_time()
if NvmStore[key].in_use then
NvmStore[key].in_use = nil
backend.store_mapblock_data(key, NvmStore[key])
push(key)
else
NvmStore[key] = nil -- remove unused data from cache
end
t = minetest.get_us_time() - t
if t > 20000 then
minetest.log("warning", "[TA Storage] duration = "..(t/1000.0).." ms")
end
end
end)
-------------------------------------------------------------------
-- Store/Restore NVM data
-------------------------------------------------------------------
NvmStore = backend.restore_at_startup()
minetest.register_on_shutdown(function()
local nvm = techage.get_nvm0()
nvm.ServerCrashed = false
backend.freeze_at_shutdown(NvmStore)
end)
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
-- Returns volatile node data as table
function techage.get_mem(pos)
local hash = minetest.hash_node_position(pos)
if not MemStore[hash] then
MemStore[hash] = {}
end
return MemStore[hash]
end
-- Returns non-volatile node data as table
function techage.get_nvm(pos)
local key1, key2 = get_keys(pos)
if not NvmStore[key1] then
NvmStore[key1] = backend.get_mapblock_data(key1)
push(key1)
end
local block = NvmStore[key1]
block.in_use = true
if not block[key2] then
block[key2] = backend.get_node_data(pos)
end
return block[key2]
end
function techage.get_nvm0()
if not NvmStore[0] then
NvmStore[0] = backend.get_mapblock_data(0)
push(0)
end
local block = NvmStore[0]
block.in_use = true
if not block[0] then
block[0] = {}
end
return block[0]
end
-- Returns true/false
function techage.has_nvm(pos)
local key1, key2 = get_keys(pos)
if not NvmStore[key1] then
NvmStore[key1] = backend.get_mapblock_data(key1)
push(key1)
end
return NvmStore[key1][key2] ~= nil
end
function techage.peek_nvm(pos)
local key1, key2 = get_keys(pos)
local block = NvmStore[key1] or {}
return block[key2] or {}
end
-- To be called when a node is removed
function techage.del_mem(pos)
local hash = minetest.hash_node_position(pos)
MemStore[hash] = nil
local key1, key2 = get_keys(pos)
NvmStore[key1] = NvmStore[key1] or backend.get_mapblock_data(key1)
NvmStore[key1][key2] = nil
backend.store_mapblock_data(key1, NvmStore[key1])
end
@@ -0,0 +1,103 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Storage backend for node related data as node metadata
]]--
-- for lazy programmers
local M = minetest.get_meta
local storage = techage.storage
-------------------------------------------------------------------
-- Marshaling
-------------------------------------------------------------------
local use_marshal = minetest.settings:get_bool('techage_use_marshal', false)
local MAR_MAGIC = 0x8e
-- default functions
local serialize = minetest.serialize
local deserialize = minetest.deserialize
if use_marshal then
if not techage.IE then
error("Please add 'secure.trusted_mods = techage' to minetest.conf!")
end
local marshal = techage.IE.require("marshal")
if not marshal then
error("Please install marshal via 'luarocks install lua-marshal'")
end
serialize = marshal.encode
deserialize = function(s)
if s ~= "" then
if s:byte(1) == MAR_MAGIC then
return marshal.decode(s)
else
return minetest.deserialize(s)
end
end
end
end
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
local api = {}
function api.get_mapblock_data(key)
return {}
end
function api.store_mapblock_data(key, mapblock_data)
for key, item in pairs(mapblock_data) do
if key ~= "in_use" then
local pos = item and item._POS_
if pos then
item._POS_ = nil
local data = serialize(item)
item._POS_ = pos
local meta = M(pos)
meta:set_string("ta_data", data)
meta:mark_as_private("ta_data")
end
end
end
end
function api.get_node_data(pos)
local tbl = {}
local s = M(pos):get_string("ta_data")
if s ~= "" then
tbl = deserialize(s) or {}
end
tbl._POS_ = table.copy(pos)
return tbl
end
-- Meta data can't be written reliable at shutdown,
-- so we have to store/restore the data differently
function api.freeze_at_shutdown(data)
storage:set_string("shutdown_nodedata", serialize(data))
end
function api.restore_at_startup()
local s = storage:get_string("shutdown_nodedata")
if s ~= "" then
return deserialize(s) or {}
end
return {}
end
return api
@@ -0,0 +1,130 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Storage backend for node related data via sqlite database
]]--
-- for lazy programmers
local M = minetest.get_meta
-------------------------------------------------------------------
-- Database
-------------------------------------------------------------------
local MN = minetest.get_current_modname()
local WP = minetest.get_worldpath()
local use_marshal = minetest.settings:get_bool('techage_use_marshal', false)
local MAR_MAGIC = 0x8e
if not techage.IE then
error("Please add 'secure.trusted_mods = techage' to minetest.conf!")
end
local sqlite3 = techage.IE.require("lsqlite3")
local marshal = techage.IE.require("marshal")
if not sqlite3 then
error("Please install sqlite3 via 'luarocks install lsqlite3'")
end
if not marshal then
error("Please install marshal via 'luarocks install lua-marshal'")
end
local db = sqlite3.open(WP.."/techage_nodedata.sqlite")
local ROW = sqlite3.ROW
-- Prevent use of this db instance.
if sqlite3 then sqlite3 = nil end
db:exec[[
CREATE TABLE mapblocks(id INTEGER PRIMARY KEY, key INTEGER, data BLOB);
CREATE UNIQUE INDEX idx ON mapblocks(key);
]]
local set = db:prepare("INSERT or REPLACE INTO mapblocks VALUES(NULL, ?, ?);")
local get = db:prepare("SELECT * FROM mapblocks WHERE key=?;")
local function set_block(key, data)
set:reset()
set:bind(1, key)
set:bind_blob(2, data)
set:step()
end
local function get_block(key)
get:reset()
get:bind(1, key)
if get:step() == ROW then
return get:get_value(2)
end
end
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
local api = {}
function api.store_mapblock_data(key, mapblock_data)
if use_marshal and mapblock_data then
local data = marshal.encode(mapblock_data)
if data then
set_block(key, data)
end
else
set_block(key, minetest.serialize(mapblock_data))
end
end
function api.get_mapblock_data(key)
local s = get_block(key)
if s then
if s:byte(1) == MAR_MAGIC then
--return marshal.decode(s)
local sts, tbl = pcall(marshal.decode, s)
if not sts then
minetest.log("warning", "[techage] marshal.decode error: " .. dump(tbl))
api.store_mapblock_data(key, {})
return {}
end
return tbl
else
return minetest.deserialize(s)
end
end
api.store_mapblock_data(key, {})
return {}
end
function api.get_node_data(pos)
-- legacy data available?
local s = M(pos):get_string("ta_data")
if s ~= "" then
M(pos):set_string("ta_data", "")
if s:byte(1) == MAR_MAGIC then
return marshal.decode(s)
else
return minetest.deserialize(s)
end
end
return {}
end
function api.freeze_at_shutdown(data)
for key, item in pairs(data) do
api.store_mapblock_data(key, item)
end
end
function api.restore_at_startup()
-- nothing to restore
return {}
end
return api
@@ -0,0 +1,142 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Storage backend for node number mapping via sqlite database
]]--
-- for lazy programmers
local M = minetest.get_meta
local storage = techage.storage
-------------------------------------------------------------------
-- Database
-------------------------------------------------------------------
local MN = minetest.get_current_modname()
local WP = minetest.get_worldpath()
local MAR_MAGIC = 0x8e
if not techage.IE then
error("Please add 'secure.trusted_mods = techage' to minetest.conf!")
end
local sqlite3 = techage.IE.require("lsqlite3")
local marshal = techage.IE.require("marshal")
if not sqlite3 then
error("Please install sqlite3 via 'luarocks install lsqlite3'")
end
if not marshal then
error("Please install marshal via 'luarocks install lua-marshal'")
end
local db = sqlite3.open(WP.."/techage_numbers.sqlite")
local ROW = sqlite3.ROW
-- Prevent use of this db instance.
if sqlite3 then sqlite3 = nil end
db:exec[[
CREATE TABLE numbers(id INTEGER PRIMARY KEY, number INTEGER, x INTEGER, y INTEGER, z INTEGER);
CREATE UNIQUE INDEX idx ON numbers(number);
]]
local set = db:prepare("INSERT or REPLACE INTO numbers VALUES(NULL, ?, ?, ?, ?);")
local get = db:prepare("SELECT * FROM numbers WHERE number=?;")
local function set_block(number, pos)
set:reset()
set:bind(1, number)
set:bind(2, pos.x)
set:bind(3, pos.y)
set:bind(4, pos.z)
set:step()
return true
end
local function get_block(number)
get:reset()
get:bind(1, number)
if get:step() == ROW then
return {x = get:get_value(2), y = get:get_value(3), z = get:get_value(4)}
end
end
local function del_block(number)
db:exec("DELETE FROM numbers WHERE number="..number..";")
end
-------------------------------------------------------------------
-- Migration from mod storage
-------------------------------------------------------------------
local Version = storage:get_int("Version") or 0
local NextNumber = 0
if Version == 0 then
Version = 4
end
if Version == 3 then
Version = 4
NextNumber = storage:get_int("NextNumber")
for i = 1, NextNumber do
local number = tostring(i)
if storage:contains(number) then
local pos = minetest.string_to_pos(storage:get_string(number))
set_block(number, pos)
storage:set_string(number, "")
end
end
elseif Version == 4 then
NextNumber = storage:get_int("NextNumber")
else
error("[] Invalid version number for 'number to pos mapping' table!")
end
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
local api = {}
function api.get_nodepos(number)
return get_block(number)
end
function api.set_nodepos(number, pos)
set_block(number, pos)
end
function api.add_nodepos(pos)
local num = tostring(NextNumber)
NextNumber = NextNumber + 1
storage:set_int("NextNumber", NextNumber)
set_block(num, pos)
return num
end
function api.del_nodepos(number)
del_block(number)
end
-- delete invalid entries
function api.delete_invalid_entries(node_def)
minetest.log("info", "[TechAge] Data maintenance started")
for id, num, x, y, z in db:urows('SELECT * FROM numbers') do
local pos = {x = x, y = y, z = z}
local name = techage.get_node_lvm(pos).name
if not node_def[name] then
del_block(num)
end
end
minetest.log("info", "[TechAge] Data maintenance finished")
end
return api
@@ -0,0 +1,99 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Storage backend for node number mapping via mod storage
]]--
local backend = {}
local storage = techage.storage
-- legacy method
local function deserialize(s)
local tbl = {}
for line in s:gmatch("[^;]+") do
local num, spos = unpack(string.split(line, "="))
tbl[num] = minetest.string_to_pos(spos)
end
return tbl
end
local Version = minetest.deserialize(storage:get_string("Version")) or 3
local NextNumber = 0
if Version == 1 then
Version = 3
local tbl = minetest.deserialize(storage:get_string("Number2Pos")) or {}
NextNumber = minetest.deserialize(storage:get_string("NextNumber")) or 1
for num, pos in pairs(tbl) do
storage:set_string(num, minetest.pos_to_string(pos))
end
storage:set_string("Number2Pos", "")
elseif Version == 2 then
Version = 3
NextNumber = minetest.deserialize(storage:get_string("NextNumber")) or 1
local tbl = deserialize(storage:get_string("Number2Pos"))
for num, pos in pairs(tbl) do
storage:set_string(num, minetest.pos_to_string(pos))
end
storage:set_string("Number2Pos", "")
else
Version = 3
NextNumber = storage:get_int("NextNumber")
end
storage:set_int("NextNumber", NextNumber)
storage:set_int("Version", Version)
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
function backend.get_nodepos(number)
if not type(number) == "string" then return end
return minetest.string_to_pos(storage:get_string(number))
end
function backend.set_nodepos(number, pos)
storage:set_string(number, minetest.pos_to_string(pos))
end
function backend.add_nodepos(pos)
local num = tostring(NextNumber)
NextNumber = NextNumber + 1
storage:set_int("NextNumber", NextNumber)
storage:set_string(num, minetest.pos_to_string(pos))
return num
end
function backend.del_nodepos(number)
storage:set_string(number, "")
end
-- delete invalid entries
function backend.delete_invalid_entries(node_def)
minetest.log("info", "[TechAge] Data maintenance started")
for i = 1, NextNumber do
local number = tostring(i)
if storage:contains(number) then
local pos = backend.get_nodepos(number)
local name = techage.get_node_lvm(pos).name
if not node_def[name] then
backend.del_nodepos(number)
else
minetest.get_meta(pos):set_string("node_number", number)
end
end
end
minetest.log("info", "[TechAge] Data maintenance finished")
end
return backend
@@ -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
}
@@ -0,0 +1,103 @@
--[[
TechAge
=======
Copyright (C) 2019-2024 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Packing functions
]]--
-- for lazy programmers
local S = techage.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local M = minetest.get_meta
-- string/usercode conversion
local function usercode_to_string(tbl)
if tbl and tbl.inventory then
for list_name,list in pairs(tbl.inventory) do
for i,item in ipairs(list) do
tbl.inventory[list_name][i] = item:to_string()
end
end
end
end
local function string_to_usercode(tbl)
if tbl and tbl.inventory then
for list_name,list in pairs(tbl.inventory) do
for i,item in ipairs(list) do
tbl.inventory[list_name][i] = ItemStack(item)
end
end
end
end
-- pack/unpack node nvm data
local function pack_nvm(pos)
if techage.has_nvm(pos) then
local s = minetest.serialize(techage.get_nvm(pos))
techage.del_mem(pos)
return s
end
end
local function unpack_nvm(pos, s)
if s and s ~= "" then
local tbl = minetest.deserialize(s)
local nvm = techage.get_nvm(pos)
for k,v in pairs(tbl) do
nvm[k] = v
end
end
end
-- pack/unpack node metedata
local function pack_meta(pos)
local tbl = M(pos):to_table() or {}
usercode_to_string(tbl)
return minetest.serialize(tbl)
end
local function unpack_meta(pos, s)
if s and s ~= "" then
local tbl = minetest.deserialize(s) or {}
string_to_usercode(tbl)
M(pos):from_table(tbl)
end
end
-------------------------------------------------------------------------------
-- preserve/restore API functions for the assembly tool
-------------------------------------------------------------------------------
function techage.preserve_nodedata(pos)
local smeta = pack_meta(pos)
local snvm = pack_nvm(pos)
return minetest.serialize({smeta = smeta, snvm = snvm})
end
function techage.restore_nodedata(pos, s)
local tbl = minetest.deserialize(s) or {}
unpack_nvm(pos, tbl.snvm)
unpack_meta(pos, tbl.smeta)
end
-- Don't allow placing if node in player inventory has metadata
function techage.cordless_screwdriver_only(pos, placer, itemstack)
local meta = itemstack:get_meta()
if placer and meta and meta:to_table() then
local tbl = meta:to_table()
if tbl.fields and next(tbl.fields) then
minetest.chat_send_player(placer:get_player_name(), S("Use the cordless screwdriver to place it! (item must be in hotbar slot 1)"))
minetest.remove_node(pos)
return true
end
end
return false
end
@@ -0,0 +1,292 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Recipe lib for formspecs
]]--
local S = techage.S
local M = minetest.get_meta
local Recipes = {} -- {rtype = {ouput = {....},...}}
local NormalizedRecipes = {} -- {output = "", items = {...}}
local range = techage.in_range
techage.recipes = {}
local GROUP_ITEMS = {
stone = "default:cobble",
wood = "default:wood",
book = "default:book",
sand = "default:sand",
leaves = "default:leaves",
stick = "default:stick",
tree = "default:tree",
vessel = "vessels:glass_bottle",
wool = "wool:white",
}
local RECIPE = {
output = {name = "", num = 0},
waste = {name = "", num = 0},
input = {
{name = "", num =0},
{name = "", num =0},
{name = "", num =0},
{name = "", num =0},
},
}
local function filter_recipes_based_on_points(recipes, owner)
local ex_points = 0
if owner then
local player = minetest.get_player_by_name(owner)
ex_points = techage.get_expoints(player) or 0
end
local tbl = {}
for _,item in ipairs(recipes) do
if ex_points >= (item.ex_points or 0) then
tbl[#tbl + 1] = item
end
end
return tbl
end
-- Formspec
local function input_string(recipe)
local tbl = {}
for idx, item in ipairs(recipe.input) do
local x = ((idx-1) % 2)
local y = math.floor((idx-1) / 2)
tbl[idx] = techage.item_image(x, y, item.name.." "..item.num)
end
return table.concat(tbl, "")
end
function techage.recipes.get(nvm, rtype, owner)
local recipes = Recipes[rtype] or {}
if owner then
recipes = filter_recipes_based_on_points(recipes, owner)
end
return recipes[nvm.recipe_idx or 1] or recipes[1]
end
-- Add 4 input/output/waste recipe
-- {
-- output = "<item-name> <units>", -- units = 1..n
-- waste = "<item-name> <units>", -- units = 1..n
-- input = { -- up to 4 items
-- "<item-name> <units>",
-- "<item-name> <units>",
-- },
-- }
function techage.recipes.add(rtype, recipe)
if not Recipes[rtype] then
Recipes[rtype] = {}
end
local name, num, output
local item = {input = {}}
for idx = 1,4 do
local inp = recipe.input[idx] or ""
name, num = unpack(string.split(inp, " "))
item.input[idx] = {name = name or "", num = tonumber(num) or 0}
end
if recipe.waste then
name, num = unpack(string.split(recipe.waste, " "))
else
name, num = "", "0"
end
item.waste = {name = name or "", num = tonumber(num) or 0}
name, num = unpack(string.split(recipe.output, " "))
item.output = {name = name or "", num = tonumber(num) or 0}
item.catalyst = recipe.catalyst
item.ex_points = recipe.ex_points or 0
Recipes[rtype][#Recipes[rtype]+1] = item
output = name
techage.recipes.register_craft({
output = recipe.output,
items = recipe.input,
type = rtype,
})
NormalizedRecipes[output] = {
output = recipe.output,
items = recipe.input,
}
end
function techage.recipes.formspec(x, y, rtype, nvm, owner)
local recipes = Recipes[rtype] or {}
recipes = filter_recipes_based_on_points(recipes, owner)
nvm.recipe_idx = range(nvm.recipe_idx or 1, 1, #recipes)
local idx = nvm.recipe_idx
local recipe = recipes[idx] or RECIPE
local output = recipe.output.name.." "..recipe.output.num
local waste = recipe.waste.name.." "..recipe.waste.num
local catalyst = recipe.catalyst and techage.item_image_small(2.05, 0, recipe.catalyst, S("Catalyst")) or ""
return "container["..x..","..y.."]"..
"background[0,0;4,3;techage_form_grey.png]"..
input_string(recipe)..
"image[2,0.7;1,1;techage_form_arrow.png]"..
catalyst..
techage.item_image(2.95, 0, output)..
techage.item_image(2.95, 1, waste)..
"button[0,2;1,1;priv;<<]"..
"button[1,2;1,1;next;>>]"..
"label[1.9,2.2;"..S("Recipe")..": "..idx.."/"..#recipes.."]"..
"container_end[]"
end
function techage.recipes.on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
nvm.recipe_idx = nvm.recipe_idx or 1
if not nvm.running then
if fields.next == ">>" then
nvm.recipe_idx = nvm.recipe_idx + 1
return true
elseif fields.priv == "<<" then
nvm.recipe_idx = nvm.recipe_idx - 1
return true
end
end
end
function techage.recipes.get_recipe(name)
return NormalizedRecipes[name]
end
function techage.recipes.set_recipe(pos, rtype, idx)
local nvm = techage.get_nvm(pos)
if not nvm.running then
local recipes = Recipes[rtype] or {}
idx = tonumber(idx) or 1
nvm.recipe_idx = range(idx, 1, #recipes)
end
end
function techage.recipes.get_default_group_item_name(item_name)
if item_name and item_name:sub(1, 6) == "group:" then
local default_name = GROUP_ITEMS[item_name:sub(7)]
if default_name then
return default_name
end
end
return item_name
end
function techage.recipes.add_group_item(group, default_item_name)
GROUP_ITEMS[group] = default_item_name
end
-------------------------------------------------------------------------------
-- Borrowed from ghaydn
-------------------------------------------------------------------------------
local has_i3 = minetest.get_modpath("i3")
local has_ui = minetest.get_modpath("unified_inventory")
local has_cg = minetest.get_modpath("craftguide")
local function format_i3(input)
local output = {}
for _, entry in ipairs(input) do
local secondput = ""
if type(entry) == "table" then
for _, secondtry in ipairs(entry) do
secondput = secondput..secondtry..","
end
table.insert(output, secondput)
else
table.insert(output, entry)
end
end
return output
end
techage.recipes.register_craft_type = function(name, def)
if has_cg then
local cg_def = {
description = def.description,
icon = def.icon,
}
craftguide.register_craft_type(name, cg_def)
end
if has_i3 then
local i3_def = {
description = def.description,
icon = def.icon,
width = def.width or 3,
height = def.height or 3,
dynamic_display_size = def.dynamic_display_size or nil,
uses_crafting_grid = def.uses_crafting_grid,
}
i3.register_craft_type(name, i3_def)
end
if has_ui then
local ui_def = {
description = def.description,
icon = def.icon,
width = def.width or 3,
height = def.height or 3,
dynamic_display_size = def.dynamic_display_size or nil,
uses_crafting_grid = def.uses_crafting_grid,
}
unified_inventory.register_craft_type(name, ui_def)
end
end
techage.recipes.register_craft = function(def)
if not def.items then
if def.input then
def.items = table.copy(def.input)
elseif def.recipe then
def.items = table.copy(def.recipe)
end
end
if not def.result then
if def.output then def.result = def.output end
end
if has_cg then
local cg_def = {
result = def.result,
type = def.type,
items = def.items,
}
craftguide.register_craft(cg_def)
end
if has_i3 then
local i3_def = {
result = def.result,
type = def.type,
items = format_i3(def.items),
width = def.width or 3,
}
i3.register_craft(i3_def)
end
if has_ui then
local ui_def = {
output = def.result,
type = def.type,
items = def.items,
width = def.width or 3,
height = def.height or 3,
}
unified_inventory.register_craft(ui_def)
end
end
@@ -0,0 +1,74 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Library for shared inventories
]]--
-- for lazy programmers
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local S = techage.S
techage.shared_inv = {}
local hyperloop = techage.hyperloop
local remote_pos = techage.hyperloop.remote_pos
local function copy_inventory_list(from_pos, to_pos, listname)
local inv1 = minetest.get_inventory({type="node", pos=from_pos})
local inv2 = minetest.get_inventory({type="node", pos=to_pos})
inv2:set_list(listname, inv1:get_list(listname))
end
function techage.shared_inv.node_timer(pos, elapsed)
local rmt_pos = remote_pos(pos)
if rmt_pos and techage.is_activeformspec(pos) then
copy_inventory_list(rmt_pos, pos, "main")
return true
end
return false
end
-- Synchronize the client inventory with the server one
function techage.shared_inv.before_inv_access(pos, listname)
if hyperloop.is_client(pos) then
local rmt_pos = remote_pos(pos)
if rmt_pos then
copy_inventory_list(rmt_pos, pos, listname)
return true
end
end
return false
end
-- Synchronize the client inventory with the server one
function techage.shared_inv.after_inv_access(pos, listname)
if hyperloop.is_client(pos) then
local rmt_pos = remote_pos(pos)
if rmt_pos then
copy_inventory_list(pos, rmt_pos, listname)
return true
end
end
return false
end
function techage.shared_inv.on_rightclick(pos, clicker, listname)
if hyperloop.is_client(pos) then
local rmt_pos = remote_pos(pos)
if rmt_pos then
copy_inventory_list(rmt_pos, pos, listname)
techage.set_activeformspec(pos, clicker)
minetest.get_node_timer(pos):start(2)
end
end
end
@@ -0,0 +1,116 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Library for shared inventories
]]--
-- for lazy programmers
local M = minetest.get_meta
local NDEF = function(pos) return minetest.registered_nodes[techage.get_node_lvm(pos).name] or {} end
techage.shared_tank = {}
local liquid = networks.liquid
local hyperloop = techage.hyperloop
local remote_pos = techage.hyperloop.remote_pos
local is_paired = techage.hyperloop.is_paired
local menu = techage.menu
local function formspec(pos)
local ndef = NDEF(pos)
local status = M(pos):get_string("conn_status")
if hyperloop.is_client(pos) or hyperloop.is_server(pos) then
local title = ndef.description .. " " .. status
local nvm = techage.get_nvm(remote_pos(pos))
return techage.liquid.formspec(pos, nvm, title)
else
return menu.generate_formspec(pos, ndef, hyperloop.SUBMENU)
end
end
function techage.shared_tank.node_timer(pos)
if techage.is_activeformspec(pos) and is_paired(pos) then
M(pos):set_string("formspec", formspec(pos))
return true
end
return false
end
function techage.shared_tank.on_rightclick(pos, node, clicker)
--if hyperloop.is_client(pos) then
techage.set_activeformspec(pos, clicker)
minetest.get_node_timer(pos):start(2)
--end
M(pos):set_string("formspec", formspec(pos))
end
function techage.shared_tank.can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
pos = remote_pos(pos)
return techage.liquid.is_empty(pos)
end
function techage.shared_tank.peek_liquid(pos, indir)
if is_paired(pos) then
pos = remote_pos(pos)
local nvm = techage.get_nvm(pos)
return liquid.srv_peek(nvm)
end
end
function techage.shared_tank.take_liquid(pos, indir, name, amount)
if is_paired(pos) then
pos = remote_pos(pos)
local nvm = techage.get_nvm(pos)
amount, name = liquid.srv_take(nvm, name, amount)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return amount, name
end
return 0, name
end
function techage.shared_tank.put_liquid(pos, indir, name, amount)
if is_paired(pos) then
pos = remote_pos(pos)
-- check if it is not powder
local ndef = minetest.registered_craftitems[name] or {}
if not ndef.groups or ndef.groups.powder ~= 1 then
local nvm = techage.get_nvm(pos)
local ndef = NDEF(pos)
local leftover = liquid.srv_put(nvm, name, amount, ndef.liquid.capa)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return leftover
end
end
return amount
end
function techage.shared_tank.untake_liquid(pos, indir, name, amount)
if is_paired(pos) then
pos = remote_pos(pos)
local nvm = techage.get_nvm(pos)
local ndef = NDEF(pos)
local leftover = liquid.srv_put(nvm, name, amount, ndef.liquid.capa)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return leftover
end
return amount
end
techage.shared_tank.formspec = formspec
@@ -0,0 +1,373 @@
--[[
TechAge
=======
Copyright (C) 2019-2025 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
A formspec control to generate formspec strings for machine settings and monitoring
]]--
local S = techage.S
techage.menu = {}
local function index(list, x)
for idx, v in ipairs(list or {}) do
if tostring(v) == x then return idx end
end
return nil
end
local function allow_put(inv, listname, index, stack, player)
local list = inv:get_list(listname)
stack:set_count(1)
inv:set_stack(listname, index, stack)
return 0
end
local function allow_take(inv, listname, index, stack, player)
local list = inv:get_list(listname)
stack:set_count(0)
inv:set_stack(listname, index, stack)
return 0
end
-- generate the formspec string to be placed into a container frame
local function generate_formspec_substring(pos, meta, form_def, player_name, width, no_note)
local tbl = {}
local player_inv_needed = false
width = width or "10"
local xpos1 = tostring(tonumber(width) / 2 - 0.25)
local xpos2 = tostring(tonumber(width) / 2)
local xpos3 = tostring(tonumber(width) / 2 + 0.3)
local xpos4 = tostring(tonumber(width) / 2 + 0.5)
local xsize = tostring(tonumber(width) / 2)
if meta and form_def then
local nvm = techage.get_nvm(pos)
for i,elem in ipairs(form_def) do
local offs = (i - 1) * 0.9 - 0.2
tbl[#tbl+1] = "label[0," .. offs .. ";" .. minetest.formspec_escape(elem.label) .. ":]"
tbl[#tbl+1] = "tooltip[0," .. offs .. ";4,1;" .. elem.tooltip .. "]"
if elem.type == "label" then
-- none
elseif elem.type == "number" then
local val = elem.default
if meta:contains(elem.name) then
val = meta:get_int(elem.name)
end
if nvm.running or techage.is_running(nvm) then
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. val .. "]"
else
tbl[#tbl+1] = "field[" .. xpos2 .. "," .. (offs+0.2) .. ";" .. xpos3 .. ",1;" .. elem.name .. ";;" .. val .. "]"
end
elseif elem.type == "numbers" then
local val = elem.default
if meta:contains(elem.name) then
val = meta:get_string(elem.name)
end
if nvm.running or techage.is_running(nvm) then
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. val .. "]"
else
tbl[#tbl+1] = "field[" .. xpos2 .. "," .. (offs+0.2) .. ";" .. xpos3 .. ",1;" .. elem.name .. ";;" .. val .. "]"
end
elseif elem.type == "float" then
local val = elem.default
if meta:contains(elem.name) then
val = tonumber(meta:get_string(elem.name)) or 0
end
if nvm.running or techage.is_running(nvm) then
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. val .. "]"
else
tbl[#tbl+1] = "field[" .. xpos2 .. "," .. (offs+0.2) .. ";" .. xpos3 .. ",1;" .. elem.name .. ";;" .. val .. "]"
end
elseif elem.type == "ascii" then
local val = elem.default
if meta:contains(elem.name) then
val = meta:get_string(elem.name)
end
if nvm.running or techage.is_running(nvm) then
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. minetest.formspec_escape(val) .. "]"
else
tbl[#tbl+1] = "field[" .. xpos2 .. "," .. (offs+0.2) .. ";" .. xpos3 .. ",1;" .. elem.name .. ";;" .. minetest.formspec_escape(val) .. "]"
end
elseif elem.type == "const" then
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. elem.value .. "]"
elseif elem.type == "output" then
local val
if elem.on_value then
val = elem.on_value(pos) or ""
else
val = nvm[elem.name] or meta:get_string(elem.name) or ""
end
if tonumber(val) then
val = techage.round(val)
end
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. val .. "]"
elseif elem.type == "dropdown" then
if nvm.running or techage.is_running(nvm) then
local val = elem.default or ""
if meta:contains(elem.name) then
val = meta:get_string(elem.name) or ""
if elem.values then
local idx = index(elem.values, val) or 1
local l = elem.choices:split(",")
val = l[idx]
end
end
tbl[#tbl+1] = "label[" .. xpos1 .. "," .. offs .. ";" .. val .. "]"
elseif elem.on_dropdown then -- block provides a specific list of choice elements
local val = elem.default
if meta:contains(elem.name) then
val = meta:get_string(elem.name) or ""
end
local choices = elem.on_dropdown(pos)
local l = choices:split(",")
local idx = index(l, val) or 1
tbl[#tbl+1] = "dropdown[" .. xpos1 .. "," .. (offs) .. ";" .. xpos4 .. ",1.4;" .. elem.name .. ";" .. choices .. ";" .. idx .. "]"
else
local val = elem.default
if meta:contains(elem.name) then
val = meta:get_string(elem.name) or ""
end
local idx
if elem.values then
idx = index(elem.values, val) or 1
else
local l = elem.choices:split(",")
idx = index(l, val) or 1
end
tbl[#tbl+1] = "dropdown[" .. xpos1 .. "," .. (offs) .. ";" .. xpos4 .. ",1.4;" .. elem.name .. ";" .. elem.choices .. ";" .. idx .. "]"
end
elseif elem.type == "items" then -- inventory
if elem.size then
tbl[#tbl+1] = "list[detached:" .. minetest.formspec_escape(player_name) .. "_techage_wrench_menu;cfg;" .. xpos1 .. "," .. offs .. ";" .. elem.size .. ",1;]"
else
tbl[#tbl+1] = "list[detached:" .. minetest.formspec_escape(player_name) .. "_techage_wrench_menu;cfg;" .. xpos1 .. "," .. offs .. ";" .. elem.width .. "," .. elem.height .. ";]"
end
player_inv_needed = true
end
end
if not no_note and (nvm.running or techage.is_running(nvm)) then
local offs = #form_def * 0.9 - 0.3
tbl[#tbl+1] = "label[0," .. offs .. ";" .. S("Note: You can't change any values while the block is running!") .. "]"
end
end
return player_inv_needed, table.concat(tbl, "")
end
local function value_check(elem, value, player_name)
if elem.check then
return elem.check(value, player_name)
end
return value ~= nil
end
local function evaluate_data(pos, meta, form_def, fields, player_name)
local res = true
if meta and form_def then
local nvm = techage.get_nvm(pos)
if nvm.running or techage.is_running(nvm) then
return res
end
for idx,elem in ipairs(form_def) do
if elem.type == "number" then
if fields[elem.name] then
if fields[elem.name] == "" then
meta:set_string(elem.name, "")
elseif fields[elem.name]:find("^[%d ]+$") then
local val = tonumber(fields[elem.name])
if value_check(elem, val, player_name) then
meta:set_int(elem.name, val)
--print("set_int", elem.name, val)
else
res = false
end
else
res = false
end
end
elseif elem.type == "numbers" then
if fields[elem.name] then
if fields[elem.name] == "" then
meta:set_string(elem.name, "")
elseif fields[elem.name]:find("^[%d ]+$") and
value_check(elem, fields[elem.name], player_name) then
meta:set_string(elem.name, fields[elem.name])
else
res = false
end
end
elseif elem.type == "float" then
if fields[elem.name] == ""then
meta:set_string(elem.name, "")
elseif fields[elem.name] then
local val = tonumber(fields[elem.name])
if val and value_check(elem, val, player_name) then
meta:set_string(elem.name, val)
else
res = false
end
end
elseif elem.type == "ascii" then
if fields[elem.name] == ""then
meta:set_string(elem.name, "")
elseif fields[elem.name] then
if value_check(elem, fields[elem.name], player_name) then
meta:set_string(elem.name, fields[elem.name])
else
res = false
end
end
elseif elem.type == "dropdown" then
if fields[elem.name] ~= nil then
if elem.values then
local l = elem.choices:split(",")
local idx = index(l, fields[elem.name]) or 1
local text = elem.values[idx]
meta:set_string(elem.name, text)
else
meta:set_string(elem.name, fields[elem.name])
end
end
elseif elem.type == "items" and player_name then
local inv_name = minetest.formspec_escape(player_name) .. "_techage_wrench_menu"
local dinv = minetest.get_inventory({type = "detached", name = inv_name})
local ninv = minetest.get_inventory({type = "node", pos = pos})
if dinv and ninv then
for i = 1, ninv:get_size("cfg") do
ninv:set_stack("cfg", i, dinv:get_stack("cfg", i))
end
end
end
end
end
return res
end
function techage.menu.generate_formspec(pos, ndef, form_def, player_name)
local meta = minetest.get_meta(pos)
local number = techage.get_node_number(pos) or "-"
local mem = techage.get_mem(pos)
mem.star = ((mem.star or 0) + 1) % 2
local star = mem.star == 1 and "*" or ""
if player_name then
local inv_name = minetest.formspec_escape(player_name) .. "_techage_wrench_menu"
minetest.create_detached_inventory(inv_name, {
allow_put = allow_put,
allow_take = allow_take})
local dinv = minetest.get_inventory({type = "detached", name = inv_name})
local ninv = minetest.get_inventory({type = "node", pos = pos})
if dinv and ninv then
dinv:set_size('cfg', ninv:get_size("cfg"))
for i = 1, ninv:get_size("cfg") do
dinv:set_stack("cfg", i, ninv:get_stack("cfg", i))
end
end
end
if meta and number and ndef and form_def then
local title = ndef.description .. " (" .. number .. ")"
local player_inv_needed, text = generate_formspec_substring(pos, meta, form_def, player_name)
local buttons
if player_inv_needed then
buttons = "button[0.5,6.2;3,1;refresh;" .. S("Refresh") .. "]" ..
"button_exit[3.5,6.2;3,1;cancel;" .. S("Cancel") .. "]" ..
"button[6.5,6.2;3,1;save;" .. S("Save") .. "]" ..
"list[current_player;main;1,7.2;8,2;]"
else
buttons = "button[0.5,8.4;3,1;refresh;" .. S("Refresh") .. "]" ..
"button_exit[3.5,8.4;3,1;cancel;" .. S("Cancel") .. "]" ..
"button[6.5,8.4;3,1;save;" .. S("Save") .. "]"
end
if #form_def > 8 then
local size = (#form_def * 10) - 60
return "size[10,9]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
"box[0,-0.1;9.8,0.5;#c6e8ff]" ..
"label[0.2,-0.1;" .. minetest.colorize( "#000000", title) .. "]" ..
"label[9.5,-0.1;" .. minetest.colorize( "#000000", star) .. "]" ..
"scrollbaroptions[max=" .. size .. "]" ..
"scrollbar[9.4,0.6;0.4,7.7;vertical;wrenchmenu;]" ..
"scroll_container[0,1;12,9;wrenchmenu;vertical;]" ..
text ..
"scroll_container_end[]" ..
buttons
else
return "size[10,9]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
"box[0,-0.1;9.8,0.5;#c6e8ff]" ..
"label[0.2,-0.1;" .. minetest.colorize( "#000000", title) .. "]" ..
"label[9.5,-0.1;" .. minetest.colorize( "#000000", star) .. "]" ..
"container[0,1]" ..
text ..
"container_end[]" ..
buttons
end
end
return ""
end
function techage.menu.generate_formspec_container(pos, ndef, form_def, ypos, width)
local meta = minetest.get_meta(pos)
local number = techage.get_node_number(pos) or "-"
local mem = techage.get_mem(pos)
mem.star = ((mem.star or 0) + 1) % 2
local star = mem.star == 1 and " *" or " "
local bttn_width = (width - 0.3) / 3
if meta and number and ndef and form_def then
local _, text = generate_formspec_substring(pos, meta, form_def, "", width, true)
local yoffs = math.min(#form_def, 8) * 0.9 + 0.7
local buttons = "button[0.1," .. yoffs .. ";" .. bttn_width .. ",1;refresh;" .. S("Refresh") .. star .. "]" ..
"button_exit[" .. (bttn_width + 0.2) .. "," .. yoffs .. ";" .. bttn_width .. ",1;cancel;" .. S("Cancel") .. "]" ..
"button[" .. (2 * bttn_width + 0.3) .. "," .. yoffs .. ";" .. bttn_width .. ",1;save;" .. S("Save") .. "]"
if #form_def > 8 then
local size = (#form_def * 10) - 60
return "container[0," .. ypos .. "]" ..
"box[0,0;" .. width .. "," .. (yoffs + 0.8) .. ";#395c74]"..
"scrollbaroptions[max=" .. size .. "]" ..
"scrollbar[9.4,0.6;0.4,7.7;vertical;wrenchmenu;]" ..
"scroll_container[0,1;12,9;wrenchmenu;vertical;]" ..
text ..
"scroll_container_end[]" ..
buttons ..
"container_end[]"
else
return "container[0," .. ypos .. "]" ..
"container[0,1]" ..
"box[-0.1,-0.3;" .. (width + 0.2) .. "," .. (yoffs + 0.3) .. ";#395c74]"..
text ..
"container_end[]" ..
buttons ..
"container_end[]"
end
end
return ""
end
function techage.menu.eval_input(pos, form_def, fields, player_name)
if fields.save or fields.key_enter_field then
local meta = minetest.get_meta(pos)
evaluate_data(pos, meta, form_def, fields, player_name)
end
return fields.refresh or fields.save or fields.key_enter_field
end
function techage.dropdown_index(sChoices, selected_value)
local l = sChoices:split(",")
return index(l, selected_value) or 1
end
@@ -0,0 +1,184 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
For tupe/pipe blocks with teleport support
]]--
-- for lazy programmers
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local NDEF = function(pos) return minetest.registered_nodes[techage.get_node_lvm(pos).name] or {} end
local M = minetest.get_meta
local S = techage.S
local menu = techage.menu
techage.teleport = {}
local PairingList = {} -- {owner = {node_type = {channel = pos}}}
local function get_pairing_table1(meta)
local owner = meta:get_string("owner")
local node_type = meta:get_string("tele_node_type")
PairingList[owner] = PairingList[owner] or {}
PairingList[owner][node_type] = PairingList[owner][node_type] or {}
return PairingList[owner][node_type]
end
local function get_pairing_table2(oldmetadata)
local owner = oldmetadata.fields.owner
local node_type = oldmetadata.fields.tele_node_type
PairingList[owner] = PairingList[owner] or {}
PairingList[owner][node_type] = PairingList[owner][node_type] or {}
return PairingList[owner][node_type]
end
local function get_free_server_list(pos)
local tbl = {""}
for name, pos in pairs(get_pairing_table1(M(pos))) do
table.insert(tbl, name)
end
return table.concat(tbl, ",")
end
local TELE_MENU = {
{
type = "label",
label = S("Enter a block name or select an existing one"),
tooltip = "",
name = "l1",
},
{
type = "ascii",
name = "conn_name",
label = S("Block name"),
tooltip = S("Connection name for this block"),
default = "",
},
{
type = "dropdown",
choices = "",
on_dropdown = get_free_server_list,
name = "remote_name",
label = S("Remote name"),
tooltip = S("Connection name of the remote block"),
},
{
type = "output",
label = S("Status"),
tooltip = S("Connection status"),
name = "status",
default = "",
},
}
function techage.teleport.formspec(pos)
local ndef = NDEF(pos)
return menu.generate_formspec(pos, ndef, TELE_MENU)
end
local function store_connection(pos, peer_pos)
local meta = M(pos)
local status = S("connected to") .. " " .. P2S(peer_pos)
meta:set_string("tele_status", status)
meta:set_string("tele_peer_pos", P2S(peer_pos))
meta:set_string("formspec", "")
end
local function get_conn_name(pos)
local name = M(pos):get_string("conn_name")
if name == "" then
return "'<no-name>'"
end
return "'" .. name .. "'"
end
function techage.teleport.update_status(pos)
local meta = M(pos)
local peer_pos = S2P(meta:get_string("tele_peer_pos"))
if peer_pos then
local status = S("Block @1 connected to @2 at pos. @3", get_conn_name(pos), get_conn_name(peer_pos), P2S(peer_pos))
meta:set_string("tele_status", status)
end
end
function techage.teleport.prepare_pairing(pos, node_type, status)
local meta = M(pos)
if node_type then
meta:set_string("tele_node_type", node_type)
end
status = status or S("not connected")
meta:set_string("tele_status", status)
meta:set_string("tele_peer_pos", "")
meta:set_string("formspec", techage.teleport.formspec(pos))
end
function techage.teleport.stop_pairing(pos, oldmetadata)
-- disconnect peer node
if oldmetadata and oldmetadata.fields then
if oldmetadata.fields.tele_peer_pos then
local peer_pos = S2P(oldmetadata.fields.tele_peer_pos)
local meta = M(peer_pos)
if meta:get_string("conn_name") ~= "" then -- Server
local tbl = get_pairing_table1(meta)
tbl[meta:get_string("conn_name")] = peer_pos
techage.teleport.prepare_pairing(peer_pos, nil, S("server not connected"))
else
techage.teleport.prepare_pairing(peer_pos)
end
elseif oldmetadata.fields.conn_name then
local tbl = get_pairing_table2(oldmetadata)
tbl[oldmetadata.fields.conn_name] = nil
end
end
end
function techage.teleport.is_connected(pos)
return M(pos):get_string("tele_peer_pos") ~= ""
end
function techage.teleport.get_remote_pos(pos)
local s = M(pos):get_string("tele_peer_pos")
if s ~= "" then
return S2P(s)
end
end
function techage.teleport.after_formspec(pos, player, fields, max_dist, ex_points)
if techage.get_expoints(player) >= ex_points then
if techage.menu.eval_input(pos, TELE_MENU, fields) then
if not techage.teleport.is_connected(pos) then
local meta = M(pos)
if fields.remote_name ~= "" then -- Client
local tbl = get_pairing_table1(meta)
local peer_pos = tbl[fields.remote_name]
if peer_pos then
if vector.distance(pos, peer_pos) <= max_dist then
tbl[fields.remote_name] = nil
store_connection(pos, peer_pos)
store_connection(peer_pos, pos)
M(pos):set_string("status", S("Connected"))
else
M(pos):set_string("status", S("Distance > @1 blocks", max_dist))
meta:set_string("formspec", techage.teleport.formspec(pos))
end
end
elseif fields.conn_name ~= "" then -- Server
local tbl = get_pairing_table1(meta)
tbl[fields.conn_name] = pos
techage.teleport.prepare_pairing(pos, nil, S("server not connected"))
end
end
end
else
M(pos):set_string("status", S("Ex-points missing (@1 < @2)", techage.get_expoints(player), ex_points))
M(pos):set_string("formspec", techage.teleport.formspec(pos))
end
end
@@ -0,0 +1,57 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tube wall entry
]]--
local S = techage.S
local Tube = techage.Tube
minetest.register_node("techage:tube_wall_entry", {
description = S("Tube Wall Entry"),
tiles = {
-- up, down, right, left, back, front
"basic_materials_concrete_block.png",
"basic_materials_concrete_block.png",
"basic_materials_concrete_block.png",
"basic_materials_concrete_block.png",
"basic_materials_concrete_block.png^techage_tube_hole.png",
"basic_materials_concrete_block.png^techage_tube_hole.png",
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
if not Tube: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)
Tube:after_dig_tube(pos, oldnode, oldmetadata)
end,
paramtype2 = "facedir", -- important!
on_rotate = screwdriver.disallow, -- important!
groups = {crumbly = 2, cracky = 2, snappy = 2},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
minetest.register_craft({
output = "techage:tube_wall_entry",
recipe = {
{"", "techage:tubeS", ""},
{"", "basic_materials:concrete_block", ""},
{"", "",""},
},
})
@@ -0,0 +1,148 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tubes based on tubelib2
]]--
local S = techage.S
-- used for registered nodes
techage.KnownNodes = {
["techage:tubeS"] = true,
["techage:tubeA"] = true,
["techage:ta4_tubeS"] = true,
["techage:ta4_tubeA"] = true,
}
local Tube = tubelib2.Tube:new({
-- North, East, South, West, Down, Up
dirs_to_check = {1,2,3,4,5,6},
max_tube_length = 200,
show_infotext = false,
primary_node_names = {
"techage:tubeS", "techage:tubeA",
"techage:ta4_tubeS", "techage:ta4_tubeA",
"techage:tube_wall_entry",
},
after_place_tube = function(pos, param2, tube_type, num_tubes, tbl)
local name = minetest.get_node(pos).name
if name == "techage:tubeS" or name == "techage:tubeA" then
minetest.swap_node(pos, {name = "techage:tube"..tube_type, param2 = param2})
elseif name == "techage:tube_wall_entry" then
minetest.swap_node(pos, {name = "techage:tube_wall_entry", param2 = param2})
else
minetest.swap_node(pos, {name = "techage:ta4_tube"..tube_type, param2 = param2})
end
end,
})
techage.Tube = Tube
minetest.register_node("techage:tubeS", {
description = S("TechAge Tube"),
tiles = { -- Top, base, right, left, front, back
"techage_tube_tube.png^[transformR90",
"techage_tube_tube.png^[transformR90",
"techage_tube_tube.png",
"techage_tube_tube.png",
"techage_tube_hole.png",
"techage_tube_hole.png",
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
if not Tube: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)
Tube:after_dig_tube(pos, oldnode, oldmetadata)
end,
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-2/8, -2/8, -4/8, 2/8, 2/8, 4/8},
},
},
selection_box = {
type = "fixed",
fixed = { -1/4, -1/4, -1/2, 1/4, 1/4, 1/2 },
},
collision_box = {
type = "fixed",
fixed = { -1/4, -1/4, -1/2, 1/4, 1/4, 1/2 },
},
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy=2, cracky=3},
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("techage:tubeA", {
description = S("TechAge Tube"),
tiles = { -- Top, base, right, left, front, back
"techage_tube_knee2.png",
"techage_tube_hole2.png^[transformR180",
"techage_tube_knee.png^[transformR270",
"techage_tube_knee.png",
"techage_tube_knee2.png",
"techage_tube_hole2.png",
},
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Tube:after_dig_tube(pos, oldnode, oldmetadata)
end,
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-2/8, -4/8, -2/8, 2/8, 2/8, 2/8},
{-2/8, -2/8, -4/8, 2/8, 2/8, -2/8},
},
},
selection_box = {
type = "fixed",
fixed = { -1/4, -1/2, -1/2, 1/4, 1/4, 1/4 },
},
collision_box = {
type = "fixed",
fixed = { -1/4, -1/2, -1/2, 1/4, 1/4, 1/4 },
},
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy=2, cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_wood_defaults(),
drop = "techage:tubeS",
})
minetest.register_craft({
output = "techage:tubeS 4",
recipe = {
{"default:steel_ingot", "", "group:wood"},
{"", "group:wood", ""},
{"group:wood", "", "default:tin_ingot"},
},
})
@@ -0,0 +1,120 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tubes in TA4 design based on tubelib2
]]--
local Tube = techage.Tube
local S = techage.S
minetest.register_node("techage:ta4_tubeS", {
description = S("TA4 Tube"),
tiles = { -- Top, base, right, left, front, back
"techage_tubeta4_tube.png^[transformR90",
"techage_tubeta4_tube.png^[transformR90",
"techage_tubeta4_tube.png",
"techage_tubeta4_tube.png",
"techage_tube_hole.png",
"techage_tube_hole.png",
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
if not Tube: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)
Tube:after_dig_tube(pos, oldnode, oldmetadata)
end,
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-2/8, -2/8, -4/8, 2/8, 2/8, 4/8},
},
},
selection_box = {
type = "fixed",
fixed = { -1/4, -1/4, -1/2, 1/4, 1/4, 1/2 },
},
collision_box = {
type = "fixed",
fixed = { -1/4, -1/4, -1/2, 1/4, 1/4, 1/2 },
},
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy=2, cracky=3},
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("techage:ta4_tubeA", {
description = S("TA4 Tube"),
tiles = { -- Top, base, right, left, front, back
"techage_tubeta4_knee2.png",
"techage_tubeta4_hole2.png^[transformR180",
"techage_tubeta4_knee.png^[transformR270",
"techage_tubeta4_knee.png",
"techage_tubeta4_knee2.png",
"techage_tubeta4_hole2.png",
},
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Tube:after_dig_tube(pos, oldnode, oldmetadata)
end,
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-2/8, -4/8, -2/8, 2/8, 2/8, 2/8},
{-2/8, -2/8, -4/8, 2/8, 2/8, -2/8},
},
},
selection_box = {
type = "fixed",
fixed = { -1/4, -1/2, -1/2, 1/4, 1/4, 1/4 },
},
collision_box = {
type = "fixed",
fixed = { -1/4, -1/2, -1/2, 1/4, 1/4, 1/4 },
},
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy=2, cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_wood_defaults(),
drop = "techage:ta4_tubeS",
})
minetest.register_craft({
output = "techage:ta4_tubeS 6",
recipe = {
{"dye:blue", "", "basic_materials:plastic_sheet"},
{"", "basic_materials:plastic_sheet", ""},
{"basic_materials:plastic_sheet", "", "techage:aluminum"},
},
})
techage.TA4tubes = {
["techage:ta4_tubeS"] = true,
["techage:ta4_tubeA"] = true,
}
@@ -0,0 +1,86 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Wind turbine helper function
]]--
local S = techage.S
local P = minetest.string_to_pos
local M = minetest.get_meta
local OCEAN = "ocean"
if minetest.global_exists("asuna") then
OCEAN = "below"
end
local function chat_message(player_name, msg)
if player_name then
minetest.chat_send_player(player_name, S("[TA4 Wind Turbine]").." "..msg)
end
return false, msg
end
-- num_turbines is the mx number of valid wind turbines. In the case of a tool
-- it should be 0, in case of the rotor: 1
function techage.valid_place_for_windturbine(pos, player_name, num_turbines)
local pos1, pos2, num
-- Check if occean (only for tool)
if num_turbines == 0 and pos.y ~= 1 then
return chat_message(player_name, S("This is not the surface of the ocean!"))
end
local node = minetest.get_node(pos)
if num_turbines == 0 and node.name ~= "default:water_source" then
return chat_message(player_name, S("This is no ocean water!"))
end
local data = minetest.get_biome_data({x=pos.x, y=-2, z=pos.z})
if data then
local name = minetest.get_biome_name(data.biome)
if not string.find(name, OCEAN) then
return chat_message(player_name, S("This is a").." "..name.." "..S("biome and no ocean!"))
end
end
-- check the space over ocean
pos1 = {x=pos.x-20, y=2, z=pos.z-20}
pos2 = {x=pos.x+20, y=22, z=pos.z+20}
num = #minetest.find_nodes_in_area(pos1, pos2, {"air", "ignore"})
if num < (41 * 41 * 21 * 0.9) then
techage.mark_region(player_name, pos1, pos2, "")
return chat_message(player_name,
S("Here is not enough wind\n(A free air space of 41x41x21 m is necessary)!"))
end
-- Check for water surface (occean)
pos1 = {x=pos.x-20, y=1, z=pos.z-20}
pos2 = {x=pos.x+20, y=1, z=pos.z+20}
num = #minetest.find_nodes_in_area(pos1, pos2,
{"default:water_source", "default:water_flowing", "ignore"})
if num < (41*41 * 0.8) then
techage.mark_region(player_name, pos1, pos2, "")
return chat_message(player_name, S("Here is not enough water (41x41 m)!"))
end
-- Check for next wind turbine
pos1 = {x=pos.x-13, y=2, z=pos.z-13}
pos2 = {x=pos.x+13, y=22, z=pos.z+13}
num = #minetest.find_nodes_in_area(pos1, pos2, {"techage:ta4_wind_turbine"})
if num > num_turbines then
techage.mark_region(player_name, pos1, pos2, "")
return chat_message(player_name, S("The next wind turbines is too close!"))
end
if num_turbines == 0 then
chat_message(player_name, minetest.pos_to_string(pos).." "..
S("is a suitable place for a wind turbine!"))
end
return true, "ok"
end