diff options
Diffstat (limited to 'castle_gates/gate_functions.lua')
-rw-r--r-- | castle_gates/gate_functions.lua | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/castle_gates/gate_functions.lua b/castle_gates/gate_functions.lua new file mode 100644 index 0000000..4a3c7f7 --- /dev/null +++ b/castle_gates/gate_functions.lua @@ -0,0 +1,389 @@ +local MP = minetest.get_modpath(minetest.get_current_modname()) +dofile(MP.."/class_pointset.lua") + +-- Given a facedir, returns a set of all the corresponding directions +local get_dirs = function(facedir) + local dirs = {} + local top = {[0]={x=0, y=1, z=0}, + {x=0, y=0, z=1}, + {x=0, y=0, z=-1}, + {x=1, y=0, z=0}, + {x=-1, y=0, z=0}, + {x=0, y=-1, z=0}} + dirs.back = minetest.facedir_to_dir(facedir) + dirs.top = top[math.floor(facedir/4)] + dirs.right = { + x=dirs.top.y*dirs.back.z - dirs.back.y*dirs.top.z, + y=dirs.top.z*dirs.back.x - dirs.back.z*dirs.top.x, + z=dirs.top.x*dirs.back.y - dirs.back.x*dirs.top.y + } + dirs.front = vector.multiply(dirs.back, -1) + dirs.bottom = vector.multiply(dirs.top, -1) + dirs.left = vector.multiply(dirs.right, -1) + return dirs +end + +-- Returns the axis that dir points along +local dir_to_axis = function(dir) + if dir.x ~= 0 then + return "x" + elseif dir.y ~= 0 then + return "y" + else + return "z" + end +end + +-- Given a hinge definition, turns it into an axis and placement that can be used by the door rotation. +local interpret_hinge = function(hinge_def, pos, node_dirs) + local axis = dir_to_axis(node_dirs[hinge_def.axis]) + + local placement + if type(hinge_def.offset) == "string" then + placement = vector.add(pos, node_dirs[hinge_def.offset]) + elseif type(hinge_def.offset) == "table" then + placement = vector.new(0,0,0) + local divisor = 0 + for _, val in pairs(hinge_def.offset) do + placement = vector.add(placement, node_dirs[val]) + divisor = divisor + 1 + end + placement = vector.add(pos, vector.divide(placement, divisor)) + else + placement = pos + end + + return axis, placement +end + + +-------------------------------------------------------------------------- +-- Rotation (slightly more complex than sliding) + +local facedir_rotate = { + ['x'] = { + [-1] = {[0]=4, 5, 6, 7, 22, 23, 20, 21, 0, 1, 2, 3, 13, 14, 15, 12, 19, 16, 17, 18, 10, 11, 8, 9}, -- 270 degrees + [1] = {[0]=8, 9, 10, 11, 0, 1, 2, 3, 22, 23, 20, 21, 15, 12, 13, 14, 17, 18, 19, 16, 6, 7, 4, 5}, -- 90 degrees + }, + ['y'] = { + [-1] = {[0]=3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14, 7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}, -- 270 degrees + [1] = {[0]=1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16, 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22}, -- 90 degrees + }, + ['z'] = { + [-1] = {[0]=16, 17, 18, 19, 5, 6, 7, 4, 11, 8, 9, 10, 0, 1, 2, 3, 20, 21, 22, 23, 12, 13, 14, 15}, -- 270 degrees + [1] = {[0]=12, 13, 14, 15, 7, 4, 5, 6, 9, 10, 11, 8, 20, 21, 22, 23, 0, 1, 2, 3, 16, 17, 18, 19}, -- 90 degrees + } +} + --90 degrees CW about x-axis: (x, y, z) -> (x, -z, y) + --90 degrees CCW about x-axis: (x, y, z) -> (x, z, -y) + --90 degrees CW about y-axis: (x, y, z) -> (-z, y, x) + --90 degrees CCW about y-axis: (x, y, z) -> (z, y, -x) + --90 degrees CW about z-axis: (x, y, z) -> (y, -x, z) + --90 degrees CCW about z-axis: (x, y, z) -> (-y, x, z) +local rotate_pos = function(axis, direction, pos) + if axis == "x" then + if direction < 0 then + return {x= pos.x, y= -pos.z, z= pos.y} + else + return {x= pos.x, y= pos.z, z= -pos.y} + end + elseif axis == "y" then + if direction < 0 then + return {x= -pos.z, y= pos.y, z= pos.x} + else + return {x= pos.z, y= pos.y, z= -pos.x} + end + else + if direction < 0 then + return {x= -pos.y, y= pos.x, z= pos.z} + else + return {x= pos.y, y= -pos.x, z= pos.z} + end + end +end + +local rotate_pos_displaced = function(pos, origin, axis, direction) + -- position in space relative to origin + local newpos = vector.subtract(pos, origin) + newpos = rotate_pos(axis, direction, newpos) + -- Move back to original reference frame + return vector.add(newpos, origin) +end + +local get_buildable_to = function(pos) + return minetest.registered_nodes[minetest.get_node(pos).name].buildable_to +end + + +local get_door_layout = function(pos, facedir, player) + -- This method does a flood-fill looking for all nodes that meet the following criteria: + -- belongs to a "castle_gate" group + -- has the same "back" direction as the initial node + -- is accessible via up, down, left or right directions unless one of those directions goes through an edge that one of the two nodes has marked as a gate edge + local door = {} + + door.all = {} + door.contains_protected_node = false + door.directions = get_dirs(facedir) + door.previous_move = minetest.get_meta(pos):get_string("previous_move") + + -- temporary pointsets used while searching + local to_test = Pointset.create() + local tested = Pointset.create() + local can_slide_to = Pointset.create() + + local castle_gate_group_value -- this will be populated from the first gate node we encounter, which will be the one that was clicked on + + to_test:set_pos(pos, true) + + local test_pos, _ = to_test:pop() + while test_pos ~= nil do + tested:set_pos(test_pos, true) -- track nodes we've looked at + local test_node = minetest.get_node(test_pos) + + if test_node.name == "ignore" then + --array is next to unloaded nodes, too dangerous to do anything. Abort. + return nil + end + + if minetest.is_protected(test_pos, player:get_player_name()) and not minetest.check_player_privs(player, "protection_bypass") then + door.contains_protected_node = true + end + + local test_node_def = minetest.registered_nodes[test_node.name] + can_slide_to:set_pos(test_pos, test_node_def.buildable_to == true) + + if test_node_def.paramtype2 == "facedir" then + local test_node_dirs = get_dirs(test_node.param2) + local coplanar = vector.equals(test_node_dirs.back, door.directions.back) + + if castle_gate_group_value == nil and test_node_def.groups.castle_gate ~= nil then + castle_gate_group_value = test_node_def.groups.castle_gate -- read the group value from the first gate node encountered + end + + if coplanar and test_node_def.groups.castle_gate == castle_gate_group_value then + local entry = {["pos"] = test_pos, ["node"] = test_node} + table.insert(door.all, entry) + if test_node_def._gate_hinge ~= nil then + local axis, placement = interpret_hinge(test_node_def._gate_hinge, test_pos, test_node_dirs) + if door.hinge == nil then + door.hinge = {axis=axis, placement=placement} + elseif door.hinge.axis ~= axis then + return nil -- Misaligned hinge axes, door cannot rotate. + else + local axis_dir = {x=0, y=0, z=0} + axis_dir[axis] = 1 + local displacement = vector.normalize(vector.subtract(placement, door.hinge.placement)) + if not (vector.equals(displacement, axis_dir) or vector.equals(displacement, vector.multiply(axis_dir, -1))) then + return nil -- Misaligned hinge offset, door cannot rotate. + end + end + end + + can_slide_to:set_pos(test_pos, true) -- since this is part of the door, other parts of the door can slide into it + + local test_directions = {"top", "bottom", "left", "right"} + for _, dir in pairs(test_directions) do + local adjacent_pos = vector.add(test_pos, door.directions[dir]) + local adjacent_node = minetest.get_node(adjacent_pos) + local adjacent_def = minetest.registered_nodes[adjacent_node.name] + can_slide_to:set_pos(adjacent_pos, adjacent_def.buildable_to == true or adjacent_def.groups.castle_gate) + + if test_node_def._gate_edges == nil or not test_node_def._gate_edges[dir] then -- if we ourselves are an edge node, don't look in the direction we're an edge in + if tested:get_pos(adjacent_pos) == nil then -- don't look at nodes that have already been looked at + if adjacent_def.paramtype2 == "facedir" then -- all doors are facedir nodes so we can pre-screen some targets + + local edge_points_back_at_test_pos = false + -- Look at the adjacent node's definition. If it's got gate edges, check if they point back at us. + if adjacent_def._gate_edges ~= nil then + local adjacent_directions = get_dirs(adjacent_node.param2) + for dir, val in pairs(adjacent_def._gate_edges) do + if vector.equals(vector.add(adjacent_pos, adjacent_directions[dir]), test_pos) then + edge_points_back_at_test_pos = true + break + end + end + end + + if not edge_points_back_at_test_pos then + to_test:set_pos(adjacent_pos, true) + end + end + end + end + end + end + end + + test_pos, _ = to_test:pop() + end + + if door.hinge == nil then + --sliding door, evaluate which directions it can go + door.can_slide = {top=true, bottom=true, left=true, right=true} + for _,door_node in pairs(door.all) do + door.can_slide.top = door.can_slide.top and can_slide_to:get_pos(vector.add(door_node.pos, door.directions.top)) + door.can_slide.bottom = door.can_slide.bottom and can_slide_to:get_pos(vector.add(door_node.pos, door.directions.bottom)) + door.can_slide.left = door.can_slide.left and can_slide_to:get_pos(vector.add(door_node.pos, door.directions.left)) + door.can_slide.right = door.can_slide.right and can_slide_to:get_pos(vector.add(door_node.pos, door.directions.right)) + end + else + --rotating door, evaluate which direction it can go. Slightly more complicated. + local origin = door.hinge.placement + local axis = door.hinge.axis + local backfront = dir_to_axis(door.directions.back) + local leftright = dir_to_axis(door.directions.right) + + door.swings = {} + + for _, direction in pairs({-1, 1}) do + door.swings[direction] = true + for _, door_node in pairs(door.all) do + origin[axis] = door_node.pos[axis] + if not vector.equals(door_node.pos, origin) then -- There's no obstruction if the node is literally located along the rotation axis + local newpos = rotate_pos_displaced(door_node.pos, origin, axis, direction) + local newnode = minetest.get_node(newpos) + local newdef = minetest.registered_nodes[newnode.name] + if not newdef.buildable_to then -- check if the destination node is free. + door.swings[direction] = false + break + end + + local swing_corner = {} -- the corner of the square "arc" that a Minetest gate swings through + local scan_dir + swing_corner[axis] = door_node.pos[axis] + swing_corner[backfront] = newpos[backfront] + swing_corner[leftright] = door_node.pos[leftright] + if not (vector.equals(newpos, swing_corner) or vector.equals(door_node.pos, swing_corner)) then -- we're right next to the hinge, no need for further testing + scan_dir = vector.direction(newpos, swing_corner) -- get the direction from the new door position toward the swing corner + repeat + newpos = vector.add(newpos, scan_dir) -- we start with newpos on the destination node, which has already been tested. + if not get_buildable_to(newpos) then + door.swings[direction] = false + end + until vector.equals(newpos, swing_corner) or door.swings[direction] == false + + if not (vector.equals(newpos, door_node.pos) or door.swings[direction] == false) then + scan_dir = vector.direction(newpos, door_node.pos) + newpos = vector.add(newpos, scan_dir) -- the first step here is a freebie since we've already checked swing_corner + while not (vector.equals(newpos, door_node.pos) or door.swings[direction] == false) do + if not get_buildable_to(newpos) then + door.swings[direction] = false + end + newpos = vector.add(newpos, scan_dir) + end + end + end + end + + if door.swings[direction] == false then + break + end + + end + end + end + return door +end + + +local slide_gate = function(door, direction) + for _, door_node in pairs(door.all) do + door_node.pos = vector.add(door_node.pos, door.directions[direction]) + end + door.previous_move = direction +end + +local rotate_door = function (door, direction) + if not door.swings[direction] then + return false + end + + local origin = door.hinge.placement + local axis = door.hinge.axis + + for _, door_node in pairs(door.all) do + door_node.pos = rotate_pos_displaced(door_node.pos, origin, axis, direction) + door_node.node.param2 = facedir_rotate[axis][direction][door_node.node.param2] + end + return true +end + + +---------------------------------------------------------------------------------------------------- +-- When creating new gate pieces use this as the "on_rightclick" method of their node definitions +-- if you want the player to be able to trigger the gate by clicking on that particular node. +-- If you just want the node to move with the gate and not trigger it this isn't necessary, +-- only the "castle_gate" group is needed for that. + +castle_gates.trigger_gate = function(pos, node, player) + local door = get_door_layout(pos, node.param2, player) + + if door ~= nil then + for _, door_node in pairs(door.all) do + minetest.set_node(door_node.pos, {name="air"}) + end + + local door_moved = false + if door.can_slide ~= nil then -- this is a sliding door + if door.previous_move == "top" and door.can_slide.top then + slide_gate(door, "top") + door_moved = true + elseif door.previous_move == "bottom" and door.can_slide.bottom then + slide_gate(door, "bottom") + door_moved = true + elseif door.previous_move == "left" and door.can_slide.left then + slide_gate(door, "left") + door_moved = true + elseif door.previous_move == "right" and door.can_slide.right then + slide_gate(door, "right") + door_moved = true + end + + if not door_moved then -- reverse door's direction for next time + if door.previous_move == "top" and door.can_slide.bottom then + door.previous_move = "bottom" + elseif door.previous_move == "bottom" and door.can_slide.top then + door.previous_move = "top" + elseif door.previous_move == "left" and door.can_slide.right then + door.previous_move = "right" + elseif door.previous_move == "right" and door.can_slide.left then + door.previous_move = "left" + else + -- find any open direction + for slide_dir, enabled in pairs(door.can_slide) do + if enabled then + door.previous_move = slide_dir + break + end + end + end + end + elseif door.hinge ~= nil then -- this is a hinged door + if door.previous_move == "deosil" then + door_moved = rotate_door(door, 1) + elseif door.previous_move == "widdershins" then + door_moved = rotate_door(door, -1) + end + + if not door_moved then + if door.previous_move == "deosil" then + door.previous_move = "widdershins" + else + door.previous_move = "deosil" + end + end + end + + for _, door_node in pairs(door.all) do + minetest.set_node(door_node.pos, door_node.node) + minetest.get_meta(door_node.pos):set_string("previous_move", door.previous_move) + end + + if door_moved then + minetest.after(1, function() + castle_gates.trigger_gate(door.all[1].pos, door.all[1].node, player) + end) + end + end +end
\ No newline at end of file |