local S = minetest.get_translator("lzr_laser") -- Opacity (0-255) of skull when it is "gone", -- i.e. is non-walkable local OPACITY_SKULL_GONE = 128 -- Special string to mark a tile in a laser element as the 'laser tile' -- for lzr_laser.register_element. The laser tile is the -- the tile (texture) that represents the laser itself. This string -- will be replaced automatically by the appropriate laser texture -- on registration lzr_laser.LASER_TILE = "<<>>" local colortiles = { lzr_laser.TILE_LASER_R, lzr_laser.TILE_LASER_G, lzr_laser.TILE_LASER_Y, lzr_laser.TILE_LASER_B, lzr_laser.TILE_LASER_M, lzr_laser.TILE_LASER_C, lzr_laser.TILE_LASER_W, } local mirror_out = { [0] = {1,0,0}, [1] = {0,0,-1}, [2] = {-1,0,0}, [3] = {0,0,1}, [4] = {1,0,0}, [5] = {0,1,0}, [6] = {-1,0,0}, [7] = {0,-1,0}, [8] = {1,0,0}, [9] = {0,-1,0}, [10] = {-1,0,0}, [11] = {0,1,0}, [12] = {0,-1,0}, [13] = {0,0,-1}, [14] = {0,1,0}, [15] = {0,0,1}, [16] = {0,1,0}, [17] = {0,0,-1}, [18] = {0,-1,0}, [19] = {0,0,1}, [20] = {-1,0,0}, [21] = {0,0,-1}, [22] = {1,0,0}, [23] = {0,0,1}, } lzr_laser.get_front_dir = function(param2) if not param2 then return end local dir_input = minetest.facedir_to_dir(param2) if not dir_input then return end local v = vector.new(dir_input[1], dir_input[2], dir_input[3]) dir_input = vector.multiply(v, -1) return dir_input end lzr_laser.get_front_dir = function(param2) if not param2 then return end local dir_input = minetest.facedir_to_dir(param2) if not dir_input then return end local v = vector.new(dir_input[1], dir_input[2], dir_input[3]) dir_input = vector.multiply(v, -1) return dir_input end lzr_laser.get_barrel_axis = function(param2) if not param2 then return end if (param2 >= 0 and param2 <= 3) or (param2 >= 20 and param2 <= 23) then return "y" elseif (param2 >= 4 and param2 <= 11) then return "z" elseif (param2 >= 12 and param2 <= 19) then return "x" else return "y" end end lzr_laser.get_pane_axis = function(param2) if not param2 then return end local dir = minetest.facedir_to_dir(param2) if dir.x ~= 0 then return "x" elseif dir.y ~= 0 then return "y" elseif dir.z ~= 0 then return "z" end end lzr_laser.check_front = function(node_pos, laser_dir) local node = minetest.get_node(node_pos) local node_dir_in = lzr_laser.get_front_dir(node.param2) if not node_dir_in then return false end local reverse_laser_dir = vector.multiply(laser_dir, -1) if vector.equals(reverse_laser_dir, node_dir_in) then return true end return false end lzr_laser.check_detector = function(detector_pos, nodename, laser_dir, laser_color) local detector_group = minetest.get_item_group(nodename, "detector") if detector_group == 0 then return false end -- Check if laser goes into hole if lzr_laser.check_front(detector_pos, laser_dir) then local detector_color = minetest.get_item_group(nodename, "detector_color") -- Detector does not care about color: Instant success if detector_color == 0 then return true -- If detector *does* care about color, it must be an exact match elseif laser_color == detector_color then return true end end return false end lzr_laser.get_mirror_dirs = function(param2) local dir_input = minetest.facedir_to_dir(param2) if not dir_input then return end dir_input = vector.multiply(dir_input, -1) local dir_output = vector.new(unpack(mirror_out[param2])) return dir_input, dir_output end -- Mirror a laser that touches a mirror with a laser coming towards -- the mirror with the laser_dir direction (direction vector). -- * nodename: Name of mirror node -- * param2: param2 of mirror node -- * laser_dir: Direction of incoming laser -- Returns , -- 1) If the laser can be mirrored, then is the direction of -- the mirrored laser and denotes on of the 2 possible -- sides the laser came from: true if it came in the front side, false if -- it came from the angled side. is useful for the -- transmissive mirror. -- 2) If the laser cannot be mirrored is false and -- is nil lzr_laser.get_mirrored_laser_dir = function(nodename, param2, laser_dir) local mirror_group = minetest.get_item_group(nodename, "mirror") local mirror_transmissive_group = minetest.get_item_group(nodename, "transmissive_mirror") if mirror_group == 0 and mirror_transmissive_group == 0 then return false end local reverse_laser_dir = vector.multiply(laser_dir, -1) local mirror_dir_in, mirror_dir_out = lzr_laser.get_mirror_dirs(param2) if not mirror_dir_in then return false end if vector.equals(reverse_laser_dir, mirror_dir_in) then return mirror_dir_out, true elseif vector.equals(reverse_laser_dir, mirror_dir_out) then return mirror_dir_in, false end return false end function lzr_laser.unlock_chests(min, max) local closed_chests = minetest.find_nodes_in_area(min, max, {"group:chest_closed"}) local detectors = minetest.find_nodes_in_area(min, max, {"group:detector"}) for c=1, #closed_chests do local cpos = closed_chests[c] local node = minetest.get_node(cpos) local def = minetest.registered_nodes[node.name] if def._lzr_unlock then def._lzr_unlock(cpos, node) for d=1, #detectors do local dpos = detectors[d] lzr_laser.particle_line(dpos, cpos, "lzr_laser_particle_trigger.png") end end end end -- Update the whole playfield and check for victory local full_update = function() local state = lzr_gamestate.get_state() if state ~= lzr_gamestate.LEVEL and state ~= lzr_gamestate.LEVEL_COMPLETE and state ~= lzr_gamestate.EDITOR then return end lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END) if state == lzr_gamestate.EDITOR then return end local done = lzr_laser.check_level_won() if done then lzr_levels.level_complete() else local detectors = lzr_laser.check_all_detectors() if detectors then lzr_laser.unlock_chests(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END) end end end -- Same as above, but special case after a detector was placed. -- This is hack to tell the detector check that the one detector in the player -- inventory has to be ignored because after_dig_node is called BEFORE -- the inventory change after placing the node. local full_update_detector_placed = function() local state = lzr_gamestate.get_state() if state ~= lzr_gamestate.LEVEL and state ~= lzr_gamestate.LEVEL_COMPLETE and state ~= lzr_gamestate.EDITOR then return end lzr_laser.full_laser_update(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END) if state == lzr_gamestate.EDITOR then return end local done = lzr_laser.check_level_won() if done then lzr_levels.level_complete() else local detectors = lzr_laser.check_all_detectors(true) if detectors then lzr_laser.unlock_chests(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END) end end end minetest.register_on_placenode(function(pos, newnode, placer) local state = lzr_gamestate.get_state() if state ~= lzr_gamestate.LEVEL and state ~= lzr_gamestate.LEVEL_COMPLETE and state ~= lzr_gamestate.EDITOR then return end if minetest.get_item_group(newnode.name, "detector") ~= 0 then full_update_detector_placed() else full_update() end end) minetest.register_on_dignode(function(pos, newnode, placer) local state = lzr_gamestate.get_state() if state ~= lzr_gamestate.LEVEL and state ~= lzr_gamestate.LEVEL_COMPLETE and state ~= lzr_gamestate.EDITOR then return end full_update() end) local after_rotate = function() full_update() end local element_on_place = function(itemstack, placer, pointed_thing) -- node's on_rightclick action takes precedence if pointed_thing.type == "node" and placer then local node = minetest.get_node(pointed_thing.under) local def = minetest.registered_nodes[node.name] local sneak = placer:get_player_control().sneak if def and def.on_rightclick and not sneak then def.on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing) return itemstack end end -- Default node placement behavior return minetest.item_place_node(itemstack, placer, pointed_thing) end --[[ Register a 'laser element', i.e. a collection of nodes that are supposed to interact with lasers. Each of these nodes is called a 'laser block'. A single laser element may register many many nodes, depending on its complexity. Laser elements may have the following properties: * They may be in an active or inactive state * They may be takable by the player, or not (must be enabled with `options.allow_take`) * For the active state, by default a node for every laser color will be added * All its nodes share a new group that identify it as part of a laser element (in `options.group`) The registered nodes follow these naming conventions: * ``: This is the base node. Inactive, cannot be taken * `_on_`: Active, cannot be taken. is a colorcode from 1 to lzr_globals.MAX_COLORCODE * `_takable`: Inactive, can be taken * `_takable_on_`: Active, can be taken. is a colorcode from 1 to lzr_globals.MAX_COLORCODE If `options.register_color` is false, the active nodes won't have a `` suffix. Instead, they will be: * `_on`: Active, non-takable * `_takable_on`: Active, takable Groups: The following groups will be added automatically to the nodes: * `[options.group]=1` to the inactive nodes * `[options.group]=2` to the active nodes * `breakable=1` to every node * `takable=1` to nodes that can be taken by the player * `not_in_creative_inventory=1` to active nodes by default * `laser_block_color=` to active nodes with a laser color (not if `options.register_color` is false) Parameters: * basename: Itemstring identifier of the base node May be preceded by a color to force a node name, as handled by minetest.register_node * def: Definition table to define the node. All fields of the Minetest node definition can be applied here. In addition, the following fields are special: * description: Description of the base node. The other node variants will have automatic variations added * after_rotate: Called after the node was rotated by the rotating hook * tiles_off: `tiles` table for the inactive and non-takable state * tiles_on: `tiles` table for the active and non-takable state * tiles_takable_off: `tiles` table for the inactive and non-takable state * tiles_takable_on: `tiles` table for the active and non-takable state * mesh_off: `mesh` value in inactive state * mesh_on: `mesh` value in active state * light_source_on: `light_source` value for the nodes in active state (default: 0) * light_source_off: `light_source` value for the nodes in inactive state (default: 0) Note about tiles: If your laser element has a laser in it, use the special tile `lzr_laser.LASER_TILE` in the `tiles_*` tables to represent the tiles texture. This function will replace it with the appropriate laser color. * options: Additional options to further specify the laser element. All fields are optional except 'group'. List of fields: * group: Mandatory name of group that identifies the nodes as part of the laser elementto add. Will add [group]=1 for inactive nodes and [group]=2 for active nodes * not_walkable_if_off: If true, the element turns non-walkable if active (default: false) * allow_take: If true, add 'takable' nodes (default: false) * keep_state_on_take: If true, element will keep its on/off state when taken by player (default: false) * activate: If true, add 'activated' nodes (default: true) * inactive: If set, override define the node name of the element in inactive state (default: nil) * active_in_creative: If true, allow the 'active' nodes to be shown in the “Creative inventory” (read: level editor) (default: false) * register_colors: If true, all laser color variants will be added for the active node state, registering a node for every colorcode (default: true) ]] lzr_laser.register_element = function(basename, def, options) local real_basename = basename if string.sub(basename, 1, 1) == ":" then real_basename = string.sub(basename, 2, -1) end local def_core = table.copy(def) if not options then options = {} end if options.not_walkable_if_off then def_core.walkable = false end if options.allow_take then def_core._lzr_takable = real_basename.."_takable" end if options.allow_take then def_core.description = S("@1 (fixed)", def.description) else def_core.description = def.description end def_core._after_rotate = after_rotate if options.activate ~= false then def_core._lzr_active = real_basename.."_on" end def_core.tiles = def.tiles_off def_core.mesh = def.mesh_off if not def_core.groups then def_core.groups = {} end def_core.groups.breakable = 1 local groupname if options.group then groupname = options.group else error("[lzr_laser] lzr_laser.register_element: No group name specified in options for laser element with basename: "..tostring(basename)) end def_core.groups[groupname] = 1 def_core.on_place = element_on_place if options.inactive ~= nil then def_core._lzr_inactive = options.inactive if not options.keep_state_on_take then def_core.drop = options.inactive end end minetest.register_node(basename, def_core) local def_core_on if options.activate ~= false then def_core_on = table.copy(def_core) if options.not_walkable_if_off then def_core_on.walkable = true elseif options.not_walkable_if_on then def_core_on.walkable = false end if options.allow_take then def_core_on.description = S("@1 (fixed, active)", def.description) else def_core_on.description = S("@1 (active)", def.description) end def_core_on._lzr_inactive = real_basename def_core_on.tiles = def.tiles_on def_core_on.mesh = def.mesh_on if not options.keep_state_on_take then def_core_on.drop = real_basename end if options.allow_take then def_core_on._lzr_takable = real_basename.."_takable_on" end def_core_on.groups[groupname] = 2 if not options.active_in_creative then def_core_on.groups.not_in_creative_inventory = 1 def_core_on.inventory_image = nil def_core_on.wield_image = nil end def_core_on.light_source = def.light_source_on if options.register_color == false then minetest.register_node(basename.."_on", def_core_on) else for c=1, lzr_globals.MAX_COLORCODE do local def_core_on_c = table.copy(def_core_on) for t=1, #def_core_on_c.tiles do if def_core_on_c.tiles[t] == lzr_laser.LASER_TILE then def_core_on_c.tiles[t] = colortiles[c] end end def_core_on_c.groups.laser_block_color = c minetest.register_node(basename.."_on_"..c, def_core_on_c) end end end if options.allow_take then local def_takable = table.copy(def_core) def_takable.tiles = def.tiles_takable_off def_takable.groups.takable = 1 def_takable._lzr_untakable = real_basename def_takable.description = def.description if options.inactive ~= nil then def_takable._lzr_inactive = options.inactive.."_takable" if not options.keep_state_on_take then def_takable.drop = options.inactive.."_takable" end end minetest.register_node(basename.."_takable", def_takable) if options.activate ~= false then def_takable._lzr_active = real_basename.."_takable_on" local def_takable_on = table.copy(def_core_on) def_takable_on._lzr_untakable = real_basename.."_on" def_takable_on.tiles = def.tiles_takable_on def_takable_on.light_source = def.light_source_on def_takable_on.groups.takable = 1 if not options.active_in_creative then def_takable_on.groups.not_in_creative_inventory = 1 def_takable_on.inventory_image = nil def_takable_on.wield_image = nil end def_takable_on.description = S("@1 (active)", def.description) if not options.keep_state_on_take then def_takable_on.drop = real_basename.."_takable" end def_takable_on._lzr_inactive = real_basename.."_takable" def_takable_on._lzr_active = real_basename.."_takable_on" if options.register_color == false then minetest.register_node(basename.."_takable_on", def_takable_on) else for c=1, lzr_globals.MAX_COLORCODE do local def_takable_on_c = table.copy(def_takable_on) for t=1, #def_takable_on_c.tiles do if def_takable_on_c.tiles[t] == lzr_laser.LASER_TILE then def_takable_on_c.tiles[t] = colortiles[c] end end def_takable_on_c.groups.laser_block_color = c minetest.register_node(basename.."_takable_on_"..c, def_takable_on_c) end end end end end minetest.register_node("lzr_laser:crate", { description = S("Heavy Crate"), tiles = { "lzr_laser_crate_fixed.png", }, sounds = lzr_sounds.node_sound_wood_defaults(), groups = { crate = 1, breakable = 1 }, }) minetest.register_node("lzr_laser:crate_mossy", { description = S("Mossy Heavy Crate"), tiles = { "lzr_laser_crate_fixed_mossy.png", }, sounds = lzr_sounds.node_sound_wood_defaults(), groups = { crate = 1, breakable = 1 }, }) minetest.register_node("lzr_laser:crate_takable", { description = S("Light Crate"), tiles = { "lzr_laser_crate.png", }, sounds = lzr_sounds.node_sound_wood_defaults(), groups = { crate = 1, breakable = 1, takable = 1 }, }) lzr_laser.register_element("lzr_laser:mirror", { description = S("Mirror"), paramtype = "light", paramtype2 = "facedir", tiles_off = { {name="lzr_laser_mirror_block.png^lzr_laser_fixed.png", backface_culling=true}, {name="lzr_laser_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_mirror_backside.png^lzr_laser_fixed.png", backface_culling=true}, }, tiles_on = { lzr_laser.LASER_TILE, {name="lzr_laser_mirror_block.png^lzr_laser_fixed.png", backface_culling=true}, {name="lzr_laser_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_mirror_backside.png^lzr_laser_fixed.png", backface_culling=true}, }, tiles_takable_off = { {name="lzr_laser_mirror_block.png", backface_culling=true}, {name="lzr_laser_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_mirror_backside.png", backface_culling=true}, }, tiles_takable_on = { lzr_laser.LASER_TILE, {name="lzr_laser_mirror_block.png", backface_culling=true}, {name="lzr_laser_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_mirror_backside.png", backface_culling=true}, }, use_texture_alpha = lzr_laser.ALPHA_LASER, drawtype = "mesh", mesh_off = "lzr_laser_mirror.obj", mesh_on = "lzr_laser_mirror_on.obj", light_source_on = lzr_globals.LASER_GLOW, groups = { rotatable = 1, laser_block = 1 }, sounds = lzr_sounds.node_sound_glass_defaults({ _rotate = {name = "lzr_laser_mirror_rotate", gain = 1.0}, }) }, { group = "mirror", allow_take = true }) local tm_def = { description = S("Transmissive Mirror"), paramtype = "light", paramtype2 = "facedir", use_texture_alpha = lzr_laser.ALPHA_LASER, drawtype = "mesh", groups = { rotatable = 1, laser_block = 1 }, sounds = lzr_sounds.node_sound_glass_defaults({ _rotate = {name = "lzr_laser_mirror_rotate", gain = 1.0}, }), } -- Transmissive mirror, inactive (also known as "State 00") local tm_def_off = table.copy(tm_def) tm_def_off._lzr_transmissive_mirror_state = "00" tm_def_off.tiles_off = { {name="lzr_laser_transmissive_mirror_block.png^lzr_laser_fixed.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_off.tiles_takable_off = { {name="lzr_laser_transmissive_mirror_block.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_off.mesh_off = "lzr_laser_tmirror.obj" local tm_options_off = { allow_take = true, activate = false, group = "transmissive_mirror", register_color = false } lzr_laser.register_element("lzr_laser:transmissive_mirror_00", tm_def_off, tm_options_off) -- Transmissive Mirror, active -- Register active transmissive mirrors --[[ This registers a LOT of nodes, for each laser color AND laser color combination. The transmissive mirror needs to deal with multiple cases in which the laser impacts the mirror from one of two possible sides of the angled mirror. There are 4 basic possibilities: * No laser: State 00 (inactive state, already handled above) * Laser from side A: State X0 * Laser from side B: State 0X * Laser from both sides: State XX Where X = a colorcode of laser The incoming direction of the laser is important because it determines on which side the laser will "go through" the mirror. In the following pictures, we will label each side: * A: Front of the mirror * B: Right of the mirror (relatively spoken) * C: Opposite side of A * D: Opposite side of B C D B A The lasers may come from side A or side B. If they come from side C or D, they are blocked. A laser from side A will be deflected to B but also be "let through" to C. This is State X0. C ^ | +---+ | |/ D | /---> B |/| + | | ^ A A laser from side B will be deflected to A but also be "let through" to D. This is State 0X. C +---+ | / D <--|-/---< B |/| + | | v A If lasers come from both sides, this is essentially a combination of State 0X and X0. This is State XX. It looks like this: C ^ | | +---+ | |/ D <--|-/----<> B |/| + | | ^ v A Laser from A is deflected to B, AND the laser from B is deflected to A. This causes both lasers to overlap while travelling between A and B, causing the colors to be mixed. E.g. red + green = yellow. This is just the normal laser behavior, overlapping happens with the regular laser as well. But also, laser from A goes through to C, AND laser from B goes through to D. The "let through" portion of the node must assume the color of the incoming laser and NOT the mixed color between A and B. This means in State XX, it is possible for the node having to deal with not one, but three laser colors: One for the portion A-B, one for center to D, and one for center to C. This unfortunately means that a LOT of nodes must be registered, one for each possible combination. For example, with a red laser from A, and a green laser from B, the laser colors of the node must be: * A to B: yellow (mix from red and green) * center to C: red * center to D: green However, even if there can be up to 3 laser colors in the same node, the node only needs to "know" 2 colors, not 3: one for each incoming laser. The 3rd laser laser color results from color mixing and can be derived from the incoming lasers. If the laser only comes from one side, the lasers inside the node will always be of the same color, so fewer registrations are needed here. This is because both the deflected laser and the laser that goes through are the same laser. <<< WHAT DOES THIS ALL MEAN??? >>> In short: * If no lasers are incoming (state 00), nothing special to do. * If one laser is incoming (state X0 or 0X), register nodes for every colorcode. * If two lasers are incoming (state XX), register nodes for each possible combination of *two* colorcodes ]] -- Definition templates for active transmissive mirrors -- State 0X (1 laser from one side) local tm_def_on_0X = table.copy(tm_def) tm_def_on_0X.light_source = lzr_globals.LASER_GLOW if not tm_def_on_0X.groups then tm_def_on_0X.groups = {} end tm_def_on_0X.groups.not_in_creative_inventory = 1 tm_def_on_0X.tiles_off = { lzr_laser.LASER_TILE, -- << will be replaced {name="lzr_laser_transmissive_mirror_block.png^lzr_laser_fixed.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_on_0X.tiles_takable_off = { lzr_laser.LASER_TILE, -- << will be replaced {name="lzr_laser_transmissive_mirror_block.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_on_0X.mesh_off = "lzr_laser_tmirror_on_thru1.obj" -- State X0 (1 laser from other side) local tm_def_on_X0 = table.copy(tm_def_on_0X) tm_def_on_X0.mesh_off = "lzr_laser_tmirror_on_thru2.obj" -- State XX (laser from both sides) local tm_def_on_XX = table.copy(tm_def_on_0X) tm_def_on_XX.tiles_off = { lzr_laser.LASER_TILE, -- << incoming lasers lzr_laser.LASER_TILE, -- << outgoing laser behind mirror lzr_laser.LASER_TILE, -- << outgoing laser behind mirror (other side) {name="lzr_laser_transmissive_mirror_block.png^lzr_laser_fixed.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_on_XX.tiles_takable_off = { lzr_laser.LASER_TILE, -- << incoming lasers lzr_laser.LASER_TILE, -- << outgoing laser behind mirror lzr_laser.LASER_TILE, -- << outgoing laser behind mirror (other side) {name="lzr_laser_transmissive_mirror_block.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror.png", backface_culling=true}, {name="lzr_laser_transmissive_mirror_mirror_out.png", backface_culling=true}, } tm_def_on_XX.mesh_off = "lzr_laser_tmirror_on_thru3.obj" local tm_options_on = table.copy(tm_options_off) tm_options_on.inactive = "lzr_laser:transmissive_mirror_00" -- Iterate through all possible combinations of 2 colorcodes -- and register the neccessary active transmissive mirrors accordingly for laser1=0, #colortiles do for laser2=0, #colortiles do local laserid = tostring(laser1) .. tostring(laser2) -- State 0X (X = 1 to max. colorcode) if laser1 == 0 and laser2 > 0 then local def = table.copy(tm_def_on_0X) def._lzr_transmissive_mirror_state = laserid def.description = S("Transmissive Mirror (active, @1)", laserid) -- Apply the laser color def.tiles_off[1] = colortiles[laser2] def.tiles_takable_off[1] = colortiles[laser2] lzr_laser.register_element("lzr_laser:transmissive_mirror_"..laserid, def, tm_options_on) -- State X0 elseif laser1 > 0 and laser2 == 0 then local def = table.copy(tm_def_on_X0) def._lzr_transmissive_mirror_state = laserid def.description = S("Transmissive Mirror (active, @1)", laserid) -- Apply the laser color def.tiles_off[1] = colortiles[laser1] def.tiles_takable_off[1] = colortiles[laser1] lzr_laser.register_element("lzr_laser:transmissive_mirror_"..laserid, def, tm_options_on) -- State XX elseif laser1 > 0 and laser2 > 0 then local def = table.copy(tm_def_on_XX) def._lzr_transmissive_mirror_state = laserid def.description = S("Transmissive Mirror (active, @1)", laserid) -- Apply the laser color (need 3 textures, explained above) local laser_mixed = bit.bor(laser1, laser2) def.tiles_off[1] = colortiles[laser_mixed] def.tiles_takable_off[1] = colortiles[laser_mixed] def.tiles_off[2] = colortiles[laser1] def.tiles_takable_off[2] = colortiles[laser1] def.tiles_off[3] = colortiles[laser2] def.tiles_takable_off[3] = colortiles[laser2] lzr_laser.register_element("lzr_laser:transmissive_mirror_"..laserid, def, tm_options_on) end end end lzr_laser.register_element("lzr_laser:crystal", { description = S("Crystal"), paramtype = "light", paramtype2 = "facedir", tiles_takable_off = { "lzr_laser_crystal.png", }, tiles_takable_on = { "lzr_laser_crystal_on.png", }, tiles_off = { "lzr_laser_crystal.png^lzr_laser_fixed.png", }, tiles_on = { "lzr_laser_crystal_on.png^lzr_laser_fixed.png", }, light_source_on = lzr_globals.LASER_GLOW, groups = { laser_block = 1 }, sounds = lzr_sounds.node_sound_glass_defaults(), }, { allow_take = true, group = "crystal" }) -- List of emitters local emitters = { -- list order: color name, suffix for item ID, suffix for texture, description, colorcode { "red", "_red", "", S("Red Emitter"), lzr_globals.COLOR_RED }, { "green", "_green", "_green", S("Green Emitter"), lzr_globals.COLOR_GREEN }, { "blue", "_blue", "_blue", S("Blue Emitter"), lzr_globals.COLOR_BLUE }, { "yellow", "_yellow", "_yellow", S("Yellow Emitter"), lzr_globals.COLOR_YELLOW }, { "magenta", "_magenta", "_magenta", S("Magenta Emitter"), lzr_globals.COLOR_MAGENTA }, { "cyan", "_cyan", "_cyan", S("Cyan Emitter"), lzr_globals.COLOR_CYAN }, { "white", "_white", "_white", S("White Emitter"), lzr_globals.COLOR_WHITE }, } for e=1, #emitters do local suffix = emitters[e][2] local suffix_tex = emitters[e][3] lzr_laser.register_element("lzr_laser:emitter"..suffix, { description = emitters[e][4], paramtype2 = "facedir", tiles_takable_off = { "lzr_laser_emitter"..suffix_tex..".png", "lzr_laser_emitter"..suffix_tex..".png", "lzr_laser_emitter"..suffix_tex..".png", "lzr_laser_emitter"..suffix_tex..".png", "lzr_laser_emitter"..suffix_tex..".png", "lzr_laser_emitter"..suffix_tex.."_front.png", }, tiles_takable_on = { "lzr_laser_emitter"..suffix_tex.."_on.png", "lzr_laser_emitter"..suffix_tex.."_on.png", "lzr_laser_emitter"..suffix_tex.."_on.png", "lzr_laser_emitter"..suffix_tex.."_on.png", "lzr_laser_emitter"..suffix_tex.."_on.png", "lzr_laser_emitter"..suffix_tex.."_on_front.png", }, tiles_off = { "lzr_laser_emitter"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_front.png^lzr_laser_fixed.png", }, tiles_on = { "lzr_laser_emitter"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_emitter"..suffix_tex.."_on_front.png^lzr_laser_fixed.png", }, light_source_on = 7, _lzr_on_toggle = function(pos, node) if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_COMPLETE then return end local nname local on = false if node.name == "lzr_laser:emitter"..suffix then nname = "lzr_laser:emitter"..suffix.."_on" on = true elseif node.name == "lzr_laser:emitter"..suffix.."_on" then nname = "lzr_laser:emitter"..suffix elseif node.name == "lzr_laser:emitter"..suffix.."_takable" then nname = "lzr_laser:emitter"..suffix.."_takable_on" on = true elseif node.name == "lzr_laser:emitter"..suffix.."_takable_on" then nname = "lzr_laser:emitter"..suffix.."_takable" end if on then minetest.sound_play({name="lzr_laser_emitter_activate", gain=1.0}, {pos=pos}, true) else minetest.sound_play({name="lzr_laser_emitter_activate", gain=0.8}, {pos=pos, pitch=0.7}, true) end minetest.swap_node(pos, {name=nname, param2=node.param2}) full_update() end, groups = { rotatable = 2, laser_block = 1, emitter_color = emitters[e][5] }, sounds = lzr_sounds.node_sound_wood_defaults(), }, { allow_take = true, keep_state_on_take = true, active_in_creative = true, register_color = false, group = "emitter" }) end minetest.register_alias("lzr_laser:emitter", "lzr_laser:emitter_red") minetest.register_alias("lzr_laser:emitter_takable", "lzr_laser:emitter_red_takable") minetest.register_alias("lzr_laser:emitter_on", "lzr_laser:emitter_red_on") minetest.register_alias("lzr_laser:emitter_takable_on", "lzr_laser:emitter_red_takable_on") -- List of detectors local detectors = { -- list order: color name, suffix for item ID, suffix for texture, description, colorcode -- Colorless detector that accepts any color { "", "", "", S("Detector "), nil }, -- Colored detectors that accept only one color { "red", "_red", "_red", S("Red Detector"), lzr_globals.COLOR_RED }, { "green", "_green", "_green", S("Green Detector"), lzr_globals.COLOR_GREEN }, { "blue", "_blue", "_blue", S("Blue Detector"), lzr_globals.COLOR_BLUE }, { "yellow", "_yellow", "_yellow", S("Yellow Detector"), lzr_globals.COLOR_YELLOW }, { "magenta", "_magenta", "_magenta", S("Magenta Detector"), lzr_globals.COLOR_MAGENTA }, { "cyan", "_cyan", "_cyan", S("Cyan Detector"), lzr_globals.COLOR_CYAN }, { "white", "_white", "_white", S("White Detector"), lzr_globals.COLOR_WHITE }, } for d=1, #detectors do local suffix = detectors[d][2] local suffix_tex = detectors[d][3] local desc = detectors[d][4] local colorcode = detectors[d][5] lzr_laser.register_element("lzr_laser:detector"..suffix, { description = desc, paramtype2 = "facedir", tiles_off = { "lzr_laser_detector"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex..".png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_front.png^lzr_laser_fixed.png", }, tiles_on = { "lzr_laser_detector"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_on.png^lzr_laser_fixed.png", "lzr_laser_detector"..suffix_tex.."_on_front.png^lzr_laser_fixed.png", }, tiles_takable_off = { "lzr_laser_detector"..suffix_tex..".png", "lzr_laser_detector"..suffix_tex..".png", "lzr_laser_detector"..suffix_tex..".png", "lzr_laser_detector"..suffix_tex..".png", "lzr_laser_detector"..suffix_tex..".png", "lzr_laser_detector"..suffix_tex.."_front.png", }, tiles_takable_on = { "lzr_laser_detector"..suffix_tex.."_on.png", "lzr_laser_detector"..suffix_tex.."_on.png", "lzr_laser_detector"..suffix_tex.."_on.png", "lzr_laser_detector"..suffix_tex.."_on.png", "lzr_laser_detector"..suffix_tex.."_on.png", "lzr_laser_detector"..suffix_tex.."_on_front.png", }, light_source_on = 5, groups = { rotatable = 2, laser_block = 1, detector_color = colorcode }, sounds = lzr_sounds.node_sound_wood_defaults(), }, { allow_take = true, is_detector = true, register_color = false, group = "detector" }) end -- Hollow barrel lzr_laser.register_element("lzr_laser:hollow_barrel", { description = S("Hollow Barrel"), paramtype = "light", paramtype2 = "facedir", drawtype = "mesh", mesh_off = "lzr_laser_hollow_barrel.obj", mesh_on = "lzr_laser_hollow_barrel_on.obj", tiles_off = { "lzr_laser_hollow_barrel_top.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_top.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_sides.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_insides.png", }, tiles_on = { "lzr_laser_hollow_barrel_top.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_top.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_sides.png^lzr_laser_fixed.png", "lzr_laser_hollow_barrel_insides.png", lzr_laser.LASER_TILE, }, tiles_takable_off = { "lzr_laser_hollow_barrel_top.png", "lzr_laser_hollow_barrel_top.png", "lzr_laser_hollow_barrel_sides.png", "lzr_laser_hollow_barrel_insides.png", }, tiles_takable_on = { "lzr_laser_hollow_barrel_top.png", "lzr_laser_hollow_barrel_top.png", "lzr_laser_hollow_barrel_sides.png", "lzr_laser_hollow_barrel_insides.png", lzr_laser.LASER_TILE, }, use_texture_alpha = lzr_laser.ALPHA_LASER, light_source_on = lzr_globals.LASER_GLOW, groups = { rotatable = 1, laser_block = 1 }, sounds = lzr_sounds.node_sound_wood_defaults(), }, { allow_take = true, group = "hollow_barrel" }) -- Is non-walkable if off, -- and walkable if on. lzr_laser.register_element("lzr_laser:skull_cursed", { description = S("Cursed Skull"), drawtype = "mesh", -- Mesh based on skull nodebox by rudzik8 mesh_off = "lzr_laser_skull.obj", mesh_on = "lzr_laser_skull.obj", collision_box = {type="regular"}, selection_box = {type="regular"}, paramtype = "light", paramtype2 = "facedir", tiles_off = { { name = "(lzr_laser_cskull_top.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_bottom.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_side.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_side.png^lzr_laser_fixed.png^[transformFX)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_back.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_front.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, }, tiles_on = { { name = "lzr_laser_cskull_top.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_cskull_bottom.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_cskull_side.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_cskull_side.png^lzr_laser_fixed.png^[transformFX", backface_culling = true }, { name = "lzr_laser_cskull_back.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_cskull_front.png^lzr_laser_fixed.png^lzr_laser_cskull_on.png", backface_culling = true }, }, tiles_takable_off = { { name = "lzr_laser_cskull_top.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_cskull_bottom.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_cskull_side.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_cskull_side.png^[transformFX)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_cskull_back.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_cskull_front.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, }, tiles_takable_on = { { name = "lzr_laser_cskull_top.png", backface_culling = true }, { name = "lzr_laser_cskull_bottom.png", backface_culling = true }, { name = "lzr_laser_cskull_side.png", backface_culling = true }, { name = "lzr_laser_cskull_side.png^[transformFX", backface_culling = true }, { name = "lzr_laser_cskull_back.png", backface_culling = true }, { name = "lzr_laser_cskull_front.png^lzr_laser_cskull_on.png", backface_culling = true }, }, use_texture_alpha = "blend", light_source_on = 5, groups = { rotatable = 2, skull = 1, laser_block = 1 }, on_rotate = screwdriver.rotate_simple, sounds = lzr_sounds.node_sound_stone_defaults(), }, { allow_take = true, not_walkable_if_off = true, register_color = false, group = "skull_cursed" }) -- Is walkable if off, -- and non-walkable if on. lzr_laser.register_element("lzr_laser:skull_shy", { description = S("Shy Skull"), drawtype = "mesh", -- Mesh based on skull nodebox by rudzik8 mesh_off = "lzr_laser_skull.obj", mesh_on = "lzr_laser_skull.obj", collision_box = {type="regular"}, selection_box = {type="regular"}, paramtype = "light", paramtype2 = "facedir", tiles_off = { { name = "lzr_laser_sskull_top.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_sskull_bottom.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_sskull_side.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_sskull_side.png^lzr_laser_fixed.png^[transformFX", backface_culling = true }, { name = "lzr_laser_sskull_back.png^lzr_laser_fixed.png", backface_culling = true }, { name = "lzr_laser_sskull_front.png^lzr_laser_fixed.png", backface_culling = true }, }, tiles_on = { { name = "(lzr_laser_sskull_top.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_bottom.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_side.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_side.png^lzr_laser_fixed.png^[transformFX)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_back.png^lzr_laser_fixed.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_front.png^lzr_laser_fixed.png^lzr_laser_sskull_on.png)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, }, tiles_takable_off = { { name = "lzr_laser_sskull_top.png", backface_culling = true }, { name = "lzr_laser_sskull_bottom.png", backface_culling = true }, { name = "lzr_laser_sskull_side.png", backface_culling = true }, { name = "lzr_laser_sskull_side.png^[transformFX", backface_culling = true }, { name = "lzr_laser_sskull_back.png", backface_culling = true }, { name = "lzr_laser_sskull_front.png", backface_culling = true }, }, tiles_takable_on = { { name = "lzr_laser_sskull_top.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_sskull_bottom.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_sskull_side.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "(lzr_laser_sskull_side.png^[transformFX)^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_sskull_back.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, { name = "lzr_laser_sskull_front.png^lzr_laser_sskull_on.png^[opacity:"..OPACITY_SKULL_GONE, backface_culling = true }, }, use_texture_alpha = "blend", light_source_on = 5, groups = { rotatable = 2, skull = 2, laser_block = 1 }, on_rotate = screwdriver.rotate_simple, sounds = lzr_sounds.node_sound_stone_defaults(), }, { allow_take = true, not_walkable_if_on = true, register_color = false, group = "skull_shy" }) minetest.register_node("lzr_laser:barricade", { description = S("Barricade"), drawtype = "mesh", mesh = "lzr_laser_burning.obj", paramtype = "light", inventory_image = "xdecor_baricade.png", wield_image = "xdecor_baricade.png", tiles = {"blank.png","xdecor_baricade.png"}, use_texture_alpha = "clip", groups = { breakable = 1, flammable = 1, laser_destroys = 2 }, sounds = lzr_sounds.node_sound_wood_defaults({ dug = {name="lzr_laser_barricade_break", gain=1.0}, }), on_place = element_on_place, _lzr_active = "lzr_laser:barricade_on", }) minetest.register_node("lzr_laser:barricade_on", { description = S("Burning Barricade"), drawtype = "mesh", paramtype = "light", light_source = 12, mesh = "lzr_laser_burning.obj", inventory_image = "lzr_laser_baricade_burning.png", wield_image = "lzr_laser_baricade_burning.png", use_texture_alpha = "clip", tiles = { { name="fire_basic_flame_animated.png", animation={ type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 1.0, },}, { name="lzr_laser_baricade_burning_animated.png", animation={ type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 1.0, },}, }, groups = { breakable = 1, not_in_creative_inventory = 1 }, sounds = lzr_sounds.node_sound_wood_defaults({ dug = {name="lzr_laser_barricade_break", gain=1.0}, }), on_construct = function(pos) minetest.sound_play({name="lzr_laser_quickburn", gain=1.0}, {pos=pos}, true) local timer = minetest.get_node_timer(pos) timer:start(1) end, on_timer = function(pos) -- spawn a few particles for the removed node minetest.add_particlespawner({ amount = 16, time = 0.001, minpos = vector.subtract(pos, vector.new(0.5, 0.5, 0.5)), maxpos = vector.add(pos, vector.new(0.5, 0.5, 0.5)), minvel = vector.new(-0.5, -0.2, -0.5), maxvel = vector.new(0.5, 0.2, 0.5), minacc = vector.new(0, -lzr_globals.GRAVITY, 0), maxacc = vector.new(0, -lzr_globals.GRAVITY, 0), minsize = 1.5, maxsize = 1.5, node = {name="lzr_laser:barricade"}, }) -- Play randomly-pitched break sound local pitch = 1.0+math.random(-100, 100)*0.001 -- 0.9..1.1 minetest.sound_play({name="lzr_laser_barricade_break", gain=1.0}, {gain=1.0, pitch=pitch, pos=pos}, true) -- remove node minetest.remove_node(pos) full_update() -- propagate to neighbors local posses = { vector.new(-1,0,0), vector.new(1,0,0), vector.new(0,-1,0), vector.new(0,1,0), vector.new(0,0,-1), vector.new(0,0,1), } for p=1, #posses do local ppos = vector.add(pos, posses[p]) local node = minetest.get_node(ppos) local def = minetest.registered_nodes[node.name] if def and def._lzr_active then if minetest.get_item_group(node.name, "flammable") > 0 then minetest.set_node(ppos, {name=def._lzr_active}) end end end end, drop = "", on_place = element_on_place, _lzr_inactive = "lzr_laser:barricade", }) -- Aliases for the pre-laser color era minetest.register_alias("lzr_laser:mirror_takable_on", "lzr_laser:mirror_takable_on_1") minetest.register_alias("lzr_laser:mirror_on", "lzr_laser:mirror_on_1") minetest.register_alias("lzr_laser:hollow_barrel_on", "lzr_laser:hollow_barrel_on_1") minetest.register_alias("lzr_laser:hollow_barrel_takable_on", "lzr_laser:hollow_barrel_takable_on_1")