techage_modpack/techage/basis/lib.lua
2022-01-04 19:43:30 +01:00

550 lines
14 KiB
Lua

--[[
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]
end
function techage.param2_turn_left(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[1]
end
function techage.param2_turn_right(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[2]
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
-- allowed for digging
local RegisteredNodesToBeDug = {}
function techage.register_node_to_be_dug(name)
RegisteredNodesToBeDug[name] = true
end
-- 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
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
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
--
-- Functions used to hide electric cable and biogas pipes
--
-- Overridden method of tubelib2!
function techage.get_primary_node_param2(pos, dir)
local npos = vector.add(pos, tubelib2.Dir6dToVector[dir or 0])
local param2 = M(npos):get_int("tl2_param2")
if param2 ~= 0 then
return param2, npos
end
end
-- Overridden method of tubelib2!
function techage.is_primary_node(pos, dir)
local npos = vector.add(pos, tubelib2.Dir6dToVector[dir or 0])
local param2 = M(npos):get_int("tl2_param2")
return 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
-- returns true, if node can be dug, otherwise false
function techage.can_node_dig(node, ndef)
if RegisteredNodesToBeDug[node.name] then
return true
end
if not ndef then return false end
if node.name == "ignore" then return false end
if node.name == "air" then return true end
if ndef.buildable_to == true then return true end
if ndef.diggable == false then return false end
if ndef.after_dig_node then return false end
-- add it to the white list
RegisteredNodesToBeDug[node.name] = true
return true
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.mydump(o, indent, nested, level)
local t = type(o)
if not level and t == "userdata" then
-- when userdata (e.g. player) is passed directly, print its metatable:
return "userdata metatable: " .. techage.mydump(getmetatable(o))
end
if t ~= "table" then
return basic_dump(o)
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
return "<circular reference>"
end
nested[o] = true
indent = " "
level = level or 1
local t = {}
local dumped_indexes = {}
for i, v in ipairs(o) do
t[#t + 1] = techage.mydump(v, indent, nested, level + 1)
dumped_indexes[i] = true
end
for k, v in pairs(o) do
if not dumped_indexes[k] then
if type(k) ~= "string" or not is_valid_identifier(k) then
k = "["..techage.mydump(k, indent, nested, level + 1).."]"
end
v = techage.mydump(v, indent, nested, level + 1)
t[#t + 1] = k.." = "..v
end
end
nested[o] = nil
if indent ~= "" then
local indent_str = string.rep(indent, level)
local end_indent_str = string.rep(indent, level - 1)
return string.format("{%s%s%s}",
indent_str,
table.concat(t, ","..indent_str),
end_indent_str)
end
return "{"..table.concat(t, ", ").."}"
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 "label["..x..","..y..";"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
-------------------------------------------------------------------------------
-- Terminal history buffer
-------------------------------------------------------------------------------
local BUFFER_DEPTH = 10
function techage.historybuffer_add(pos, s)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
if #s > 2 then
table.insert(mem.hisbuf, s)
if #mem.hisbuf > BUFFER_DEPTH then
table.remove(mem.hisbuf, 1)
end
mem.hisbuf_idx = #mem.hisbuf + 1
end
end
function techage.historybuffer_priv(pos)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
mem.hisbuf_idx = mem.hisbuf_idx or 1
mem.hisbuf_idx = math.max(1, mem.hisbuf_idx - 1)
return mem.hisbuf[mem.hisbuf_idx]
end
function techage.historybuffer_next(pos)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
mem.hisbuf_idx = mem.hisbuf_idx or 1
mem.hisbuf_idx = math.min(#mem.hisbuf, mem.hisbuf_idx + 1)
return mem.hisbuf[mem.hisbuf_idx]
end
-------------------------------------------------------------------------------
-- Player TA5 Experience Points
-------------------------------------------------------------------------------
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
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