mods that hopefully work for a TechAge-based pack
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user