-- LUALOCALS < --------------------------------------------------------- local math, minetest, nodecore, pairs, rawset, string, table, tonumber, vector = math, minetest, nodecore, pairs, rawset, string, table, tonumber, 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 powernodes = {} for i = minpower, 8 do powernodes["nc_lux:cobble" .. i] = true 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 unode = minetest.get_node(vector.add(pos, face.b)) if not powernodes[unode.name] then return end return zipscan(pos, face.k) 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 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 correct %s to %s", player:get_player_name(), pos, p) 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}) 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 = "Charcoal ZipRune" nodecore.translate_inform(ziprunedesc) local glyphdef = minetest.registered_nodes[glyph] local oldthru = glyphdef.on_node_touchthru if oldthru then rawset(glyphdef, "on_node_touchthru", function(pos, node, ...) if isziprune(pos, node) then return ziprunedesc end return oldthru(pos, node, ...) 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}, neighbors = {"group:lux_cobble"}, action = function(pos) nodecore.dnt_set(pos, modname) end })