nc_ziprunes/init.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
})