310 lines
8.9 KiB
Lua
310 lines
8.9 KiB
Lua
-- LUALOCALS < ---------------------------------------------------------
|
|
local math, minetest, nodecore, pairs, rawset, string, table, tonumber,
|
|
type, unpack, vector
|
|
= math, minetest, nodecore, pairs, rawset, string, table, tonumber,
|
|
type, unpack, vector
|
|
local math_abs, math_random, string_format, table_remove, table_sort
|
|
= math.abs, math.random, string.format, table.remove, table.sort
|
|
-- LUALOCALS > ---------------------------------------------------------
|
|
|
|
local modname = minetest.get_current_modname()
|
|
|
|
local glyph = "nc_writing:glyph1"
|
|
local maxdist = tonumber(minetest.settings:get(modname .. "_maxdist")) or 32
|
|
local minpower = tonumber(minetest.settings:get(modname .. "_minpower")) or 3
|
|
|
|
local function sparkly(posa, posb)
|
|
local minpos = {
|
|
x = (posa.x < posb.x and posa.x or posb.x) - 0.5,
|
|
y = (posa.y < posb.y and posa.y or posb.y) - 0.5,
|
|
z = (posa.z < posb.z and posa.z or posb.z) - 0.5
|
|
}
|
|
local maxpos = {
|
|
x = (posa.x > posb.x and posa.x or posb.x) + 0.5,
|
|
y = (posa.y > posb.y and posa.y or posb.y) + 1.5,
|
|
z = (posa.z > posb.z and posa.z or posb.z) + 0.5
|
|
}
|
|
local volume = (maxpos.x - minpos.x + 1) * (maxpos.y - minpos.y + 1)
|
|
* (maxpos.z - minpos.z + 1)
|
|
minetest.add_particlespawner({
|
|
amount = 5 * volume,
|
|
time = 0.25,
|
|
minpos = minpos,
|
|
maxpos = maxpos,
|
|
minvel = {x = -0.5, y = -0.5, z = -0.5},
|
|
maxvel = {x = 0.5, y = 0.5, z = 0.5},
|
|
texture = "nc_lux_base.png^[mask:nc_lux_dot_mask.png^[opacity:128",
|
|
minexptime = 0.20,
|
|
maxexptime = 0.25,
|
|
glow = 4
|
|
})
|
|
end
|
|
|
|
local function zipscan(pos, dir)
|
|
for i = 1, maxdist - 1 do
|
|
local p = {
|
|
x = pos.x + dir.x * i,
|
|
y = pos.y + dir.y * i,
|
|
z = pos.z + dir.z * i
|
|
}
|
|
if nodecore.walkable(p) then return end
|
|
if dir.y == 0 and nodecore.walkable({x = p.x, y = p.y + 1, z = p.z}) then return end
|
|
local node = minetest.get_node(p)
|
|
local face = nodecore.facedirs[node.param2]
|
|
if node.name == glyph and vector.equals(face.k, dir) then
|
|
if dir.y == 1 then dir = vector.add(dir, face.b) end
|
|
return vector.add(p, dir)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function zipcheck(pos)
|
|
local node = minetest.get_node(pos)
|
|
|
|
local face = nodecore.facedirs[node.param2]
|
|
local dir = face.k
|
|
local hit = zipscan(pos, dir)
|
|
if not hit then return end
|
|
|
|
if nodecore.walkable(hit) or
|
|
nodecore.walkable({x = hit.x, y = hit.y + 1, z = hit.z})
|
|
then return end
|
|
|
|
return hit
|
|
end
|
|
|
|
local cache = {}
|
|
local function zipdata_get(player)
|
|
local pname = player:get_player_name()
|
|
local found = cache[pname]
|
|
if found then return found end
|
|
local s = player:get_meta():get_string(modname) or ""
|
|
found = s and s ~= "" and minetest.deserialize(s) or {}
|
|
cache[pname] = found
|
|
return found
|
|
end
|
|
local function zipdata_set(player, data)
|
|
local pname = player:get_player_name()
|
|
cache[pname] = data
|
|
player:get_meta():set_string(modname, data and minetest.serialize(data) or "")
|
|
end
|
|
|
|
local function appendpos(oldpos, pos)
|
|
for _, p in pairs(oldpos) do
|
|
if vector.equals(pos, p) then return oldpos end
|
|
end
|
|
oldpos[#oldpos + 1] = pos
|
|
if #oldpos > 100 then table_remove(oldpos, 1) end
|
|
return oldpos
|
|
end
|
|
|
|
local enercache = {}
|
|
local function energized(pos)
|
|
local hash = minetest.hash_node_position(pos)
|
|
local found = enercache[hash]
|
|
if found and found > nodecore.gametime then return end
|
|
enercache[hash] = nodecore.gametime + 1
|
|
minetest.add_particlespawner({
|
|
amount = 10,
|
|
time = 1,
|
|
minpos = {x = pos.x - 0.5, y = pos.y - 0.5, z = pos.z - 0.5},
|
|
maxpos = {x = pos.x + 0.5, y = pos.y + 1.5, z = pos.z + 0.5},
|
|
minvel = {x = -0.5, y = -0.5, z = -0.5},
|
|
maxvel = {x = 0.5, y = 0.5, z = 0.5},
|
|
texture = "nc_lux_base.png^[mask:nc_lux_dot_mask.png^[opacity:128",
|
|
minexptime = 0.20,
|
|
maxexptime = 0.25,
|
|
glow = 4
|
|
})
|
|
end
|
|
|
|
local luxemits = {}
|
|
minetest.after(0, function()
|
|
for k, v in pairs(minetest.registered_nodes) do
|
|
luxemits[k] = v.groups and v.groups.lux_emit or 0
|
|
end
|
|
end)
|
|
local function isziprune(pos, node)
|
|
pos = vector.round(pos)
|
|
node = node or minetest.get_node(pos)
|
|
|
|
if node.name ~= glyph then return end
|
|
|
|
local face = nodecore.facedirs[node.param2]
|
|
|
|
local upos = vector.add(pos, face.b)
|
|
local unode = minetest.get_node(upos)
|
|
if luxemits[unode.name] >= minpower then
|
|
return zipscan(pos, face.k)
|
|
end
|
|
|
|
local uupos = vector.add(upos, face.b)
|
|
local uunode = minetest.get_node(uupos)
|
|
if luxemits[uunode.name] >= 16 then
|
|
return zipscan(pos, face.k)
|
|
end
|
|
end
|
|
|
|
local function inrange(pos, spot)
|
|
-- Allow very little lateral movement, i.e. voluntarily
|
|
-- walking off a rune makes you leave it, but allow some
|
|
-- downward movement for gravity.
|
|
return math_abs(pos.x - spot.x) <= 0.1
|
|
and math_abs(pos.z - spot.z) <= 0.1
|
|
and (pos.y - spot.y) <= 0.1
|
|
and (pos.y - spot.y) >= -1.5
|
|
end
|
|
|
|
local function dsqr(a, b)
|
|
local v = vector.subtract(a, b)
|
|
return vector.dot(v, v)
|
|
end
|
|
|
|
local yoffs = 0.49
|
|
|
|
local function logact(fmt, ...)
|
|
local params = {...}
|
|
for i, v in pairs(params) do
|
|
if type(v) == "table" and v.x and v.y and v.z then
|
|
params[i] = minetest.pos_to_string(vector.round(v))
|
|
end
|
|
end
|
|
return minetest.log("action", modname .. ": "
|
|
.. string_format(fmt, unpack(params)))
|
|
end
|
|
|
|
local function playercheck(player, stepdata)
|
|
local ctl = player:get_player_control()
|
|
if ctl.sneak or ctl.jump or ctl.up or ctl.down or ctl.left or ctl.right then return end
|
|
|
|
local pos = player:get_pos()
|
|
local data = zipdata_get(player)
|
|
if not data.pos then
|
|
if not isziprune(pos) then return end
|
|
logact("%s enters ziprune at %s", player:get_player_name(), pos)
|
|
data = {pos = pos}
|
|
zipdata_set(player, data)
|
|
end
|
|
stepdata.properties.makes_footstep_sound = false
|
|
|
|
local vel = player:get_player_velocity()
|
|
if vector.dot(vel, vel) > 0.1 then return end
|
|
|
|
if not inrange(pos, data.pos) then
|
|
if isziprune(pos) then
|
|
data = {pos = pos}
|
|
zipdata_set(player, data)
|
|
else
|
|
for _, p in pairs(data.oldpos or {}) do
|
|
if inrange(pos, p) then
|
|
logact("%s correcting %0.2f",
|
|
player:get_player_name(),
|
|
vector.distance(pos, data.pos))
|
|
data.pos.keepinv = true
|
|
player:set_pos(data.pos)
|
|
return nodecore.player_visible(player) and energized(pos)
|
|
end
|
|
end
|
|
logact("%s exits ziprune at %s", player:get_player_name(), pos)
|
|
return zipdata_set(player)
|
|
end
|
|
end
|
|
pos = vector.round(pos)
|
|
|
|
local runes = nodecore.find_nodes_around(pos, glyph, 1)
|
|
table_sort(runes, function(a, b)
|
|
local da = dsqr(pos, a)
|
|
local db = dsqr(pos, b)
|
|
if da ~= db then return da < db end
|
|
if a.y ~= b.y then return a.y < b.y end
|
|
if a.x ~= b.x then return a.x < b.x end
|
|
return a.z < b.z
|
|
end)
|
|
for _, p in pairs(runes) do
|
|
local hit = zipcheck(p)
|
|
if hit then
|
|
logact("%s zips from %s to %s", player:get_player_name(), pos, hit)
|
|
if nodecore.player_visible(player) then
|
|
local backdir = vector.direction(hit, pos)
|
|
for i = 0, vector.distance(pos, hit), 4 do
|
|
local spos = vector.add(hit, vector.multiply(backdir, i))
|
|
spos.x = spos.x + math_random() - 0.5
|
|
spos.y = spos.y + math_random() - 0.5
|
|
spos.z = spos.z + math_random() - 0.5
|
|
nodecore.sound_play(modname .. "_zip", {pos = spos})
|
|
end
|
|
end
|
|
data.oldpos = appendpos(data.oldpos or {}, data.pos)
|
|
data.pos = hit
|
|
zipdata_set(player, data)
|
|
player:set_pos({x = hit.x, y = hit.y - yoffs, z = hit.z, keepinv = true})
|
|
return nodecore.player_visible(player) and sparkly(p, hit)
|
|
end
|
|
end
|
|
|
|
return nodecore.player_visible(player) and energized(pos)
|
|
end
|
|
|
|
nodecore.register_playerstep({
|
|
label = "ziprune check",
|
|
priority = -100,
|
|
action = playercheck
|
|
})
|
|
|
|
do
|
|
local ziprunedesc = nodecore.translate("Charcoal ZipRune")
|
|
|
|
local glyphdef = minetest.registered_nodes[glyph]
|
|
local oldthru = glyphdef.on_node_touchthru
|
|
if oldthru then
|
|
rawset(glyphdef, "on_node_touchthru", function(pos, node, under, player, ...)
|
|
if isziprune(pos, node) then
|
|
local raw = nodecore.touchtip_node(under, nil, player)
|
|
if raw and vector.equals(vector.subtract(under, pos),
|
|
nodecore.facedirs[node.param2].b) then
|
|
return ziprunedesc .. raw
|
|
end
|
|
end
|
|
return oldthru(pos, node, under, player, ...)
|
|
end)
|
|
end
|
|
end
|
|
|
|
nodecore.register_dnt({
|
|
name = modname,
|
|
time = 1,
|
|
action = function(pos, node)
|
|
if not isziprune(pos, node) then return end
|
|
|
|
nodecore.dnt_set(pos, modname)
|
|
nodecore.sound_play(modname .. "_hum", {pos = pos, gain = 0.25})
|
|
minetest.add_particlespawner({
|
|
amount = 20,
|
|
time = 1,
|
|
minpos = {x = pos.x - 0.5, y = pos.y - 7/16, z = pos.z - 0.5},
|
|
maxpos = {x = pos.x + 0.5, y = pos.y - 7/16, z = pos.z + 0.5},
|
|
minvel = {x = 0, y = 0, z = 0},
|
|
maxvel = {x = 0, y = 0, z = 0},
|
|
minacc = {x = 0, y = 1, z = 0},
|
|
maxacc = {x = 0, y = 1, z = 0},
|
|
texture = "nc_lux_base.png^[mask:nc_lux_dot_mask.png^[opacity:128",
|
|
minexptime = 1,
|
|
maxexptime = 2,
|
|
glow = 4
|
|
})
|
|
end
|
|
})
|
|
|
|
nodecore.register_limited_abm({
|
|
label = "ZipRune Detection",
|
|
interval = 1,
|
|
chance = 1,
|
|
limited_max = 100,
|
|
limited_alert = 1000,
|
|
nodenames = {glyph},
|
|
action = function(pos, node)
|
|
if not isziprune(pos, node) then return end
|
|
nodecore.dnt_set(pos, modname)
|
|
end
|
|
})
|