lazarr/mods/lzr_laser/physics.lua

216 lines
6.8 KiB
Lua
Raw Normal View History

2021-12-21 08:45:26 -08:00
-- Max. number of steps in the laser travel algorithm
local MAX_LASER_ITERATIONS = 10000
2021-12-21 08:45:26 -08:00
-- Add a laser node to pos with the direction `dir`.
-- Dir is a direction vector, and only one direction must be set
function lzr_laser.add_laser(pos, dir)
2021-12-27 19:31:46 -08:00
-- Check if laser is still within the playfield
if pos.x < lzr_globals.PLAYFIELD_START.x or pos.x > lzr_globals.PLAYFIELD_END.x or
pos.y < lzr_globals.PLAYFIELD_START.y or pos.y > lzr_globals.PLAYFIELD_END.y or
pos.z < lzr_globals.PLAYFIELD_START.z or pos.z > lzr_globals.PLAYFIELD_END.z then
return false
end
2021-12-21 08:45:26 -08:00
local node = minetest.get_node(pos)
-- Laser through air
if node.name == "air" then
local dirs = lzr_laser.vector_to_dirs(dir)
local dirstring = lzr_laser.dirs_to_dirstring(dirs)
minetest.set_node(pos, {name="lzr_laser:laser_"..dirstring})
2021-12-21 13:00:51 -08:00
pos = vector.add(pos, dir)
2021-12-27 07:36:31 -08:00
return {{pos, dir}}
2021-12-21 08:45:26 -08:00
-- Laser through laser (laser intersection)
elseif minetest.get_item_group(node.name, "laser") > 0 then
local dirnum = minetest.get_item_group(node.name, "laser")
local dirstring_old = lzr_laser.dec2bin(dirnum, 3)
2021-12-21 08:45:26 -08:00
local dirs_new = lzr_laser.vector_to_dirs(dir)
local dirstring_new = lzr_laser.dirs_to_dirstring(dirs_new)
local place_dirstring = lzr_laser.bitwise_or(dirstring_old, dirstring_new)
minetest.set_node(pos, {name="lzr_laser:laser_"..place_dirstring})
2021-12-21 13:00:51 -08:00
pos = vector.add(pos, dir)
2021-12-27 07:36:31 -08:00
return {{pos, dir}}
2021-12-21 13:00:51 -08:00
-- Mirror laser
elseif minetest.get_item_group(node.name, "mirror") > 0 then
local mirror_dir = lzr_laser.get_mirrored_laser_dir(pos, dir)
if mirror_dir then
2021-12-27 19:11:20 -08:00
local def = minetest.registered_nodes[node.name]
local active = def._lzr_active
2021-12-29 06:24:19 -08:00
if not active then
return false
end
2021-12-21 13:00:51 -08:00
-- Activate mirror node
2021-12-27 19:11:20 -08:00
minetest.set_node(pos, {name=def._lzr_active, param2 = node.param2})
2021-12-21 13:00:51 -08:00
-- Set new pos and dir
pos = vector.add(pos, mirror_dir)
dir = mirror_dir
2021-12-27 07:36:31 -08:00
return {{pos, dir}}
else
return false
end
-- Mirror and split laser
elseif minetest.get_item_group(node.name, "transmissive_mirror") > 0 then
local mirror_dir = lzr_laser.get_mirrored_laser_dir(pos, dir)
if mirror_dir then
2021-12-27 19:11:20 -08:00
local def = minetest.registered_nodes[node.name]
local active = def._lzr_active
2021-12-29 06:24:19 -08:00
if not active then
return false
end
2021-12-27 07:36:31 -08:00
-- Activate mirror node
2021-12-27 19:11:20 -08:00
minetest.set_node(pos, {name=active, param2 = node.param2})
2021-12-27 07:36:31 -08:00
-- Set new pos and dir
local pos_straight = vector.add(pos, dir)
local dir_straight = dir
local pos_mirrored = vector.add(pos, mirror_dir)
local dir_mirrored = mirror_dir
return {{pos_straight, dir_straight}, {pos_mirrored, dir_mirrored}}
2021-12-21 13:00:51 -08:00
else
return false
end
2021-12-29 06:24:19 -08:00
-- Spread laser to all directions
elseif minetest.get_item_group(node.name, "crystal") > 0 then
local def = minetest.registered_nodes[node.name]
local active = def._lzr_active
if not active then
return false
end
-- Activate node
minetest.set_node(pos, {name=active, param2 = node.param2})
-- Set dirs
local dirs = {
{ x = 0, y = -1, z = 0 },
{ x = 0, y = 1, z = 0 },
{ x = -1, y = 0, z = 0 },
{ x = 1, y = 0, z = 0 },
{ x = 0, y = 0, z = -1 },
{ x = 0, y = 0, z = 1 },
}
for d=1, #dirs do
if vector.equals(dirs[d], dirs) then
table.remove(dirs, d)
break
end
end
local output = {}
for d=1, #dirs do
table.insert(output, { vector.add(pos, dirs[d]), dirs[d] })
end
return output
2021-12-21 14:13:14 -08:00
-- Detector
elseif minetest.get_item_group(node.name, "detector") > 0 then
local detected = lzr_laser.check_detector(pos, dir)
if detected then
2021-12-27 19:11:20 -08:00
local def = minetest.registered_nodes[node.name]
local active = def._lzr_active
2021-12-21 14:13:14 -08:00
-- Activate node
2021-12-27 19:11:20 -08:00
minetest.set_node(pos, {name=active, param2 = node.param2})
2021-12-21 15:35:16 -08:00
local done = lzr_laser.check_detectors_in_area(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
if done then
lzr_levels.next_level()
end
2021-12-21 14:13:14 -08:00
end
2021-12-21 14:18:11 -08:00
-- Laser ends here
return false
2021-12-21 08:45:26 -08:00
-- Anything else: fail
else
return false
end
end
function lzr_laser.emit_laser(pos)
local node = minetest.get_node(pos)
2021-12-21 11:23:15 -08:00
if minetest.get_item_group(node.name, "emitter") == 0 then
minetest.log("error", "[lzr_laser] lzr_laser.emit_laser was called at invalid pos!")
2021-12-21 08:45:26 -08:00
return false
end
local dir = minetest.facedir_to_dir(node.param2)
2021-12-21 11:23:15 -08:00
dir = vector.multiply(dir, -1)
2021-12-21 08:45:26 -08:00
local i_pos = vector.add(pos, dir)
2021-12-21 11:23:15 -08:00
lzr_laser.travel_laser(i_pos, dir)
2021-12-21 08:45:26 -08:00
end
function lzr_laser.travel_laser(pos, dir)
2021-12-21 11:23:15 -08:00
local i_pos = table.copy(pos)
2021-12-21 08:45:26 -08:00
local cond = true
2021-12-27 07:36:31 -08:00
local next_lasers = {{i_pos, dir}}
2021-12-21 08:45:26 -08:00
local i = 0
while cond do
i = i + 1
-- Halt execution for very long loops to prevent freezing the game
if i > MAX_LASER_ITERATIONS then
minetest.log("error", "[lzr_laser] lzr_laser.travel_laser aborted (too many iterations!)")
break
end
2021-12-27 07:36:31 -08:00
local next_laser = next_lasers[1]
local add_laser_result = lzr_laser.add_laser(next_laser[1], next_laser[2])
table.remove(next_lasers, 1)
if add_laser_result ~= false then
for a=1, #add_laser_result do
table.insert(next_lasers, add_laser_result[a])
end
end
if #next_lasers == 0 then
break
2021-12-21 13:00:51 -08:00
end
2021-12-21 08:45:26 -08:00
end
end
-- Remove all lasers in area and disable all laser blocks
2021-12-21 13:32:35 -08:00
function lzr_laser.clear_lasers_in_area(pos1, pos2, ignore_emitters)
2021-12-21 13:00:51 -08:00
-- Remove lasers
2021-12-21 08:45:26 -08:00
local lasers = minetest.find_nodes_in_area(pos1, pos2, {"group:laser"})
minetest.bulk_set_node(lasers, {name="air"})
2021-12-21 13:00:51 -08:00
-- Disable laser blocks (mirror, etc.)
2021-12-21 08:45:26 -08:00
local laser_blocks = minetest.find_nodes_in_area(pos1, pos2, {"group:laser_block"})
for b=1, #laser_blocks do
2021-12-21 13:00:51 -08:00
local block_pos = laser_blocks[b]
local block = minetest.get_node(block_pos)
2021-12-21 08:45:26 -08:00
local def = minetest.registered_nodes[block.name]
2021-12-21 13:32:35 -08:00
local is_ignored_emitter = false
if ignore_emitters ~= true then
is_ignored_emitter = minetest.get_item_group(block.name, "emitter") > 0
end
if def and not is_ignored_emitter then
2021-12-21 08:45:26 -08:00
local inactive = def._lzr_inactive
if inactive then
2021-12-21 13:00:51 -08:00
minetest.set_node(block_pos, {name=inactive, param2=block.param2})
2021-12-21 08:45:26 -08:00
end
end
end
end
2021-12-21 13:32:35 -08:00
-- Emit lasers from all active emitters
function lzr_laser.emit_lasers_in_area(pos1, pos2)
local emitters = minetest.find_nodes_in_area(pos1, pos2, {"group:emitter"})
for e=1, #emitters do
local epos = emitters[e]
local emitter = minetest.get_node(epos)
local is_active = minetest.get_item_group(emitter.name, "emitter") == 2
if is_active then
lzr_laser.emit_laser(emitters[e])
end
end
end
2021-12-21 13:36:28 -08:00
2021-12-21 14:18:11 -08:00
-- Return true if all detectors in area are on (including if no detector)
function lzr_laser.check_detectors_in_area(pos1, pos2)
local detectors = minetest.find_nodes_in_area(pos1, pos2, {"group:detector"})
for d=1, #detectors do
local dpos = detectors[d]
local detector = minetest.get_node(dpos)
local is_active = minetest.get_item_group(detector.name, "detector") == 2
if not is_active then
return false
end
end
return true
end
2021-12-21 13:36:28 -08:00
-- Completely recalculate all lasers
function lzr_laser.full_laser_update(pos1, pos2)
lzr_laser.clear_lasers_in_area(pos1, pos2)
lzr_laser.emit_lasers_in_area(pos1, pos2)
end