-- "I'll stop calling it Minetest when it stops being one." minetest = core -- Override properties of a table from those of another table function extend(dst, src) for k, v in pairs(src) do dst[k] = v end return dst end table.merge = extend -- PHP-style helper verb function include(file) return dofile(minetest.get_modpath(minetest.get_current_modname()).."/"..file) end function include_all(dir) for _, x in pairs(minetest.get_dir_list(minetest.get_modpath(minetest.get_current_modname()).."/"..dir)) do include(dir.."/"..x) end end function warn(str) minetest.log("warning", str) end function err(str) minetest.log("error", str.."\n"..debug.traceback()) end function tell(p, msg) minetest.chat_send_player(p, "# Server: "..msg) end function say(msg) minetest.chat_send_all("# Server: "..msg) end EventTarget = { init = function() local e = { listeners = {} } return setmetatable(e, {__index = EventTarget}) end, listen = function(e, channel, fn) if not e.listeners[channel] then e.listeners[channel] = {} end local l = e.listeners[channel] l[#l +1] = fn end, unlisten = function(e, channel, fn) if not e.listeners[channel] then return end local l = e.listeners[channel] local idx = table.indexof(l, fn) if idx < 0 then return end table.remove(l, idx) end, dispatch = function(e, channel, ...) local l = e.listeners[channel] if not l then return end for i = 1, #l do if l[i] then l[i](...) end end end } setmetatable(EventTarget, { __call = function(_, ...) return EventTarget.init(...) end }) StateMachine = { init = function(obj, states) local super = EventTarget() local e = table.merge(super, { states = states, active_states = {}, obj = obj }) return setmetatable(e, {__index = StateMachine}) end, add_state = function(e, state) if e.active_states[state] then return false end if e.states[state].excludes then for i = 1, #e.states[state].excludes do if e.active_states[e.states[state].excludes[i]] then e:remove_state(e.states[state].excludes[i]) end end end e.states[state]:add(e.obj) e.active_states[state] = true return true end, tick = function(e) for k in pairs(e.active_states) do e.states[k]:tick(e.obj) end end, remove_state = function(e, state) if not e.active_states[state] then return false end e.states[state]:remove(e.obj) e.active_states[state] = nil return true end, } setmetatable(StateMachine, { __call = function(_, ...) return StateMachine.init(...) end, __index = EventTarget() }) ModifierStack = { init = function(mode) local e = EventTarget() extend(e, { mode = mode or "add", value = 0 }) return setmetatable(e, {__index = ModifierStack}) end, set = function(e, key, value) if key == "value" then return end if e[key] then if e.mode == "multiply" then e.value = e.value /e[key] else e.value = e.value -e[key] end end e[key] = value if e[key] then if e.mode == "multiply" then e.value = e.value *e[key] else e.value = e.value +e[key] end end e:dispatch("change") end, } setmetatable(ModifierStack, { __call = function(_, ...) return ModifierStack.init(...) end, __index = EventTarget }) rgt = { adjacent_neighbor_offests = { vector.new(0,0,1), vector.new(0,0,-1), vector.new(1,0,0), vector.new(-1,0,0), vector.new(1,0,1), vector.new(1,0,-1), vector.new(-1,0,1), vector.new(-1,0,-1), }, adjacent_horizontal_neighbor_offests = { vector.new(0,0,1), vector.new(0,0,-1), vector.new(1,0,0), vector.new(-1,0,0), }, abutting_neighbor_offests = { vector.new(0,0,1), vector.new(0,0,-1), vector.new(1,0,0), vector.new(-1,0,0), vector.new(0,1,0), vector.new(0,-1,0), }, nodes_to_content_ids = {}, content_ids_to_nodes = {}, vm_data = {}, } local ns = rgt function ns.register_node(name, def) def._name = name local alias if not name:find(":") then alias = name name = "red_glazed_terracotta:"..name end if def.groups then if def.groups.interactable then def.on_rightclick = function(pos, _, p) rgt.players[p:get_player_name()].interacting_with = pos end end end if def._variants then if type(def._variants) == "string" then rgt_world["register_"..def._variants](def) else for _, x in ipairs(def._variants) do rgt_world["register_"..x](def) end end end minetest.register_node(":"..name, def) local cid = minetest.get_content_id(name) ns.nodes_to_content_ids[name] = cid ns.content_ids_to_nodes[cid] = name if alias then minetest.register_alias(alias, name) end end function ns.register_item(name, def) def._name = name local alias if not name:find(":") then alias = name name = "red_glazed_terracotta:"..name end minetest.register_craftitem(":"..name, def) if alias then minetest.register_alias(alias, name) end end function ns.register_tool(name, def) def._name = name local alias if not name:find(":") then alias = name name = "red_glazed_terracotta:"..name end minetest.register_tool(":"..name, def) if alias then minetest.register_alias(alias, name) end end function ns.register_entity(name, def) def._name = name if not name:find(":") then name = "red_glazed_terracotta:"..name end minetest.register_entity(":"..name, def) end -- Make node dig particles denser. minetest.register_on_dignode(function(pos, node, digger) local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 local def = minetest.registered_nodes[node.name] minetest.add_particlespawner({ amount = 128, time = 0.001, minpos = vector.offset(pos, -0.35, -0.35, -0.35), maxpos = vector.offset(pos, 0.35, 0.35, 0.35), minvel = vector.new(-1.7, 0, -1.7), maxvel = vector.new(1.7, 3.5, 1.7), minacc = vector.new(0, -gravity *2, 0), maxacc = vector.new(0, -gravity *2, 0), minexptime = 0, maxexptime = 1, minsize = 0, maxsize = 0, -- random node = node, minsize = 0.5, maxsize = 1.4, blend = (def and def.use_texture_alpha == "blend") and "blend" or "clip", }) end) -- HACK: Lookup table for getting a rotation from a -- facedir (because Minetest doesn't have any way -- to get this information normally) local facedir_rotations = { -- +Y [0] = vector.new(0, 0, 0), [1] = vector.new(0, math.pi * 1.5, 0), [2] = vector.new(0, math.pi * 1.0, 0), [3] = vector.new(0, math.pi * 0.5, 0), -- +Z [4] = vector.new(math.pi * 1.5, 0, 0), [5] = vector.new(0, math.pi * 1.5, math.pi * 1.5), [6] = vector.new(math.pi * 0.5, math.pi * 1.0, 0), [7] = vector.new(0, math.pi * 0.5, math.pi * 0.5), -- -Z [8] = vector.new(math.pi * 0.5, 0, 0), [9] = vector.new(0, math.pi * 1.5, math.pi * 0.5), [10] = vector.new(math.pi * 1.5, math.pi * 1.0, 0), [11] = vector.new(0, math.pi * 0.5, math.pi * 1.5), -- +X [12] = vector.new(0, 0, math.pi * 0.5), [13] = vector.new(math.pi * 1.5, math.pi * 1.5, 0), [14] = vector.new(0, math.pi * 1.0, math.pi * 1.5), [15] = vector.new(math.pi * 0.5, math.pi * 0.5, 0), -- -X [16] = vector.new(0, 0, math.pi * 1.5), [17] = vector.new(math.pi * 0.5, math.pi * 1.5, 0), [18] = vector.new(0, math.pi * 1.0, math.pi * 0.5), [19] = vector.new(math.pi * 1.5, math.pi * 0.5, 0), -- -Y [20] = vector.new(0, 0, math.pi * 1.0), [21] = vector.new(0, math.pi * 0.5, math.pi * 1.0), [22] = vector.new(0, math.pi * 1.0, math.pi * 1.0), [23] = vector.new(0, math.pi * 1.5, math.pi * 1.0), } function ns.facedir_to_rotation(facedir) return facedir_rotations[facedir] or minetest.facedir_to_dir(facedir):dir_to_rotation() end -- Fills the area from pos1 to pos2 with the node named `node`. function ns.fill_area(pos1, pos2, node) local minp = vector.new(math.min(pos1.x, pos2.x), math.min(pos1.y, pos2.y), math.min(pos1.z, pos2.z)) local maxp = vector.new(math.max(pos1.x, pos2.x), math.max(pos1.y, pos2.y), math.max(pos1.z, pos2.z)) local vm = minetest.get_voxel_manip(pos1, pos2) local min, max = vm:get_emerged_area() local va = VoxelArea(min, max) local data = ns.vm_data vm:get_data(data) local c_node = minetest.get_content_id(node) for i in va:iterp(minp, maxp) do data[i] = c_node end vm:set_data(data) vm:write_to_map() if vm.close then vm:close() end end -- Get a flat texture that may represent the given node (using the first tile). function ns.get_node_texture(node) local def = minetest.registered_nodes[node] if not def or not def.tiles then return "blank.png" end local tx = def.tiles[1] if type(tx) == "string" then return tx end return tx.name or "blank.png" end -- Out-of-line node metadata, allowing meta for a node to be accessed even when its containing mapblock is not loaded. local db = minetest.get_mod_storage() local NodeMetaRef = { set = function(e, key, value) return db:set_string(e._pos.."_"..key, value) end, get = function(e, key) return db:get(e._pos.."_"..key) end, } NodeMetaRef.__index = NodeMetaRef function ns.get_node_meta(pos) return setmetatable({_pos = tostring(minetest.hash_node_position(pos))}, NodeMetaRef) end ns.clock = EventTarget() local seconds = minetest.get_us_time() minetest.register_globalstep(function(dtime) ns.clock:dispatch("tick", dtime) local time = minetest.get_us_time() if time - seconds >= 1000000 then ns.clock:dispatch("every_second", dtime) seconds = time end end) -- Allow nodes to provide a callback to run on activation without -- needing to register a bunch of mostly identical LBMs. minetest.register_lbm { name = ":red_glazed_terracotta:on_activate", nodenames = {"group:run_on_activate"}, action = function(pos, node) minetest.registered_nodes[node.name].on_activate(pos) end } minetest.register_on_joinplayer(function(p) if p:get_player_name() == "singleplayer" then minetest.change_player_privs(p:get_player_name(), { fast = true, fly = true, noclip = true, server = true, give = true, }) end end)