From 3b5e096277cc1482f7e585c8582ec7c75a485d05 Mon Sep 17 00:00:00 2001 From: Oversword Date: Mon, 1 Feb 2021 21:17:13 +0000 Subject: [PATCH] Separate out functionality & registration & init --- default-doors.lua | 5 + init.lua | 919 +--------------------------------------------- register.lua | 876 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 889 insertions(+), 911 deletions(-) create mode 100644 default-doors.lua create mode 100644 register.lua diff --git a/default-doors.lua b/default-doors.lua new file mode 100644 index 0000000..d818387 --- /dev/null +++ b/default-doors.lua @@ -0,0 +1,5 @@ + +bigdoors.register("doors:door_wood") +bigdoors.register("doors:door_steel") +bigdoors.register("doors:door_glass") +bigdoors.register("doors:door_obsidian_glass") \ No newline at end of file diff --git a/init.lua b/init.lua index 370a980..6b1e37f 100644 --- a/init.lua +++ b/init.lua @@ -1,915 +1,12 @@ +bigdoors = {} --- Load support for MT game translation. -local S = minetest.get_translator("doors") +bigdoors.debug_hidden_nodes = false -- true -local debug_hidden_nodes = false -- true +bigdoors.door_widths = {10,15,20} +bigdoors.door_heights = {20,30,40} -local bigdoors = {} +bigdoors.modname = minetest.get_current_modname() +bigdoors.modpath = minetest.get_modpath(bigdoors.modname) -local door_widths = {10,15,20} -local door_heights = {20,30,40} -local door_sizes = {} -local hidden_sizes = {} -local replacement_doors = {} - -local function craft_size(s) - if s == 1.5 then - return 3, 2 - end - return s, 1 -end - -local function numstr(num) - return string.sub("00"..tostring(math.floor(num*10)),-2) -end - -local function size_to_string(size) - local width_string = numstr(size.width) - local height_string = numstr(size.height) - return width_string..height_string -end - -local function string_to_size(str) - return { - height = tonumber(string.sub(str, -2))/10, - width = tonumber(string.sub(str, 0, -3))/10, - } -end - -local function new_hitbox(size, rev) - local normalbox = {-1/2,-1/2,-1/2,1/2,3/2,-6/16} - normalbox[5] = normalbox[2]+size.height - if rev then - normalbox[1] = normalbox[4]-size.width - else - normalbox[4] = normalbox[1]+size.width - end - return normalbox -end - -local max_width = 0 - -for _,w in ipairs(door_widths) do - local W = w/10 - if W > max_width then - max_width = W - end - local wrem = W%1 - if wrem == 0 then wrem = 1 end - for _,h in ipairs(door_heights) do - local H = h/10 - local hrem = H%1 - if hrem == 0 then hrem = 1 end - local hidden_size = { width=wrem, height=hrem } - hidden_sizes[size_to_string(hidden_size)] = hidden_size - local wr, mw = craft_size(W) - local hr, mh = craft_size(H/2) - - local size = { - width = W, - height = H, - recipe = { - width = wr, - height = hr, - output = mw*mh - }, - } - size.hitbox = { - ad=new_hitbox(size), - bc=new_hitbox(size, true) - } - local size_string = size_to_string(size) - door_sizes[size_string] = size - end -end -local max_pair_distance = (max_width*2)-1 - -local param2_to_vector = { - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = -1}, -} - -local reverse_rotation = { - [0]=20,23,22,21 -} - -local function doorientation(param2, dir_str) - if dir_str == 'a' then - return param2, 0 - end - if dir_str == 'b' then - return param2, 2 - end - if dir_str == 'c' then - return (param2-1)%4, 0 - end - if dir_str == 'd' then - return (param2+1)%4, 2 - end -end - -local function parse_door_name(name) - local sep = string.sub(name, -2,-2) - if sep == '_' then -- mode & size - local size_string = string.sub(name, -6, -3) - local clean_name = string.sub(name, 0, -8) - local mode = string.sub(name, -1) - return { - mode = mode, - size_string = size_string, - size = string_to_size(size_string), - name = clean_name, - closed = mode == 'a' or mode == 'b' - } - else -- just size - local size_string = string.sub(name, -4) - local clean_name = string.sub(name, 0, -6) - return { - mode = false, - size_string = size_string, - size = string_to_size(size_string), - name = clean_name, - closed = nil - } - end -end - -local function parse_door(pos, node) - if not node then - node = minetest.get_node(pos) - end - local name_parse = parse_door_name(node.name) - local dir, state = doorientation(node.param2, name_parse.mode) - name_parse.dir = dir - name_parse.state = state - return name_parse -end - -local function door_occupies(pos, door, pair_pos) - -- TODO: cache for each size and then run through adding pos? - - -- Generate list of nodes this door will occupy, to be checked and blocked with hidden nodes - local nextward = param2_to_vector[((door.dir+2+door.state)%4)+1] - local behindward = param2_to_vector[((door.dir+1)%4)+1] - local upward = {x=0,y=1,z=0} - - local check_nodes = {} - local base_pos = table.copy(pos) - local pair_closed = false - local pair_height = 0 - if pair_pos and door.size.width ~= math.ceil(door.size.width) then - local pair_name = parse_door_name(minetest.get_node(pair_pos).name) - if pair_name.closed then - pair_closed = true - pair_height = pair_name.size.height - end - end - for y=0,math.ceil(door.size.height)-1,1 do - -- Check place it will be positioned - if y ~= 0 then -- Ignore the fist one, already checked and has node - table.insert(check_nodes, { - pos=base_pos, - dir="spine", - edge=false - }) - end - local mx = math.ceil(door.size.width)-1 - for x=1,mx,1 do - local edge = x==mx - table.insert(check_nodes, { - pos=vector.add(base_pos, nextward), - dir="next", - edge=edge, - overlap=edge and pair_closed and y < pair_height - }) - end - -- Check place it will open into - local mz = math.ceil(door.size.width)-1 - for z=1,mz,1 do - table.insert(check_nodes, { - pos=vector.add(base_pos, behindward), - dir="back", - edge=z==mz - }) - end - base_pos = vector.add(base_pos, upward) - end - - return check_nodes -end - -local function get_pair_pos(pos, pair) - if not pair then - local meta = minetest.get_meta(pos) - pair = meta:get_string('pair') - end - if pair and pair ~= '' then - return minetest.string_to_pos(pair) - end -end - -local function unpair(pos, pair) - local pair_pos = get_pair_pos(pos, pair) - if pair_pos then - local pair_meta = minetest.get_meta(pair_pos) - pair_meta:set_string('pair', nil) - return pair_pos - end -end - -local function pairing_on_left (pos, door) - local leftward = param2_to_vector[door.dir + 1] - local left_side = pos - local pair = nil - for l=1,max_pair_distance,1 do - left_side = vector.add(left_side, leftward) - local left_node = minetest.get_node(left_side) - local left_node_name = left_node.name - if minetest.get_item_group(left_node_name, "door") == 1 then - local dir_str = string.sub(left_node_name,-1) - local pair_width = door.size.width + door_sizes[string.sub(left_node_name,-6,-3)].width - if ( - (dir_str == 'a' and door.dir == left_node.param2) -- If closed and same - or (dir_str == 'c' and (door.dir+1)%4 == left_node.param2) -- If open and same when closed - ) -- If normal door and same rotation, on the same plane (when closed) - and pair_width == l+1 -- If doors match perfectly, filling the width - then - pair = left_side - end - break - end - end - return pair -end - -local function pairing_on_right (pos, door) - local rightward = param2_to_vector[((door.dir + 2)%4)+1] - local right_side = pos - local pair = nil - for r=1,max_pair_distance,1 do - right_side = vector.add(right_side, rightward) - local right_node = minetest.get_node(right_side) - local right_node_name = right_node.name - if minetest.get_item_group(right_node_name, "door") == 1 then - local dir_str = string.sub(right_node_name,-1) - local pair_width = door.size.width + door_sizes[string.sub(right_node_name,-6,-3)].width - if ( - (dir_str == 'b' and door.dir == right_node.param2) -- If closed and same - or (dir_str == 'd' and (door.dir-1)%4 == right_node.param2) -- If open and same when closed - ) -- If normal door and same rotation, on the same plane (when closed) - and pair_width == r+1 -- If doors match perfectly, filling the width - then - pair = right_side - end - break - end - end - return pair -end - -local function swap_pair_edge(pos, door) - local width = 1-(door.size.width%1) - - local size_str = size_to_string({ width=width, height=1 }) - - local param2 = door.dir - if door.state == 0 then - param2 = reverse_rotation[param2] - end - minetest.set_node(pos, { - name = "bigdoors:hidden_section_"..size_str, - param2 = param2 - }) -end - - -local function swap_nodes(check_nodes, door, open) - -- local check_nodes = door_occupies(pos, dir, state, size, pair_pos) - - local hidden_param2 = door.dir - if door.state == 2 then - hidden_param2 = (door.dir + 3) % 4 - end - - if open then - for _,check_node in ipairs(check_nodes) do - if check_node.dir == 'next' then - if check_node.overlap then - swap_pair_edge(check_node.pos, door) - else - minetest.set_node(check_node.pos, { - name = "bigdoors:hidden", - param2 = hidden_param2 - }) - end - else - local width = 1 - if check_node.edge then - local remainder = door.size.width%1 - if remainder ~= 0 then - width = remainder - end - end - local size_str = size_to_string({ width=width, height=1}) - - local param2 = (door.dir + 1 + door.state) % 4 - if door.state == 0 then - param2 = reverse_rotation[param2] - end - minetest.set_node(check_node.pos, { - name = "bigdoors:hidden_section_"..size_str, - param2 = param2 - }) - end - end - else - for _,check_node in ipairs(check_nodes) do - if check_node.dir == 'back' then - minetest.set_node(check_node.pos, { - name = "bigdoors:hidden", - param2 = hidden_param2 - }) - else - local width = 1 - if check_node.edge and not check_node.overlap then - local remainder = door.size.width%1 - if remainder ~= 0 then - width = remainder - end - end - local size_str = size_to_string({ width=width, height=1}) - - local param2 = door.dir - if door.state == 2 then - param2 = reverse_rotation[param2] - end - minetest.set_node(check_node.pos, { - name = "bigdoors:hidden_section_"..size_str, - param2 = param2 - }) - end - end - end -end - -local function remove_door_surroundings(pos, pair_pos, node) - local door = parse_door(pos, node) - local check_nodes = door_occupies(pos, door, pair_pos) - local top_nodes = {} - for _,check_pos in ipairs(check_nodes) do - if check_pos.pos.y == pos.y+door.size.height-1 then - table.insert(top_nodes, check_pos) - end - if check_pos.overlap then - swap_pair_edge(check_pos.pos, door) - else - minetest.remove_node(check_pos.pos) - end - end - for _,top_pos in ipairs(top_nodes) do - minetest.check_for_falling(top_pos.pos) - end -end - -local function on_rightclick(pos, node, clicker, itemstack, pointed_thing) - -- Toggle door open/close on right click - - local toggled = doors.door_toggle(pos, node, clicker) - if not toggled then - return itemstack - end - - local door = parse_door(pos, node) - - local pair_pos = get_pair_pos(pos) - local nodes = door_occupies(pos, door, pair_pos) - - swap_nodes(nodes, door, door.closed) - - return itemstack -end - -local function after_dig_node (pos, node, meta, digger) - local pair_pos = unpair(pos, meta.fields.pair) - - remove_door_surroundings(pos, pair_pos, node) -end - -local function on_blast_unprotected (pos, intensity) - local pair_pos = unpair(pos) - - local node = minetest.get_node(pos) - remove_door_surroundings(pos, pair_pos, node) - minetest.remove_node(pos) - return {node.name} -end - -local function on_destruct (pos) - local pair_pos = unpair(pos) - - remove_door_surroundings(pos, pair_pos) -end - -local function on_place_node (place_to, newnode, - placer, oldnode, itemstack, pointed_thing) - -- Run script hook - for _, callback in ipairs(minetest.registered_on_placenodes) do - -- Deepcopy pos, node and pointed_thing because callback can modify them - local place_to_copy = {x = place_to.x, y = place_to.y, z = place_to.z} - local newnode_copy = - {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2} - local oldnode_copy = - {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2} - local pointed_thing_copy = { - type = pointed_thing.type, - above = vector.new(pointed_thing.above), - under = vector.new(pointed_thing.under), - ref = pointed_thing.ref, - } - callback(place_to_copy, newnode_copy, placer, - oldnode_copy, itemstack, pointed_thing_copy) - end -end - -local function on_place (itemstack, placer, pointed_thing) - -- Find pos, unaffected, same as original - local pos - - if not pointed_thing.type == "node" then - return itemstack - end - - local node = minetest.get_node(pointed_thing.under) - local pdef = minetest.registered_nodes[node.name] - if pdef and pdef.on_rightclick and - not (placer and placer:is_player() and - placer:get_player_control().sneak) then - return pdef.on_rightclick(pointed_thing.under, - node, placer, itemstack, pointed_thing) - end - - if pdef and pdef.buildable_to then - pos = pointed_thing.under - else - pos = pointed_thing.above - node = minetest.get_node(pos) - pdef = minetest.registered_nodes[node.name] - if not pdef or not pdef.buildable_to then - return itemstack - end - end - - local player_name = placer and placer:get_player_name() or "" - if minetest.is_protected(pos, player_name) then - return itemstack - end - - - local name = itemstack:get_name() - if replacement_doors[name] then - name = replacement_doors[name] - end - local door = parse_door_name(name) - door.dir = placer and minetest.dir_to_facedir(placer:get_look_dir()) or 0 - - - -- Flip the door if we find a matching one to the left - local pair = pairing_on_left(pos, door) - door.state = 0 - if pair then - door.state = 2 - else - pair = pairing_on_right(pos, door) - end - - local check_nodes = door_occupies(pos, door, pair) - - -- Check surroundings for validity of placement - for _,check_pos in ipairs(check_nodes) do - if not check_pos.overlap then - local check_node = minetest.get_node_or_nil(check_pos.pos) - local check_def = check_node and minetest.registered_nodes[check_node.name] - - if not check_def or not check_def.buildable_to then - return itemstack - end - - if minetest.is_protected(check_pos, player_name) then - return itemstack - end - end - end - - -- Create node - local dir_name - if door.state == 2 then - dir_name = name .. "_b" - else - dir_name = name .. "_a" - end - minetest.set_node(pos, {name = dir_name, param2 = door.dir}) - - -- Set metadata - local meta = minetest.get_meta(pos) - meta:set_int("state", door.state) - if pair then - local pair_pos = minetest.pos_to_string(pair) - local this_pos = minetest.pos_to_string(pos) - - meta:set_string("pair", pair_pos) - local pair_meta = minetest.get_meta(pair) - pair_meta:set_string("pair", this_pos) - end - - -- Create hidden nodes to prevent obstructing placement - swap_nodes(check_nodes, door, false) - - local def = minetest.registered_nodes[dir_name] - -- Other stuffs, unaffected, same as original - if def.protected then - meta:set_string("owner", player_name) - meta:set_string("infotext", def.description .. "\n" .. S("Owned by @1", player_name)) - end - - if not (creative and creative.is_enabled_for and creative.is_enabled_for(player_name)) then - itemstack:take_item() - end - - minetest.sound_play(def.sounds.place, {pos = pos}, true) - - on_place_node(pos, minetest.get_node(pos), - placer, node, itemstack, pointed_thing) - - return itemstack -end - - --- CUSTOM HIDDEN NODES - -local hidden_def = { - description = S("Hidden Door Segment"), - drawtype = "airlike", - paramtype = "light", - paramtype2 = "facedir", - sunlight_propagates = true, - -- has to be walkable for falling nodes to stop falling. - walkable = true, - pointable = false, - diggable = false, - buildable_to = false, - floodable = false, - drop = "", - groups = {not_in_creative_inventory = 1}, - on_blast = function() end, - collision_box = { - type = "fixed", - fixed = {0,0,0,0,0,0}, - }, -} - -if debug_hidden_nodes then - local s = 0.3 - hidden_def.selection_box = {type = "fixed", fixed = {-s,-s,-s,s,s,s}} - hidden_def.tiles = {{ name = "bigdoors_debug.png" }} - hidden_def.groups = {} - hidden_def.pointable = true - hidden_def.drawtype = 'glasslike' -end - -minetest.register_node("bigdoors:hidden", hidden_def) -- doors:hidden has a hitbox, no thanks - -local function hidden_section(size) - -- Create hidden sections for hit-box purposes - local size_string = size_to_string(size) - local name = "bigdoors:hidden_section_"..size_string - - local def = { - description = S("Hidden Door Section - "..size_string), - drawtype = "airlike", - paramtype = "light", - paramtype2 = "facedir", - sunlight_propagates = true, - -- has to be walkable for falling nodes to stop falling. - walkable = true, - is_ground_content = false, - pointable = false, - diggable = false, - buildable_to = false, - floodable = false, - drop = "", - groups = {not_in_creative_inventory = 1}, - on_blast = function() end, - selection_box = { - type = "fixed", - fixed = {0,0,0,0,0,0}, - }, - collision_box = { - type = "fixed", - fixed = new_hitbox(size) - } - } - - if debug_hidden_nodes then - local debug_select = new_hitbox(size) - debug_select[6] = -0.3 - debug_select[3] = -0.7 - def.selection_box = {type = "fixed", fixed = debug_select} - def.tiles = {{ name = "bigdoors_debug.png" }} - def.groups = {} - def.pointable = true - def.drawtype = 'glasslike' - end - - -- Register nodes - - minetest.register_node(":" .. name, def) -end - -for _,hidden_size in pairs(hidden_sizes) do - hidden_section(hidden_size) -end - - --- REGISTER NEW DOOR - - -function by_value( t1 ) - local t2 = { } - if #t1 > 0 then - -- ordered copy of arrays - t2 = { unpack( t1 ) } - else - -- shallow copy of hashes - for k, v in pairs( t1 ) do - t2[ k ] = v - end - end - return t2 -end - - -function new_recipe(size, item) - local recipe = {} - local recipe_row = {} - for i=1,size.width,1 do - table.insert(recipe_row, item) - end - for i=1,size.height,1 do - table.insert(recipe, recipe_row) - end - return recipe -end - -function user_size_to_sys(sizes) - if not sizes then return end - local sys_sizes = {} - for _,size in ipairs(sizes) do - table.insert(sys_sizes, math.floor(size*10)) - end - return sys_sizes -end - -function size_variations(variations) - local valid_sizes = {} - - -- Generate list of all width-height combinations - local whcombos = {} - local heights = user_size_to_sys(variations.heights) or door_heights - local widths = user_size_to_sys(variations.widths) or door_widths - for _,h in ipairs(heights) do - for _,w in ipairs(widths) do - local size = {width=w/10,height=h/10} - local size_string = size_to_string(size) - whcombos[size_string] = size - end - end - - -- Use only those defined in sizes - local sizecombos = {} - if variations.sizes then - for _,size in ipairs(variations.sizes) do - local size_string = size_to_string(size) - if whcombos[size_string] then - sizecombos[size_string] = size - end - end - else - sizecombos = whcombos - end - - -- Use only those that maintain proportions - local propcombos = {} - if variations.original_proportions then - for size_string,size in pairs(sizecombos) do - if math.abs((size.width*2)-size.height) < 0.1 then - propcombos[size_string] = size - end - end - else - propcombos = sizecombos - end - - -- Use only existing size combinations - for size_string,_ in pairs(propcombos) do - if door_sizes[size_string] then - valid_sizes[size_string] = door_sizes[size_string] - end - end - return valid_sizes -end - -function print_keys(tab, check) - for k,v in pairs(tab) do - if (not check) or check(k,v) then - -- if type(v) == 'table' then - minetest.log("error", k) - end - end -end - -function bigdoors.register(originalname, config) - - local basedef = minetest.registered_nodes[originalname..'_a'] - local baseitem = minetest.registered_craftitems[originalname] - - if not config then - config = {} - end - - if config.replace_original and not config.original_recipe then - minetest.log("error", "BigDoors: Cannot replace original door ("..originalname..") if original_recipe is not defined. (Recipes cannot be retrieved from the minetest API)") - return - end - - local base_name = originalname - if config.name then - base_name = config.name - end - if not base_name:find(":") then - base_name = "bigdoors:" .. base_name - end - - local recipe_name = originalname - if config.replace_original then - recipe_name = base_name..'_1020' - end - if config.replace_original then - replacement_doors[originalname] = base_name..'_1020' - end - - local valid_sizes = door_sizes - if config.variations then - valid_sizes = size_variations(config.variations) - end - - if config.replace_original and not valid_sizes["1020"] then - minetest.log("error", "BigDoors: Cannot replace original door ("..originalname..") if 1x2 size variation is disallowed") - return - end - - for size_string, size in pairs(valid_sizes) do - - -- Name - local name = base_name..'_'..size_string - - -- Create item - minetest.register_craftitem(":" .. name, { - description = baseitem.description .. ' (' .. tostring(size.width) .. ' x ' .. tostring(size.height) .. ')', - inventory_image = baseitem.inventory_image, - groups = table.copy(baseitem.groups), - on_place = on_place - }) - - -- Use recipe to create crafts - if size_string == "1020" and config.replace_original then - minetest.register_craft({ - output = name, - recipe = config.original_recipe, - }) - elseif config.recipe then - -- TODO: multiple recipes for each size? - local recipe = config.recipe[size_string] - if recipe then - local output = name - if recipe.output then - output = output..' '..tostring(recipe.output) - end - local recipe_obj = recipe - if recipe.recipe then - recipe_obj = recipe.recipe - end - minetest.register_craft({ - output = output, - recipe = recipe_obj, - }) - end - else - -- TODO: more complex crafts for breaking apart & making from other components? e.g. (1x3)+(1x3)=(2x3) - minetest.register_craft({ - output = name..' '..tostring(size.recipe.output), - recipe = new_recipe(size.recipe, recipe_name), - }) - if not config.replace_original then - minetest.register_craft({ - output = name..' '..tostring(size.recipe.output), - recipe = new_recipe(size.recipe, originalname), - }) - end - end - - local def = table.copy(basedef) - def.drop = name - def.door.name = name - - - -- Callbacks - def.on_rightclick = on_rightclick - - def.after_dig_node = after_dig_node - def.on_destruct = on_destruct - if not def.protected then - def.on_blast = on_blast_unprotected - end - - -- Model and hitbox - - -- TODO: replace by_value - local defa = by_value(def) - local defb = by_value(def) - local defc = by_value(def) - local defd = by_value(def) - - defa.selection_box = {type = "fixed", fixed = size.hitbox.ad} - defb.selection_box = {type = "fixed", fixed = size.hitbox.bc} - defc.selection_box = {type = "fixed", fixed = size.hitbox.bc} - defd.selection_box = {type = "fixed", fixed = size.hitbox.ad} - - defa.mesh = "bigdoor_a"..size_string..".obj" - defb.mesh = "bigdoor_b"..size_string..".obj" - defc.mesh = "bigdoor_c"..size_string..".obj" - defd.mesh = "bigdoor_d"..size_string..".obj" - - -- Register nodes - minetest.register_node(":" .. name .. "_a", defa) - minetest.register_node(":" .. name .. "_b", defb) - minetest.register_node(":" .. name .. "_c", defc) - minetest.register_node(":" .. name .. "_d", defd) - - doors.registered_doors[name .. "_a"] = true - doors.registered_doors[name .. "_b"] = true - doors.registered_doors[name .. "_c"] = true - doors.registered_doors[name .. "_d"] = true - end - - if config.replace_original then - -- disable original items - local swap_name = base_name..'_1020' - minetest.registered_craftitems[originalname] = table.copy(minetest.registered_craftitems[swap_name]) - minetest.registered_items[originalname] = table.copy(minetest.registered_items[swap_name]) - minetest.registered_craftitems[originalname].groups.not_in_creative_inventory = 1 - minetest.registered_items[originalname].groups.not_in_creative_inventory = 1 - -- replace old doors of this type automatically - minetest.register_lbm({ - name = ":bigdoors:replace_" .. originalname:gsub(":", "_"), - nodenames = {originalname.."_a", originalname.."_b", originalname.."_c", originalname.."_d"}, - action = function(new_pos, node) - local new_door = swap_name..string.sub(node.name,-2) - local new_node = {name = new_door, param2 = node.param2} - minetest.swap_node(new_pos, new_node) - local door = parse_door(nil, new_node) - local check_nodes = door_occupies(new_pos, door) - swap_nodes(check_nodes, door, not door.closed) - end - }) - end - -end - - -if minetest.get_modpath("doors") ~= nil then - bigdoors.register("doors:door_wood", { - }) - bigdoors.register("doors:door_steel", { - name = "testerooney", - replace_original = true, - original_recipe = { - {'default:steel_ingot','default:steel_ingot'}, - {'default:steel_ingot','default:steel_ingot'}, - {'default:steel_ingot','default:steel_ingot'}, - }, - variations = { - widths = {1,1.5}, - heights = {2,3,4,5}, - sizes = {{width=1,height=3},{width=1,height=2}}, - original_proportions = true - } - }) - bigdoors.register("doors:door_glass", { - - }) - bigdoors.register("doors:door_obsidian_glass", { - - }) -end \ No newline at end of file +dofile(bigdoors.modpath .. "/register.lua") +dofile(bigdoors.modpath .. "/default-doors.lua") \ No newline at end of file diff --git a/register.lua b/register.lua new file mode 100644 index 0000000..b00341e --- /dev/null +++ b/register.lua @@ -0,0 +1,876 @@ + +-- Load support for MT game translation. +local S = minetest.get_translator("doors") + +local door_sizes = {} +local hidden_sizes = {} +local replacement_doors = {} + +local function craft_size(s) + if s == 1.5 then + return 3, 2 + end + return s, 1 +end + +local function numstr(num) + return string.sub("00"..tostring(math.floor(num*10)),-2) +end + +local function size_to_string(size) + local width_string = numstr(size.width) + local height_string = numstr(size.height) + return width_string..height_string +end + +local function string_to_size(str) + return { + height = tonumber(string.sub(str, -2))/10, + width = tonumber(string.sub(str, 0, -3))/10, + } +end + +local function new_hitbox(size, rev) + local normalbox = {-1/2,-1/2,-1/2,1/2,3/2,-6/16} + normalbox[5] = normalbox[2]+size.height + if rev then + normalbox[1] = normalbox[4]-size.width + else + normalbox[4] = normalbox[1]+size.width + end + return normalbox +end + +local max_width = 0 + +for _,w in ipairs(bigdoors.door_widths) do + local W = w/10 + if W > max_width then + max_width = W + end + local wrem = W%1 + if wrem == 0 then wrem = 1 end + for _,h in ipairs(bigdoors.door_heights) do + local H = h/10 + local hrem = H%1 + if hrem == 0 then hrem = 1 end + local hidden_size = { width=wrem, height=hrem } + hidden_sizes[size_to_string(hidden_size)] = hidden_size + local wr, mw = craft_size(W) + local hr, mh = craft_size(H/2) + + local size = { + width = W, + height = H, + recipe = { + width = wr, + height = hr, + output = mw*mh + }, + } + size.hitbox = { + ad=new_hitbox(size), + bc=new_hitbox(size, true) + } + local size_string = size_to_string(size) + door_sizes[size_string] = size + end +end +local max_pair_distance = (max_width*2)-1 + +local param2_to_vector = { + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = -1}, +} + +local reverse_rotation = { + [0]=20,23,22,21 +} + +local function doorientation(param2, dir_str) + if dir_str == 'a' then + return param2, 0 + end + if dir_str == 'b' then + return param2, 2 + end + if dir_str == 'c' then + return (param2-1)%4, 0 + end + if dir_str == 'd' then + return (param2+1)%4, 2 + end +end + +local function parse_door_name(name) + local sep = string.sub(name, -2,-2) + if sep == '_' then -- mode & size + local size_string = string.sub(name, -6, -3) + local clean_name = string.sub(name, 0, -8) + local mode = string.sub(name, -1) + return { + mode = mode, + size_string = size_string, + size = string_to_size(size_string), + name = clean_name, + closed = mode == 'a' or mode == 'b' + } + else -- just size + local size_string = string.sub(name, -4) + local clean_name = string.sub(name, 0, -6) + return { + mode = false, + size_string = size_string, + size = string_to_size(size_string), + name = clean_name, + closed = nil + } + end +end + +local function parse_door(pos, node) + if not node then + node = minetest.get_node(pos) + end + local name_parse = parse_door_name(node.name) + local dir, state = doorientation(node.param2, name_parse.mode) + name_parse.dir = dir + name_parse.state = state + return name_parse +end + +local function door_occupies(pos, door, pair_pos) + -- TODO: cache for each size and then run through adding pos? + + -- Generate list of nodes this door will occupy, to be checked and blocked with hidden nodes + local nextward = param2_to_vector[((door.dir+2+door.state)%4)+1] + local behindward = param2_to_vector[((door.dir+1)%4)+1] + local upward = {x=0,y=1,z=0} + + local check_nodes = {} + local base_pos = table.copy(pos) + local pair_closed = false + local pair_height = 0 + if pair_pos and door.size.width ~= math.ceil(door.size.width) then + local pair_name = parse_door_name(minetest.get_node(pair_pos).name) + if pair_name.closed then + pair_closed = true + pair_height = pair_name.size.height + end + end + for y=0,math.ceil(door.size.height)-1,1 do + -- Check place it will be positioned + if y ~= 0 then -- Ignore the fist one, already checked and has node + table.insert(check_nodes, { + pos=base_pos, + dir="spine", + edge=false + }) + end + local mx = math.ceil(door.size.width)-1 + for x=1,mx,1 do + local edge = x==mx + table.insert(check_nodes, { + pos=vector.add(base_pos, nextward), + dir="next", + edge=edge, + overlap=edge and pair_closed and y < pair_height + }) + end + -- Check place it will open into + local mz = math.ceil(door.size.width)-1 + for z=1,mz,1 do + table.insert(check_nodes, { + pos=vector.add(base_pos, behindward), + dir="back", + edge=z==mz + }) + end + base_pos = vector.add(base_pos, upward) + end + + return check_nodes +end + +local function get_pair_pos(pos, pair) + if not pair then + local meta = minetest.get_meta(pos) + pair = meta:get_string('pair') + end + if pair and pair ~= '' then + return minetest.string_to_pos(pair) + end +end + +local function unpair(pos, pair) + local pair_pos = get_pair_pos(pos, pair) + if pair_pos then + local pair_meta = minetest.get_meta(pair_pos) + pair_meta:set_string('pair', nil) + return pair_pos + end +end + +local function pairing_on_left (pos, door) + local leftward = param2_to_vector[door.dir + 1] + local left_side = pos + local pair = nil + for l=1,max_pair_distance,1 do + left_side = vector.add(left_side, leftward) + local left_node = minetest.get_node(left_side) + local left_node_name = left_node.name + local size_string = string.sub(left_node_name,-6,-3) + if minetest.get_item_group(left_node_name, "door") == 1 and not string.match(size_string, "%D") then + local dir_str = string.sub(left_node_name,-1) + local pair_width = door.size.width + door_sizes[size_string].width + if ( + (dir_str == 'a' and door.dir == left_node.param2) -- If closed and same + or (dir_str == 'c' and (door.dir+1)%4 == left_node.param2) -- If open and same when closed + ) -- If normal door and same rotation, on the same plane (when closed) + and pair_width == l+1 -- If doors match perfectly, filling the width + then + pair = left_side + end + break + end + end + return pair +end + +local function pairing_on_right (pos, door) + local rightward = param2_to_vector[((door.dir + 2)%4)+1] + local right_side = pos + local pair = nil + for r=1,max_pair_distance,1 do + right_side = vector.add(right_side, rightward) + local right_node = minetest.get_node(right_side) + local right_node_name = right_node.name + local size_string = string.sub(right_node_name,-6,-3) + if minetest.get_item_group(right_node_name, "door") == 1 and not string.match(size_string, "%D") then + local dir_str = string.sub(right_node_name,-1) + local pair_width = door.size.width + door_sizes[size_string].width + if ( + (dir_str == 'b' and door.dir == right_node.param2) -- If closed and same + or (dir_str == 'd' and (door.dir-1)%4 == right_node.param2) -- If open and same when closed + ) -- If normal door and same rotation, on the same plane (when closed) + and pair_width == r+1 -- If doors match perfectly, filling the width + then + pair = right_side + end + break + end + end + return pair +end + +local function swap_pair_edge(pos, door) + local width = 1-(door.size.width%1) + + local size_str = size_to_string({ width=width, height=1 }) + + local param2 = door.dir + if door.state == 0 then + param2 = reverse_rotation[param2] + end + minetest.set_node(pos, { + name = bigdoors.modname..":hidden_section_"..size_str, + param2 = param2 + }) +end + + +local function swap_nodes(check_nodes, door, open) + -- local check_nodes = door_occupies(pos, dir, state, size, pair_pos) + + local hidden_param2 = door.dir + if door.state == 2 then + hidden_param2 = (door.dir + 3) % 4 + end + + if open then + for _,check_node in ipairs(check_nodes) do + if check_node.dir == 'next' then + if check_node.overlap then + swap_pair_edge(check_node.pos, door) + else + minetest.set_node(check_node.pos, { + name = bigdoors.modname..":hidden", + param2 = hidden_param2 + }) + end + else + local width = 1 + if check_node.edge then + local remainder = door.size.width%1 + if remainder ~= 0 then + width = remainder + end + end + local size_str = size_to_string({ width=width, height=1}) + + local param2 = (door.dir + 1 + door.state) % 4 + if door.state == 0 then + param2 = reverse_rotation[param2] + end + minetest.set_node(check_node.pos, { + name = bigdoors.modname..":hidden_section_"..size_str, + param2 = param2 + }) + end + end + else + for _,check_node in ipairs(check_nodes) do + if check_node.dir == 'back' then + minetest.set_node(check_node.pos, { + name = bigdoors.modname..":hidden", + param2 = hidden_param2 + }) + else + local width = 1 + if check_node.edge and not check_node.overlap then + local remainder = door.size.width%1 + if remainder ~= 0 then + width = remainder + end + end + local size_str = size_to_string({ width=width, height=1}) + + local param2 = door.dir + if door.state == 2 then + param2 = reverse_rotation[param2] + end + minetest.set_node(check_node.pos, { + name = bigdoors.modname..":hidden_section_"..size_str, + param2 = param2 + }) + end + end + end +end + +local function remove_door_surroundings(pos, pair_pos, node) + local door = parse_door(pos, node) + local check_nodes = door_occupies(pos, door, pair_pos) + local top_nodes = {} + for _,check_pos in ipairs(check_nodes) do + if check_pos.pos.y == pos.y+door.size.height-1 then + table.insert(top_nodes, check_pos) + end + if check_pos.overlap then + swap_pair_edge(check_pos.pos, door) + else + minetest.remove_node(check_pos.pos) + end + end + for _,top_pos in ipairs(top_nodes) do + minetest.check_for_falling(top_pos.pos) + end +end + +local function on_rightclick(pos, node, clicker, itemstack, pointed_thing) + -- Toggle door open/close on right click + + local toggled = doors.door_toggle(pos, node, clicker) + if not toggled then + return itemstack + end + + local door = parse_door(pos, node) + + local pair_pos = get_pair_pos(pos) + local nodes = door_occupies(pos, door, pair_pos) + + swap_nodes(nodes, door, door.closed) + + return itemstack +end + +local function after_dig_node (pos, node, meta, digger) + local pair_pos = unpair(pos, meta.fields.pair) + + remove_door_surroundings(pos, pair_pos, node) +end + +local function on_blast_unprotected (pos, intensity) + local pair_pos = unpair(pos) + + local node = minetest.get_node(pos) + remove_door_surroundings(pos, pair_pos, node) + minetest.remove_node(pos) + return {node.name} +end + +local function on_destruct (pos) + local pair_pos = unpair(pos) + + remove_door_surroundings(pos, pair_pos) +end + +local function on_place_node (place_to, newnode, + placer, oldnode, itemstack, pointed_thing) + -- Run script hook + for _, callback in ipairs(minetest.registered_on_placenodes) do + -- Deepcopy pos, node and pointed_thing because callback can modify them + local place_to_copy = {x = place_to.x, y = place_to.y, z = place_to.z} + local newnode_copy = + {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2} + local oldnode_copy = + {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2} + local pointed_thing_copy = { + type = pointed_thing.type, + above = vector.new(pointed_thing.above), + under = vector.new(pointed_thing.under), + ref = pointed_thing.ref, + } + callback(place_to_copy, newnode_copy, placer, + oldnode_copy, itemstack, pointed_thing_copy) + end +end + +local function on_place (itemstack, placer, pointed_thing) + -- Find pos, unaffected, same as original + local pos + + if not pointed_thing.type == "node" then + return itemstack + end + + local node = minetest.get_node(pointed_thing.under) + local pdef = minetest.registered_nodes[node.name] + if pdef and pdef.on_rightclick and + not (placer and placer:is_player() and + placer:get_player_control().sneak) then + return pdef.on_rightclick(pointed_thing.under, + node, placer, itemstack, pointed_thing) + end + + if pdef and pdef.buildable_to then + pos = pointed_thing.under + else + pos = pointed_thing.above + node = minetest.get_node(pos) + pdef = minetest.registered_nodes[node.name] + if not pdef or not pdef.buildable_to then + return itemstack + end + end + + local player_name = placer and placer:get_player_name() or "" + if minetest.is_protected(pos, player_name) then + return itemstack + end + + + local name = itemstack:get_name() + if replacement_doors[name] then + name = replacement_doors[name] + end + local door = parse_door_name(name) + door.dir = placer and minetest.dir_to_facedir(placer:get_look_dir()) or 0 + + + -- Flip the door if we find a matching one to the left + local pair = pairing_on_left(pos, door) + door.state = 0 + if pair then + door.state = 2 + else + pair = pairing_on_right(pos, door) + end + + local check_nodes = door_occupies(pos, door, pair) + + -- Check surroundings for validity of placement + for _,check_pos in ipairs(check_nodes) do + if not check_pos.overlap then + local check_node = minetest.get_node_or_nil(check_pos.pos) + local check_def = check_node and minetest.registered_nodes[check_node.name] + + if not check_def or not check_def.buildable_to then + return itemstack + end + + if minetest.is_protected(check_pos, player_name) then + return itemstack + end + end + end + + -- Create node + local dir_name + if door.state == 2 then + dir_name = name .. "_b" + else + dir_name = name .. "_a" + end + minetest.set_node(pos, {name = dir_name, param2 = door.dir}) + + -- Set metadata + local meta = minetest.get_meta(pos) + meta:set_int("state", door.state) + if pair then + local pair_pos = minetest.pos_to_string(pair) + local this_pos = minetest.pos_to_string(pos) + + meta:set_string("pair", pair_pos) + local pair_meta = minetest.get_meta(pair) + pair_meta:set_string("pair", this_pos) + end + + -- Create hidden nodes to prevent obstructing placement + swap_nodes(check_nodes, door, false) + + local def = minetest.registered_nodes[dir_name] + -- Other stuffs, unaffected, same as original + if def.protected then + meta:set_string("owner", player_name) + meta:set_string("infotext", def.description .. "\n" .. S("Owned by @1", player_name)) + end + + if not (creative and creative.is_enabled_for and creative.is_enabled_for(player_name)) then + itemstack:take_item() + end + + minetest.sound_play(def.sounds.place, {pos = pos}, true) + + on_place_node(pos, minetest.get_node(pos), + placer, node, itemstack, pointed_thing) + + return itemstack +end + + +-- CUSTOM HIDDEN NODES + +local hidden_def = { + description = S("Hidden Door Segment"), + drawtype = "airlike", + paramtype = "light", + paramtype2 = "facedir", + sunlight_propagates = true, + -- has to be walkable for falling nodes to stop falling. + walkable = true, + pointable = false, + diggable = false, + buildable_to = false, + floodable = false, + drop = "", + groups = {not_in_creative_inventory = 1}, + on_blast = function() end, + collision_box = { + type = "fixed", + fixed = {0,0,0,0,0,0}, + }, +} + +if bigdoors.debug_hidden_nodes then + local s = 0.3 + hidden_def.selection_box = {type = "fixed", fixed = {-s,-s,-s,s,s,s}} + hidden_def.tiles = {{ name = "bigdoors_debug.png" }} + hidden_def.groups = {} + hidden_def.pointable = true + hidden_def.drawtype = 'glasslike' +end + +minetest.register_node(bigdoors.modname..":hidden", hidden_def) -- doors:hidden has a hitbox, no thanks + +local function hidden_section(size) + -- Create hidden sections for hit-box purposes + local size_string = size_to_string(size) + local name = bigdoors.modname..":hidden_section_"..size_string + + local def = { + description = S("Hidden Door Section - "..size_string), + drawtype = "airlike", + paramtype = "light", + paramtype2 = "facedir", + sunlight_propagates = true, + -- has to be walkable for falling nodes to stop falling. + walkable = true, + is_ground_content = false, + pointable = false, + diggable = false, + buildable_to = false, + floodable = false, + drop = "", + groups = {not_in_creative_inventory = 1}, + on_blast = function() end, + selection_box = { + type = "fixed", + fixed = {0,0,0,0,0,0}, + }, + collision_box = { + type = "fixed", + fixed = new_hitbox(size) + } + } + + if bigdoors.debug_hidden_nodes then + local debug_select = new_hitbox(size) + debug_select[6] = -0.3 + debug_select[3] = -0.7 + def.selection_box = {type = "fixed", fixed = debug_select} + def.tiles = {{ name = "bigdoors_debug.png" }} + def.groups = {} + def.pointable = true + def.drawtype = 'glasslike' + end + + -- Register nodes + + minetest.register_node(":" .. name, def) +end + +for _,hidden_size in pairs(hidden_sizes) do + hidden_section(hidden_size) +end + + +-- REGISTER NEW DOOR + + +function by_value( t1 ) + local t2 = { } + if #t1 > 0 then + -- ordered copy of arrays + t2 = { unpack( t1 ) } + else + -- shallow copy of hashes + for k, v in pairs( t1 ) do + t2[ k ] = v + end + end + return t2 +end + + +function new_recipe(size, item) + local recipe = {} + local recipe_row = {} + for i=1,size.width,1 do + table.insert(recipe_row, item) + end + for i=1,size.height,1 do + table.insert(recipe, recipe_row) + end + return recipe +end + +function user_size_to_sys(sizes) + if not sizes then return end + local sys_sizes = {} + for _,size in ipairs(sizes) do + table.insert(sys_sizes, math.floor(size*10)) + end + return sys_sizes +end + +function size_variations(variations) + local valid_sizes = {} + + -- Generate list of all width-height combinations + local whcombos = {} + local heights = user_size_to_sys(variations.heights) or bigdoors.door_heights + local widths = user_size_to_sys(variations.widths) or bigdoors.door_widths + for _,h in ipairs(heights) do + for _,w in ipairs(widths) do + local size = {width=w/10,height=h/10} + local size_string = size_to_string(size) + whcombos[size_string] = size + end + end + + -- Use only those defined in sizes + local sizecombos = {} + if variations.sizes then + for _,size in ipairs(variations.sizes) do + local size_string = size_to_string(size) + if whcombos[size_string] then + sizecombos[size_string] = size + end + end + else + sizecombos = whcombos + end + + -- Use only those that maintain proportions + local propcombos = {} + if variations.original_proportions then + for size_string,size in pairs(sizecombos) do + if math.abs((size.width*2)-size.height) < 0.1 then + propcombos[size_string] = size + end + end + else + propcombos = sizecombos + end + + -- Use only existing size combinations + for size_string,_ in pairs(propcombos) do + if door_sizes[size_string] then + valid_sizes[size_string] = door_sizes[size_string] + end + end + return valid_sizes +end + +function bigdoors.register(originalname, config) + + local basedef = minetest.registered_nodes[originalname..'_a'] + local baseitem = minetest.registered_craftitems[originalname] + + if not config then + config = {} + end + + if config.replace_original and not config.original_recipe then + minetest.log("error", "BigDoors: Cannot replace original door ("..originalname..") if original_recipe is not defined. (Recipes cannot be retrieved from the minetest API)") + return + end + + local base_name = originalname + if config.name then + base_name = config.name + end + if not base_name:find(":") then + base_name = bigdoors.modname..":" .. base_name + end + + local recipe_name = originalname + if config.replace_original then + recipe_name = base_name..'_1020' + end + if config.replace_original then + replacement_doors[originalname] = base_name..'_1020' + end + + local valid_sizes = door_sizes + if config.variations then + valid_sizes = size_variations(config.variations) + end + + if config.replace_original and not valid_sizes["1020"] then + minetest.log("error", "BigDoors: Cannot replace original door ("..originalname..") if 1x2 size variation is disallowed") + return + end + + for size_string, size in pairs(valid_sizes) do + + -- Name + local name = base_name..'_'..size_string + + -- Create item + minetest.register_craftitem(":" .. name, { + description = baseitem.description .. ' (' .. tostring(size.width) .. ' x ' .. tostring(size.height) .. ')', + inventory_image = baseitem.inventory_image, + groups = table.copy(baseitem.groups), + on_place = on_place + }) + + -- Use recipe to create crafts + if size_string == "1020" and config.replace_original then + minetest.register_craft({ + output = name, + recipe = config.original_recipe, + }) + elseif config.recipe then + -- TODO: multiple recipes for each size? + local recipe = config.recipe[size_string] + if recipe then + local output = name + if recipe.output then + output = output..' '..tostring(recipe.output) + end + local recipe_obj = recipe + if recipe.recipe then + recipe_obj = recipe.recipe + end + minetest.register_craft({ + output = output, + recipe = recipe_obj, + }) + end + else + -- TODO: more complex crafts for breaking apart & making from other components? e.g. (1x3)+(1x3)=(2x3) + minetest.register_craft({ + output = name..' '..tostring(size.recipe.output), + recipe = new_recipe(size.recipe, recipe_name), + }) + if not config.replace_original then + minetest.register_craft({ + output = name..' '..tostring(size.recipe.output), + recipe = new_recipe(size.recipe, originalname), + }) + end + end + + local def = table.copy(basedef) + def.drop = name + def.door.name = name + + + -- Callbacks + def.on_rightclick = on_rightclick + + def.after_dig_node = after_dig_node + def.on_destruct = on_destruct + if not def.protected then + def.on_blast = on_blast_unprotected + end + + -- Model and hitbox + + -- TODO: replace by_value + local defa = by_value(def) + local defb = by_value(def) + local defc = by_value(def) + local defd = by_value(def) + + defa.selection_box = {type = "fixed", fixed = size.hitbox.ad} + defb.selection_box = {type = "fixed", fixed = size.hitbox.bc} + defc.selection_box = {type = "fixed", fixed = size.hitbox.bc} + defd.selection_box = {type = "fixed", fixed = size.hitbox.ad} + + defa.mesh = "bigdoor_a"..size_string..".obj" + defb.mesh = "bigdoor_b"..size_string..".obj" + defc.mesh = "bigdoor_c"..size_string..".obj" + defd.mesh = "bigdoor_d"..size_string..".obj" + + -- Register nodes + minetest.register_node(":" .. name .. "_a", defa) + minetest.register_node(":" .. name .. "_b", defb) + minetest.register_node(":" .. name .. "_c", defc) + minetest.register_node(":" .. name .. "_d", defd) + + doors.registered_doors[name .. "_a"] = true + doors.registered_doors[name .. "_b"] = true + doors.registered_doors[name .. "_c"] = true + doors.registered_doors[name .. "_d"] = true + end + + if config.replace_original then + -- disable original items + local swap_name = base_name..'_1020' + minetest.registered_craftitems[originalname] = table.copy(minetest.registered_craftitems[swap_name]) + minetest.registered_items[originalname] = table.copy(minetest.registered_items[swap_name]) + minetest.registered_craftitems[originalname].groups.not_in_creative_inventory = 1 + minetest.registered_items[originalname].groups.not_in_creative_inventory = 1 + -- replace old doors of this type automatically + minetest.register_lbm({ + name = ":"..bigdoors.modname..":replace_" .. originalname:gsub(":", "_"), + nodenames = {originalname.."_a", originalname.."_b", originalname.."_c", originalname.."_d"}, + action = function(new_pos, node) + local new_door = swap_name..string.sub(node.name,-2) + local new_node = {name = new_door, param2 = node.param2} + minetest.swap_node(new_pos, new_node) + local door = parse_door(nil, new_node) + local check_nodes = door_occupies(new_pos, door) + swap_nodes(check_nodes, door, not door.closed) + end + }) + end + +end +