comboblock/init.lua
Sirrobzeroone 18de64dd2e In game help letter
Added a quick 1 page "letter" which tries to show functionality using pictures only
2022-06-15 20:27:22 +10:00

540 lines
24 KiB
Lua
Executable File

-------------------------------------------------------------------------------------
-- _________ ___. __________.__ __ --
-- \_ ___ \ ____ _____\_ |__ ____\______ \ | ____ ____ | | __ --
-- / \ \/ / _ \ / \| __ \ / _ \| | _/ | / _ \_/ ___\| |/ / --
-- \ \___( <_> ) Y Y \ \_\ ( <_> ) | \ |_( <_> ) \___| < --
-- \______ /\____/|__|_| /___ /\____/|______ /____/\____/ \___ >__|_ \ --
-- \/ \/ \/ \/ \/ \/ --
-- --
-- Orginally written/created by Pithydon/Pithy --
-- Version 5.5.0.1 --
-- first 3 numbers version of minetest created for, --
-- last digit mod version for MT version --
-------------------------------------------------------------------------------------
----------------------------
-- Settings --
----------------------------
local S = minetest.get_translator(minetest.get_current_modname())
local cs = tonumber(minetest.settings:get("comboblock_scale")) or 16
local node_count = 0
local existing_node_count = 0
local to_many_nodes = false
comboblock = {}
local m_name = minetest.get_current_modname()
local m_path = minetest.get_modpath(m_name)
dofile(m_path.. "/i_comboblock_letter.lua" )
----------------------------
-- Functions --
----------------------------
------------------------------
-- Group retrieval function --
------------------------------
--by blert2112 minetest forum
local function registered_nodes_by_group(groupname)
local result = {}
for name, def in pairs(minetest.registered_nodes) do
node_count = node_count + 1
if def.groups[groupname] then
result[#result+1] = name
end
end
return result
end
----------------------------
-- Add Lowpart and Resize --
----------------------------
-- Add "^[lowpart:50:" and resize to all image names
-- against source node for V2 (bottoms)
function add_lowpart(tiles)
local name_split = string.split(tiles.name,"^")
local new_name = ""
local i = 1
while i <= #name_split do
if string.sub(name_split[i],1,1) == "[" then
if name_split[i] =="[transformR90" then -- remove the rotate 90's
new_name = new_name.."^[transformR180FY"..name_split[i]
else
new_name = new_name.."^"..name_split[i] -- catch coloring etc
end
else
new_name = new_name..
"^[lowpart:50:"..name_split[i].. -- overlay lower 50%
"\\^[resize\\:"..cs.."x"..cs -- resize image to comboblock scale
end
i=i+1
end
return new_name -- Output Single image eg ^[lowpart:50:default_cobble.png
end -- Output Two or more image eg ^[lowpart:50:default_cobble.png^[lowpart:50:cracked_cobble.png
-------------------------
-- Get Comboblock Name --
-------------------------
local function cb_get_name(pla_tar,placer,node_c_name) -- pas == pot_short_axis
if not pla_tar.err then
local first_node_name = pla_tar[1]
local second_node_name = node_c_name
if pla_tar[1] == "clicked" then
first_node_name = node_c_name
second_node_name = pla_tar[1]
end
local output = "comboblock:"..first_node_name:split(":")[2].."_onc_"..second_node_name:split(":")[2]
return output
else
minetest.chat_send_player(placer:get_player_name(), pla_tar.err)
end
end
------------------------
-- Comboblock Raycast --
------------------------
local function combo_raycast(placer)
-- calculation of eye position ripped from builtins 'pointed_thing_to_face_pos'
local placer_pos = placer:get_pos()
local eye_height = placer:get_properties().eye_height
local eye_offset = placer:get_eye_offset()
placer_pos.y = placer_pos.y + eye_height
placer_pos = vector.add(placer_pos, eye_offset)
-- get wielded item range 5 is engine default
-- order tool/item range >> hand_range >> fallback 5
local tool_range = placer:get_wielded_item():get_definition().range or nil
local hand_range
for key, val in pairs(minetest.registered_items) do
if key == "" then
hand_range = val.range or nil
end
end
local wield_range = tool_range or hand_range or 5
-- determine ray end position
local look_dir = placer:get_look_dir()
look_dir = vector.multiply(look_dir, wield_range)
local end_pos = vector.add(look_dir, placer_pos)
-- get pointed_thing
local ray = {}
local ray = minetest.raycast(placer_pos, end_pos, false, false)
local ray_pt = ray:next()
return ray_pt
end
--------------------------
-- Comboblock Give Drop --
--------------------------
function comboblock.give_drop(digger, item)
if not creative.is_enabled_for(digger:get_player_name()) then
local inv = digger:get_inventory()
if inv:room_for_item("main", ItemStack(item.." ".. 1)) then
inv:add_item("main", ItemStack(item.." ".. 1))
else
local pos = digger:get_pos()
minetest.item_drop(ItemStack(item.." ".. 1), digger, pos)
end
end
end
--------------------
-- After Dig Node --
--------------------
local function cb_after_dig_node(pos, oldnode, oldmetadata, digger)
-- I know weird I put the node back but otherwise we cant raycast and get exact pos
-- This avoids having to alter on_dig which is not recommended.
minetest.swap_node(pos, {name = oldnode.name, param2 = oldnode.param2})
local pointed = combo_raycast(digger)
local point = vector.subtract(pointed.intersection_point,pointed.under)
local normal = pointed.intersection_normal
local nor_string = tostring(math.abs(normal.x)..math.abs(normal.y)..math.abs(normal.z))
local exact_nor_string = tostring(normal.x..normal.y..normal.z)
local v1 = minetest.registered_nodes[oldnode.name].comboblock_v1
local v2 = minetest.registered_nodes[oldnode.name].comboblock_v2
-- always lose this thread: https://forum.minetest.net/viewtopic.php?f=47&t=24188
-- converts axis and param2 into useful values for removal/swap
local comboblock_p2_axis = {
["100"] = {[0] = {"hb",22,0},[1] = {"vl",10,4 },[2] = {"vr",4 ,10},[3] = {"bb",13,13},[4] = {"bt",13,13},[5] = {"ht",0,22}, ["h"] = "z", ["v"] = "y"}, -- +X
["-100"] = {[0] = {"hb",22,0},[1] = {"vl",10,4 },[2] = {"vr",4 ,10},[3] = {"bb",19,19},[4] = {"bt",19,19},[5] = {"ht",0,22}, ["h"] = "z", ["v"] = "y"}, -- -X
["010"] = {[0] = {"bb",22,0},[1] = {"vl",10,4 },[2] = {"vr",4 ,10},[3] = {"hb",19,13},[4] = {"ht",13,19},[5] = {"bt",0,22}, ["h"] = "z", ["v"] = "x"}, -- +Y
["0-10"] = {[0] = {"bb",22,0},[1] = {"vl",10,4 },[2] = {"vr",4 ,10},[3] = {"hb",19,13},[4] = {"ht",13,19},[5] = {"bt",0,22}, ["h"] = "z", ["v"] = "x"}, -- -Y
["001"] = {[0] = {"hb",22,0},[1] = {"bb",4 ,4 },[2] = {"bt",4 ,4 },[3] = {"vl",19,13},[4] = {"vr",13,19},[5] = {"ht",0,22}, ["h"] = "x", ["v"] = "y"}, -- +Z
["00-1"] = {[0] = {"hb",22,0},[1] = {"bb",10,10},[2] = {"bt",10,10},[3] = {"vl",19,13},[4] = {"vr",13,19},[5] = {"ht",0,22}, ["h"] = "x", ["v"] = "y"}} -- -Z
local p2_axis = math.floor(oldnode.param2/4)
local outcome = comboblock_p2_axis[exact_nor_string][p2_axis][1]
local o_p2_v1 = comboblock_p2_axis[exact_nor_string][p2_axis][2]
local o_p2_v2 = comboblock_p2_axis[exact_nor_string][p2_axis][3]
local hor = comboblock_p2_axis[exact_nor_string]["h"]
local ver = comboblock_p2_axis[exact_nor_string]["v"]
-- v1 is always top half v2 is always bottom half - reverse of expected.
-- Using swap to minimise accidental callback triggering
if outcome == "hb" then
if point[ver] >= 0 then
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2})
comboblock.give_drop(digger, v1)
else
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1})
comboblock.give_drop(digger, v2)
end
elseif outcome == "ht" then
if point[ver] >= 0 then
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1})
comboblock.give_drop(digger, v2)
else
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2})
comboblock.give_drop(digger, v1)
end
elseif outcome == "vl" then
if point[hor] >= 0 then
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2})
comboblock.give_drop(digger, v1)
else
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1})
comboblock.give_drop(digger, v2)
end
elseif outcome == "vr" then
if point[hor] >= 0 then
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1 })
comboblock.give_drop(digger, v2)
else
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2 })
comboblock.give_drop(digger, v1)
end
elseif outcome == "bb" then
if tonumber(exact_nor_string) == nil or
tonumber(exact_nor_string) < 0 then
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1 })
comboblock.give_drop(digger, v2)
else
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2 })
comboblock.give_drop(digger, v1)
end
elseif outcome == "bt" then
if tonumber(exact_nor_string) == nil or
tonumber(exact_nor_string) < 0 then
minetest.swap_node(pos, {name = v2, param2 = o_p2_v2})
comboblock.give_drop(digger, v1)
else
minetest.swap_node(pos, {name = v1, param2 = o_p2_v1 })
comboblock.give_drop(digger, v2)
end
else
local name = digger:get_player_name()
minetest.chat_send_player(name ,"Hmm...something has gone wrong, everything has crumbled!?!")
minetest.swap_node(pos, {name = "air"})
end
end
----------------------------
-- Main Code --
----------------------------
-- moreblocks code part borrowed from Linuxdirk MT forums
-- Im directly updating these on the fly not using override_item
-- Not the best practice and maybe slower - review later
local mblocks = minetest.get_modpath("moreblocks") -- used to establish if moreblocks is loaded
if mblocks ~= nil then
minetest.debug("moreblocks present")
for name, def in pairs(minetest.registered_nodes) do
local slab_name = string.sub (def.description, -6)
if slab_name == "(8/16)" then -- The only way Ive found of identifying moreblocks half slabs
def.groups.slab = 1 -- Add any slabs in moreblocks that are 8/16 to the slab group
for index,_ in pairs (def.tiles)do -- Part borrowed from Linuxdirk
if type(def.tiles[index]) == 'table' then
def.tiles[index].align_style = 'world'
else
def.tiles[index] = {
name = def.tiles[index],
align_style = 'world'
}
end
end
end
end
end
-- creates an index of any node name in the group "slab"
local slab_index = registered_nodes_by_group("slab")
-- Calculate max permutations, this over estimates as we
-- don't mix glass and non-glass - in-built error margin
local max_perm = #slab_index*(#slab_index-1)
local existing_node_count = node_count
for _,v1 in pairs(slab_index) do
-- set from v2_tiles node registration count
if to_many_nodes then
break
else
local v1_def = minetest.registered_nodes[v1] -- Makes a copy of the relevant node settings
local v1_groups = table.copy(v1_def.groups) -- Takes the above and places the groups into its own seperate copy
local v1_tiles -- v1 tiles table
v1_groups.not_in_creative_inventory = 1 -- Don't want comboblocks cluttering inventory
v1_groups.slab = nil -- They aren't slabs so remove slab group
if type(v1_def.tiles) ~= "table" then -- Check tiles are stored as table some old mods just have tiles = "texture.png"
v1_tiles = {v1_def.tiles} -- construct table as {"texture.png"}
else
v1_tiles = table.copy(v1_def.tiles) -- copy of the node texture images
end
for i = 2, 6, 1 do -- Checks for image names for each side, If not image name
if not v1_tiles[i] then -- then copy previous image name in: 1 = Top, 2 = Bottom, 3-6 = Sides
v1_tiles[i] = v1_tiles[i-1]
elseif mblocks ~= nil and i >= 5 then
v1_tiles[i].name = v1_tiles[i].name:gsub("%^%[transformR90", "") -- v1 R90 not needed as applied at V2 stage needed for moreblocks
end
end
for _,v2 in pairs(slab_index) do -- every slab has to be done with every other slab including itself
if node_count > 32668 then -- Prevent registering more than the max 32768 - limit set 100 less than max
minetest.debug("WARNING:Comboblock - Max nodes"..
" registered: "..(max_perm+existing_node_count)-node_count..
" slab combos not registered")
to_many_nodes = true -- Outer loop break trigger
break
else
local v2_def = minetest.registered_nodes[v2] -- this creates a second copy of all slabs and is identical to v1
local v2_tiles
if type(v2_def.tiles) ~= "table" then -- Check tiles are stored as table some old mods just have tiles = "texture.png"
v2_tiles = {v2_def.tiles} -- construct table as {"texture.png"}
else
v2_tiles = table.copy(v2_def.tiles) -- copy of the node texture images
end
for i = 2, 6, 1 do -- Checks for image names for each side, If not image name
if not v2_tiles[i] and i <= 2 then -- then copy previous image name in: 1 = Top, 2 = Bottom, 3-6 = Sides
v2_tiles[i] = v2_tiles[i-1]
elseif i >= 3 then
if not v2_tiles[i] then
v2_tiles[i] = table.copy(v2_tiles[i-1]) -- must be table copy as we don't want a pointer
v2_tiles[i].name = add_lowpart(v2_tiles[i]) -- only need to do this once as 4,5,6 are basically copy of 3
else
v2_tiles[i].name = add_lowpart(v2_tiles[i]) -- If node has images specified for each slot have to add string to the front of those
end
end
end
-- Register nodes --
-- Very strange behaviour with orginal mod when placing glass and normal slabs ontop of each other.
-- Removed the ability to place glass slabs on non-glass slabs.
-- Original Behaviour summary:
-- Normal slab on glass slab - slab appears as top slab with some strange graphic overlay, Unknown why - guess drawtype conflict with graphic
-- Glass slab on normal slab - slab appears as top glass slab ie full glass block, Unknown why - guess drawtype conflict with graphic
-- For example of above place "glass" slab on "obsidian glass" slab and vice versa this seemed minimal issue so left this combo okay
-- Glass slab on Glass slab - black box inside this is due to orginal code having drawtype = normal for glassslab on glassslab combo
local v1_is_glass = string.find(string.lower(tostring(v1)), "glass") -- Slabs dont use drawtype "glasslike" in stairs due to nodebox requirement,
local v2_is_glass = string.find(string.lower(tostring(v2)), "glass") -- so using name string match but this pretty unreliable.
-- returns value nil if not otherwise returns integar see lua string.find
if v1_is_glass and v2_is_glass then -- glass_glass nodes so drawtype = glasslike
minetest.register_node("comboblock:"..v1:split(":")[2].."_onc_"..v2:split(":")[2], { -- registering the new combo node
description = v1_def.description.." on "..v2_def.description,
tiles = {v1_tiles[1].name.."^[resize:"..cs.."x"..cs,
v2_tiles[2].name.."^[resize:"..cs.."x"..cs,
v1_tiles[3].name.."^[resize:"..cs.."x"..cs..v2_tiles[3].name, -- Stairs registers it's tiles slightly differently now
v1_tiles[4].name.."^[resize:"..cs.."x"..cs..v2_tiles[4].name, -- in a nested table structure and now makes use of
v1_tiles[5].name.."^[resize:"..cs.."x"..cs..v2_tiles[5].name, -- align_style = "world" for most slabs....I think
v1_tiles[6].name.."^[resize:"..cs.."x"..cs..v2_tiles[6].name
},
paramtype = "light",
paramtype2 = "facedir",
drawtype = "glasslike",
sounds = v1_def.sounds,
groups = v1_groups,
drop = "",
comboblock_v1 = tostring(v1),
comboblock_v2 = tostring(v2),
after_dig_node = function(pos, oldnode, oldmetadata, digger)
cb_after_dig_node(pos, oldnode, oldmetadata, digger)
end })
elseif not v1_is_glass and not v2_is_glass then -- normal nodes
minetest.register_node("comboblock:"..v1:split(":")[2].."_onc_"..v2:split(":")[2], {
description = v1_def.description.." on "..v2_def.description,
tiles = {v1_tiles[1].name.."^[resize:"..cs.."x"..cs,
v2_tiles[2].name.."^[resize:"..cs.."x"..cs,
v1_tiles[3].name.."^[resize:"..cs.."x"..cs..v2_tiles[3].name, -- Stairs registers it's tiles slightly differently now
v1_tiles[4].name.."^[resize:"..cs.."x"..cs..v2_tiles[4].name, -- in a nested table structure and now makes use of
v1_tiles[5].name.."^[resize:"..cs.."x"..cs..v2_tiles[5].name, -- align_style = "world" for most slabs....I think
v1_tiles[6].name.."^[resize:"..cs.."x"..cs..v2_tiles[6].name
},
paramtype2 = "facedir",
drawtype = "normal",
sounds = v1_def.sounds,
groups = v1_groups,
drop = "",
comboblock_v1 = tostring(v1),
comboblock_v2 = tostring(v2),
after_dig_node = function(pos, oldnode, oldmetadata, digger)
cb_after_dig_node(pos, oldnode, oldmetadata, digger)
end })
else
-- Can't have a nodetype as half "glasslike" and half "normal" :(
end
node_count = node_count+1
end
end-- v2_tiles for end
-- Override all slabs registered on_place function
minetest.override_item(v1, {
on_place = function(itemstack, placer, pointed_thing)
local pos = pointed_thing.under
local placer_pos = placer:get_pos()
local err_mix = S("Hmmmm... that wont work I can't mix glass slabs and none glass slabs")-- error txt for mixing glass/not glass
local err_un = S("Hmmmm... The slab wont fit there, somethings in the way") -- error txt for unknown/unexpected
local pla_is_glass = string.find(string.lower(tostring(itemstack:get_name())), "glass") -- itemstack item glass slab (trying to place item) - cant use drawtype as slabs are all type = nodebox
local node_c = minetest.get_node({x=pos.x, y=pos.y, z=pos.z}) -- node clicked
local node_c_is_glass = string.find(string.lower(tostring(node_c.name)), "glass") -- is node clicked glass
-- Setup Truth table
local pgn = "F" -- Place is_Glass Node
local cgn = "F" -- Click is_Glass Node
-- Check truth table variables and switch "T"
if pla_is_glass then pgn = "T" end
if node_c_is_glass then cgn = "T" end
local comboblock_truthtable_rel_axis_horiz = {
-- User clicked top/bottom surface slab and slab is Horizontal
-- Place Glass, Click Glass
-- [ T , T ] - top record table key spaced out
["TT"] = {itemstack:get_name(), "clicked"}, -- New: Outcome Two
["TF"] = {err = err_mix}, -- New: Error Two
["FT"] = {err = err_mix}, -- New: Error One
["FF"] = {itemstack:get_name(), "clicked"}} -- New: Outcome One
-- Used to identify potential half slab deep axis
local comboblock_param_side_offset = {
--xyz -- 1st axis = face dir, 2nd two used for placement
["100"] = {"x","y","z"},
["010"] = {"y","x","z"},
["001"] = {"z","y","x"}}
local comboblock_p2_axis = {
["100"] = {l = 4, r = 10, t = 22, b = 0, m = 15}, -- +X
["-100"] = {l = 10, r = 4, t = 22, b = 0, m = 17}, -- -X
["010"] = {l = 4, r = 10, t = 19, b = 13, m = 0}, -- +Y
["0-10"] = {l = 10, r = 4, t = 19, b = 13, m = 22}, -- -Y
["001"] = {l = 13, r = 19, t = 22, b = 0, m = 6}, -- +Z
["00-1"] = {l = 19, r = 13, t = 22, b = 0, m = 8}} -- -Z
-- clicked side for slabs can either be on node boundry or sunk in half
-- node depth. Need to identify the clicked side and later check if it's
-- == 0 or not. Doing slabs this way means remaining code can effectively ignore
-- axis and param2 except for final placement.
local pointed = combo_raycast(placer)
local point = vector.subtract(pointed.intersection_point,pointed.under)
local normal = pointed.intersection_normal
local nor_string = tostring(math.abs(normal.x)..math.abs(normal.y)..math.abs(normal.z))
local pot_short_axis = point[comboblock_param_side_offset[nor_string][1]] -- When == 0 large flat side is inside node
local node_ax_pos = vector.add(pos,normal)
local node_along_axis = minetest.get_node(node_ax_pos) -- retrieve the node along +- axis for xyz
local node_ax_is_slab = minetest.registered_nodes[node_along_axis.name].groups.slab -- is node along axis in slab group
local node_ax_is_build = minetest.registered_nodes[node_along_axis.name].buildable_to -- true/false
local node_ax_is_glass = string.find(string.lower(tostring(node_along_axis.name)), "glass") -- is node glass
local is_prot = minetest.is_protected(pos,placer:get_player_name())
if pot_short_axis == 0 and not is_prot then -- Clicked inside existing node with slab
local outcome = comboblock_truthtable_rel_axis_horiz[pgn..cgn]
local combo_name = cb_get_name(outcome,placer,node_c.name)
if outcome.err == nil then -- Cant mix glass and normal slabs
minetest.swap_node(pos,{name=combo_name, param2=node_c.param2})
itemstack:take_item(1)
end
elseif node_ax_is_build then -- Clicked surface and node along axis is_buildable
local hor = comboblock_param_side_offset[nor_string][3]
local ver = comboblock_param_side_offset[nor_string][2]
-- Switchs left/right when -/+ axis face clicked
local multi = normal[comboblock_param_side_offset[nor_string][1]]
local p2 = 0
-- top(t)/bot(b)/left(l)/right(r) are fairly meaningless just labels - align on X
if math.abs(point[hor]) < 0.25 and math.abs(point[ver]) < 0.25 then
p2 = comboblock_p2_axis[tostring(normal.x..normal.y..normal.z)]["m"]
elseif multi*point[hor] >= math.abs(point[ver]) then
p2 = comboblock_p2_axis[tostring(normal.x..normal.y..normal.z)]["r"]
elseif multi*-point[hor] >= math.abs(point[ver]) then
p2 = comboblock_p2_axis[tostring(normal.x..normal.y..normal.z)]["l"]
elseif point[ver] >= math.abs(point[hor]) then
p2 = comboblock_p2_axis[tostring(normal.x..normal.y..normal.z)]["t"]
elseif -point[ver] >= math.abs(point[hor]) then
p2 = comboblock_p2_axis[tostring(normal.x..normal.y..normal.z)]["b"]
end
minetest.item_place(itemstack, placer, pointed_thing, p2)
elseif node_ax_is_slab and not is_prot then -- Clicked surface and node along axis is_slab
-- use node_along_axis instead of node clicked
-- recalc our clicked glass node true/false using node_along_axis as sub
if node_ax_is_glass then cgn = "T" end
-- same process as our 1st if now but sub in node_along_axis details
local outcome = comboblock_truthtable_rel_axis_horiz[pgn..cgn]
local combo_name = cb_get_name(outcome,placer,node_along_axis.name)
if outcome.err == nil then -- Cant mix glass and normal slabs
minetest.swap_node(node_ax_pos,{name=combo_name, param2=node_along_axis.param2})
itemstack:take_item(1)
end
else -- Last error catch
local name = placer:get_player_name()
minetest.chat_send_player(name ,err_un) -- New:Unknown Case
end
if not creative.is_enabled_for(placer:get_player_name()) then
return itemstack
end
end })
end
end