429 lines
13 KiB
Lua
429 lines
13 KiB
Lua
-- Distance from center to side of the laser beam square. Not really the radius, but it sounds cooler. :P
|
|
local LASER_RADIUS = -1/16
|
|
|
|
local COLSTRING_IDS = {
|
|
[lzr_globals.COLOR_NONE] = "_", -- nothing
|
|
[lzr_globals.COLOR_RED] = "R", -- red
|
|
[lzr_globals.COLOR_GREEN] = "G", -- green
|
|
[lzr_globals.COLOR_YELLOW] = "Y", -- yellow (R+G)
|
|
[lzr_globals.COLOR_BLUE] = "B", -- blue
|
|
[lzr_globals.COLOR_MAGENTA] = "M", -- magenta (R+B)
|
|
[lzr_globals.COLOR_CYAN] = "C", -- cyan (G+B)
|
|
[lzr_globals.COLOR_WHITE] = "W", -- white (R+G+B)
|
|
}
|
|
|
|
-- The maximum possible colorcode
|
|
local MAX_COLORCODE = 7
|
|
|
|
-- DATA FORMATS:
|
|
--[[ colorcode:
|
|
A number representing a laser color.
|
|
It ranges from 0 to 7.
|
|
Value 0 stands for 'no color', the absense of a laser.
|
|
There are 3 primary colors:
|
|
* 1 = red
|
|
* 2 = green
|
|
* 4 = blue
|
|
The other values are reserved for secondary colors that
|
|
result from adding the values from 2 primary colors
|
|
that have been additive color mixing.
|
|
See lzr_globals for the full list of colorcodes.
|
|
]]
|
|
|
|
--[[ dirstring: A representation of 3 axes (XYZ) which each have
|
|
a colorcode converted to a string.
|
|
The 3 characters stand for these directions, in order: X, Y, Z
|
|
Example: "140" = red on X axis, blue on Y axis and no laser in the Z axis
|
|
]]
|
|
|
|
--[[ dirs table: Like dirstring, but in table format. It's a table
|
|
with 3 numbers, each of them is a colorcode.
|
|
The same order of axes is used like for dirstring.
|
|
Example: { 1, 4, 0 } -- X = red, Y = blue, Z = no laser
|
|
]]
|
|
|
|
-- Converts a dirstring to a dirs table
|
|
function lzr_laser.dirstring_to_dirs(dirstring)
|
|
local dirs = {}
|
|
for d=1, 3 do
|
|
local dirnum = tonumber(string.sub(dirstring, d, d))
|
|
if not dirnum then
|
|
error("[lzr_laser] dirstring_to_dirs: Invalid dirstring! Character #"..d.." is not a digit.")
|
|
elseif dirnum >= 0 and dirnum <= MAX_COLORCODE then
|
|
dirs[d] = dirnum
|
|
else
|
|
error("[lzr_laser] dirstring_to_dirs: Invalid dirstring! Character #"..d.." is outside the valid digit value range (0-"..MAX_COLORCODE..").")
|
|
end
|
|
end
|
|
return dirs
|
|
end
|
|
|
|
-- Converts a dirstring into a "colstring", a string
|
|
-- of letters each representing a color
|
|
function lzr_laser.dirstring_to_colstring(dirstring)
|
|
local dirs = {}
|
|
local colstr = ""
|
|
for d=1, string.len(dirstring) do
|
|
local dirnum = tonumber(string.sub(dirstring, d, d))
|
|
if not dirnum then
|
|
error("[lzr_laser] dirstring_to_dirs: Invalid dirstring! Character #"..d.." is not a digit.")
|
|
elseif dirnum >= 0 or dirnum <= MAX_COLORCODE then
|
|
colstr = colstr .. COLSTRING_IDS[dirnum]
|
|
else
|
|
error("[lzr_laser] dirstring_to_dirs: Invalid dirstring! Character #"..d.." is outside the valid digit value range (0-7).")
|
|
end
|
|
end
|
|
return colstr
|
|
end
|
|
|
|
|
|
|
|
-- Converts a dirs table to a dirstring
|
|
function lzr_laser.dirs_to_dirstring(dirs)
|
|
local dirstring = ""
|
|
for d=1, 3 do
|
|
if dirs[d] >= 0 or dirs[d] <= MAX_COLORCODE then
|
|
dirstring = dirstring .. tostring(dirs[d])
|
|
else
|
|
error("[lzr_laser] dirs_to_dirstring: Invalid dirs!")
|
|
end
|
|
end
|
|
return dirstring
|
|
end
|
|
|
|
function lzr_laser.vector_to_dirs(dir_vector)
|
|
local dirs = {0,0,0}
|
|
if dir_vector.x ~= 0 then
|
|
dirs[1] = 1
|
|
end
|
|
if dir_vector.y ~= 0 then
|
|
dirs[2] = 1
|
|
end
|
|
if dir_vector.z ~= 0 then
|
|
dirs[3] = 1
|
|
end
|
|
return dirs
|
|
end
|
|
|
|
function lzr_laser.vector_and_color_to_dirs(dir_vector, color)
|
|
local dirs = {0,0,0}
|
|
if dir_vector.x ~= 0 then
|
|
dirs[1] = color
|
|
end
|
|
if dir_vector.y ~= 0 then
|
|
dirs[2] = color
|
|
end
|
|
if dir_vector.z ~= 0 then
|
|
dirs[3] = color
|
|
end
|
|
return dirs
|
|
end
|
|
|
|
-- Convert number to string in binary form
|
|
-- * num: Number
|
|
-- * minLength: Minimum number of characters the string must have, will
|
|
-- fill string with leading zeroes if needed (default: 1)
|
|
-- Returns: string
|
|
function lzr_laser.dec2bin(num, minLength)
|
|
if not minLength then
|
|
minLength = 1
|
|
end
|
|
local t = {}
|
|
local rem
|
|
while num > 0 do
|
|
rem = math.fmod(num,2)
|
|
t[#t+1] = rem
|
|
num = (num-rem)/2
|
|
end
|
|
local bin = table.concat(t)
|
|
bin = string.reverse(bin)
|
|
local length = string.len(bin)
|
|
if length < minLength then
|
|
bin = string.rep("0", minLength - length) .. bin
|
|
end
|
|
return bin
|
|
end
|
|
|
|
-- Takes 2 binary numbers (as strings!)
|
|
-- and returns the 'bitwise or' of them (also as string).
|
|
-- Both strings MUST be of equal length.
|
|
function lzr_laser.bitwise_or(bin1, bin2)
|
|
local len = string.len(bin1)
|
|
local out = ""
|
|
for i=1, len do
|
|
if string.sub(bin1, i, i) == "1" or string.sub(bin2, i, i) == "1" then
|
|
out = out .. "1"
|
|
else
|
|
out = out .. "0"
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- Lookup table for dirstring_or
|
|
local colorcode_bor_lookup = {}
|
|
for i=0, MAX_COLORCODE do
|
|
colorcode_bor_lookup[tostring(i)] = {}
|
|
for j=0, MAX_COLORCODE do
|
|
colorcode_bor_lookup[tostring(i)][tostring(j)] = tostring(bit.bor(i, j))
|
|
end
|
|
end
|
|
|
|
-- Takes two dirstrings (with digits from 0 to 7)
|
|
-- and does a bitwise or on each digit on them
|
|
-- and returns the result.
|
|
-- Example: "001" and "003" give "003"
|
|
function lzr_laser.dirstring_or(dirstring1, dirstring2)
|
|
local len = string.len(dirstring1)
|
|
local out = ""
|
|
for i=1, len do
|
|
local d1 = string.sub(dirstring1, i, i)
|
|
local d2 = string.sub(dirstring2, i, i)
|
|
-- We use a lookup table for improved performance
|
|
-- since this function is called for every
|
|
-- laser step in the laser propagation
|
|
-- algorithm.
|
|
local res = colorcode_bor_lookup[d1][d2]
|
|
out = out .. tostring(res)
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- Generates a line of particles between two positions
|
|
-- * pos1, pos2: Start and end position
|
|
-- * texture: Particle texture file name
|
|
-- * amount: (optional) number of particles per step
|
|
-- * spread: (optional) how far to spread out particles from the perfect line
|
|
-- * vspread: (optional) maximum random velocity of particles
|
|
-- * size: (optional) particle size multiplier
|
|
-- * steps: (optional) how many iterations to use (more = more detailed but slower)
|
|
function lzr_laser.particle_line(pos1, pos2, texture, amount, spread, vspread, size, steps)
|
|
if not amount then amount = 10 end
|
|
if not spread then spread = 0.1 end
|
|
if not vspread then vspread = 0.01 end
|
|
if not size then size = 0.4 end
|
|
if not steps then steps = 30 end
|
|
|
|
local pos = vector.copy(pos1)
|
|
for i=0,steps-1 do
|
|
pos.x = pos1.x + (pos2.x - pos1.x) * (i/steps)
|
|
pos.y = pos1.y + (pos2.y - pos1.y) * (i/steps)
|
|
pos.z = pos1.z + (pos2.z - pos1.z) * (i/steps)
|
|
minetest.add_particlespawner({
|
|
amount = amount,
|
|
time = 0.001,
|
|
minpos = vector.subtract(pos, vector.new(spread, spread, spread)),
|
|
maxpos = vector.add(pos, vector.new(spread, spread, spread)),
|
|
minvel = vector.new(-vspread, -vspread, -vspread),
|
|
maxvel = vector.new(vspread, vspread, vspread),
|
|
minsize = size,
|
|
maxsize = size,
|
|
texture = texture,
|
|
minexptime = 1.5,
|
|
maxexptime = 1.7,
|
|
})
|
|
end
|
|
end
|
|
|
|
local dirstring_lookup = {}
|
|
function lzr_laser.laser_group_to_dirstring(laser_group)
|
|
return dirstring_lookup[laser_group]
|
|
end
|
|
function lzr_laser.colors_to_laser_group(color_x, color_y, color_z)
|
|
return color_x + color_y * 8 + color_z * 64
|
|
end
|
|
|
|
-- Create a lookup table for laser_group_to_distring for performance
|
|
for x=0,MAX_COLORCODE do
|
|
for y=0,MAX_COLORCODE do
|
|
for z=0,MAX_COLORCODE do
|
|
local val = lzr_laser.colors_to_laser_group(x,y,z)
|
|
dirstring_lookup[val] = x .. y .. z
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Returns true if the given laser block (by nodename)
|
|
-- is active.
|
|
function lzr_laser.is_laser_block_active(nodename)
|
|
local def = minetest.registered_nodes[nodename]
|
|
if not def then
|
|
return false
|
|
end
|
|
local element_group = def._lzr_element_group
|
|
if not element_group then
|
|
return false
|
|
end
|
|
if minetest.get_item_group(nodename, element_group) == 2 then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Convert a 'pos string' to a list of positions.
|
|
-- A 'pos string' is a string containing of positions,
|
|
-- each in the `minetest.pos_to_string` format, and each
|
|
-- position is separated by a semicolon.
|
|
lzr_laser.pos_string_to_positions = function(pos_str)
|
|
local strs = string.split(pos_str, ";")
|
|
local posses = {}
|
|
if strs then
|
|
for s=1, #strs do
|
|
local stpos = minetest.string_to_pos(strs[s])
|
|
if not stpos then
|
|
minetest.log("error", "[lzr_laser] Invalid pos_string syntax of node at "..minetest.pos_to_string(stpos)..": "..tostring(pos_str))
|
|
return {}
|
|
end
|
|
table.insert(posses, stpos)
|
|
end
|
|
end
|
|
return posses
|
|
end
|
|
|
|
-- Convert a list of positions to a 'pos string'.
|
|
-- See `lzr_laser.pos_string_to_positions` for a definition
|
|
-- of 'pos string'.
|
|
lzr_laser.positions_to_pos_string = function(positions)
|
|
local to_str = ""
|
|
for p=1, #positions do
|
|
to_str = to_str .. minetest.pos_to_string(positions[p])
|
|
if p < #positions then
|
|
to_str = to_str .. ";"
|
|
end
|
|
end
|
|
return to_str
|
|
end
|
|
|
|
|
|
local temp_waypoints = {}
|
|
local add_temp_waypoint = function(player, texture, wpos, size, z_index_add)
|
|
size = size or 1
|
|
local id = player:hud_add({
|
|
type = "image_waypoint",
|
|
text = texture,
|
|
offset = { x = 0, y = 0 },
|
|
scale = { x = 8 * size, y = 8 * size },
|
|
z_index = -290 + (z_index_add or 0),
|
|
world_pos = wpos,
|
|
})
|
|
if not id then
|
|
return
|
|
end
|
|
local job = minetest.after(1.7, function(player)
|
|
if player and player:is_player() then
|
|
for i=1, #temp_waypoints do
|
|
if temp_waypoints[i].id == id then
|
|
table.remove(temp_waypoints, i)
|
|
break
|
|
end
|
|
end
|
|
player:hud_remove(id)
|
|
end
|
|
end, player)
|
|
table.insert(temp_waypoints, {id=id, job=job})
|
|
end
|
|
local remove_temp_waypoints = function(player)
|
|
for t=1, #temp_waypoints do
|
|
player:hud_remove(temp_waypoints[t].id)
|
|
temp_waypoints[t].job:cancel()
|
|
end
|
|
temp_waypoints = {}
|
|
end
|
|
|
|
-- Show information in the HUD of the trigger
|
|
-- with the given trigger ID. This shows waypoints
|
|
-- of senders/receivers associated to the trigger
|
|
-- particle paths towards those associated triggers
|
|
-- and other icons.
|
|
-- `trigger_id`: Trigger ID
|
|
-- `player`: Player doing the trigger
|
|
-- `show_trigger_type`: If `true`, also show icons for the sender/receiver type (default: false)
|
|
lzr_laser.show_trigger_info = function(trigger_id, player, show_trigger_type)
|
|
remove_temp_waypoints(player)
|
|
local trigger = lzr_triggers.get_trigger(trigger_id)
|
|
local tpos
|
|
if type(trigger.location) == "table" then
|
|
tpos = trigger.location
|
|
elseif trigger.location == "player" then
|
|
tpos = player:get_pos()
|
|
tpos = vector.offset(0, 1, 0)
|
|
else
|
|
return
|
|
end
|
|
|
|
local tpos = trigger.location
|
|
local receivers = lzr_triggers.get_receivers(trigger_id)
|
|
local senders = lzr_triggers.get_senders(trigger_id)
|
|
if #receivers > 0 and #senders > 0 then
|
|
add_temp_waypoint(player, "lzr_triggers_icon_sender_receiver.png", tpos)
|
|
end
|
|
if #receivers > 0 then
|
|
if #senders == 0 then
|
|
add_temp_waypoint(player, "lzr_triggers_icon_sender.png", tpos)
|
|
end
|
|
for r=1, #receivers do
|
|
local rtrig = lzr_triggers.get_trigger(receivers[r])
|
|
if not rtrig then
|
|
local rpos = minetest.string_to_pos(receivers[r])
|
|
if rpos then
|
|
add_temp_waypoint(player, "lzr_laser_particle_unknown.png^[opacity:127", rpos)
|
|
end
|
|
minetest.log("error", "[lzr_laser] Receiver '"..receivers[r].." for trigger '"..trigger_id.."' does not exist!")
|
|
else
|
|
local rloc = rtrig.location
|
|
if rloc == "player" then
|
|
rloc = player:get_pos()
|
|
rloc = vector.offset(rloc, 0, 1, 0)
|
|
end
|
|
add_temp_waypoint(player, "lzr_triggers_icon_receiver.png^[opacity:127", rloc)
|
|
if show_trigger_type then
|
|
-- Small icon for receiver type
|
|
local rloc_receiver_type = vector.offset(rloc, 0.125, 0.125, 0.125)
|
|
add_temp_waypoint(player, lzr_triggers.RECEIVER_TYPE_ICONS[rtrig.receiver_type].."^[opacity:127", rloc_receiver_type, 0.4, 1)
|
|
end
|
|
lzr_laser.particle_line(tpos, rloc, "lzr_laser_particle_signal_marker.png", 1, 0, 0)
|
|
end
|
|
end
|
|
if show_trigger_type then
|
|
-- Small icon for signal type
|
|
local tpos_signal_type = vector.offset(tpos, -0.125, -0.125, -0.125)
|
|
add_temp_waypoint(player, lzr_triggers.SIGNAL_TYPE_ICONS[trigger.signal_type], tpos_signal_type, 0.4, 1)
|
|
end
|
|
end
|
|
if #senders > 0 then
|
|
if #receivers == 0 then
|
|
add_temp_waypoint(player, "lzr_triggers_icon_receiver.png", tpos)
|
|
end
|
|
for s=1, #senders do
|
|
local strig = lzr_triggers.get_trigger(senders[s])
|
|
if not strig then
|
|
local spos = minetest.string_to_pos(senders[s])
|
|
if spos then
|
|
add_temp_waypoint(player, "lzr_laser_particle_unknown.png^[opacity:127", spos)
|
|
end
|
|
minetest.log("error", "[lzr_laser] Sender '"..senders[s].." for trigger '"..trigger_id.."' does not exist!")
|
|
else
|
|
local sloc = strig.location
|
|
if sloc == "player" then
|
|
sloc = player:get_pos()
|
|
sloc = vector.offset(sloc, 0, 1, 0)
|
|
end
|
|
add_temp_waypoint(player, "lzr_triggers_icon_sender.png^[opacity:127", sloc)
|
|
if show_trigger_type then
|
|
-- Small icon for signal type
|
|
local sloc_signal_type = vector.offset(sloc, -0.125, -0.125, -0.125)
|
|
add_temp_waypoint(player, lzr_triggers.SIGNAL_TYPE_ICONS[strig.signal_type].."^[opacity:127", sloc_signal_type, 0.4, 1)
|
|
end
|
|
lzr_laser.particle_line(tpos, sloc, "lzr_laser_particle_signal_marker.png", 1, 0, 0)
|
|
end
|
|
end
|
|
if show_trigger_type then
|
|
-- Small icon for receiver type
|
|
local tpos_receiver_type = vector.offset(tpos, 0.125, 0.125, 0.125)
|
|
add_temp_waypoint(player, lzr_triggers.RECEIVER_TYPE_ICONS[trigger.receiver_type], tpos_receiver_type, 0.4, 1)
|
|
end
|
|
end
|
|
end
|
|
|