protector/init.lua

921 lines
29 KiB
Lua

--------------------------------------------------------
-- Minetest :: Protection Redux Mod (protector)
--
-- See README.txt for licensing and other information.
-- Copyright (c) 2016-2019, Leslie E. Krause
--
-- ./games/minetest_game/mods/protector/init.lua
--------------------------------------------------------
local OWNER_ANYBODY = "_anybody"
local OWNER_SOMEBODY = "_somebody"
local OWNER_NOBODY = "" -- do not change this value!
local member_limit = 8
local max_tool_range = 10
local protector_radius = tonumber( minetest.setting_get( "protector_radius" ) or 5 )
minetest.register_privilege( "superuser", {
description = "Bypass ownership and protection checks.",
give_to_singleplayer = true,
} )
---------------------------
-- Helper Functions
---------------------------
local function is_superuser( name )
return minetest.check_player_privs( name, "superuser" )
end
local function get_members( meta )
return meta:get_string( "members" ):split( " " )
end
local function set_members( meta, members )
meta:set_string( "members", table.concat( members, " " ) )
end
local function is_member( meta, name )
for _, n in pairs( get_members( meta ) ) do
if n == name then
return true
end
end
return false
end
local function is_owner( meta, name )
return name == meta:get_string( "owner" )
end
local function add_member( meta, name )
if is_member( meta, name ) or is_owner( meta, name ) then
return
end
local members = get_members( meta )
if #members < member_limit then
table.insert( members, name )
end
set_members( meta, members )
end
local function del_member( meta, name )
local members = get_members( meta )
for i, n in pairs( members ) do
if n == name then
table.remove( members, i )
break
end
end
set_members( meta, members )
end
local function get_area_stats( meta )
local bitmap = meta:get_string( "bitmap" )
local node_total = #bitmap
local lock_count = 0
for node_count = 1, node_total do
if string.byte( bitmap, node_count ) == 120 then
lock_count = lock_count + 1
end
end
return node_total, lock_count
end
local function is_area_locked( meta )
local bitmap = meta:get_string( "bitmap" )
return bitmap ~= ""
end
local function is_area_locked_at( meta, source_pos, target_pos )
local bitmap = meta:get_string( "bitmap" )
-- if there's no bitmap, then the entire area is unlocked
if bitmap == "" then return false end
-- check for dot in bitmap, indicating that position is locked
local voxel_area = VoxelArea:new( {
MinEdge = vector.subtract( source_pos, protector_radius ),
MaxEdge = vector.add( source_pos, protector_radius )
} )
-- TODO: Sanity check for correct bitmap length!
local idx = voxel_area:indexp( target_pos )
return string.byte( bitmap, idx ) == 120
end
local function lock_area( meta, source_pos, content_ids )
local pos1 = vector.subtract( source_pos, protector_radius )
local pos2 = vector.add( source_pos, protector_radius )
local voxel_manip = minetest.get_voxel_manip( )
local min_pos, max_pos = voxel_manip:read_from_map( pos1, pos2 )
local map_buffer = voxel_manip:get_data( )
local voxel_area = VoxelArea:new( { MinEdge = min_pos, MaxEdge = max_pos } )
local node_data = { }
local lock_count = 0
-- indicate all non-buildable nodes as dot in bitmap
for idx in voxel_area:iterp( pos1, pos2 ) do
local is_buildable = content_ids[ map_buffer[ idx ] ]
table.insert( node_data, is_buildable and " " or "x" )
if not is_buildable then
lock_count = lock_count + 1
end
end
-- bitmap is compressed by engine, so no need for optimization
meta:set_string( "bitmap", table.concat( node_data ) )
return #node_data, lock_count
end
local function unlock_area( meta )
meta:set_string( "bitmap", "" )
end
local function create_bitmap( is_buildable )
return string.rep( is_buildable and " " or "x", math.pow( protector_radius * 2 + 1, 3 ) )
end
local function get_bitmap_raw( meta )
local bitmap = meta:get_string( "bitmap" )
return bitmap ~= "" and bitmap or nil
end
local function set_bitmap_raw( meta, bitmap )
meta:set_string( "bitmap", bitmap )
return get_area_stats( meta )
end
---------------------------
-- Formspec Handlers
---------------------------
local function open_shared_editor( pos, meta, player_name )
local ignore_air = true
local ignore_water = true
local ignore_lava = true
local function get_formspec( )
local formspec = "size[8,7]"
.. default.gui_bg
.. default.gui_bg_img
.. default.gui_slots
.. "label[0.0,0.0;Protector Properties (Shared)]"
.. "box[0.0,0.6;7.9,0.1;#111111]"
.. "box[0.0,6.1;7.9,0.1;#111111]"
.. "label[0.0,1.0;Members: (type player name then click '+' to add)]"
.. "button_exit[6.0,6.5;2.0,0.5;close;Close]"
if is_area_locked( meta, pos ) then
local node_total, lock_count = get_area_stats( meta )
formspec = formspec
.. "label[0.0,4.5;Bitmap Mask: (click 'Unlock' to disengage bitmap mask)]"
.. "button_exit[0.0,5.2;2.0,0.5;unlock;Unlock]"
.. string.format( "label[2.0,5.2;Contains %d nodes (%d locked, %d unlocked)]", node_total, lock_count, node_total - lock_count )
else
formspec = formspec
.. "label[0.0,4.5;Bitmap Mask: (click 'Lock' to engage bitmap mask)]"
.. "button_exit[0.0,5.2;2.0,0.5;lock;Lock]"
.. "checkbox[2.0,5.0;ignore_air;Ignore Air;" .. tostring( ignore_air ) .. "]"
.. "checkbox[3.8,5.0;ignore_water;Ignore Water;" .. tostring( ignore_water ) .. "]"
.. "checkbox[6.0,5.0;ignore_lava;Ignore Lava;" .. tostring( ignore_lava ) .. "]"
end
local members = get_members( meta )
local count = 0
for _, member in pairs( members ) do
if count < member_limit then
formspec = formspec
.. string.format( "button[%0.2f,%0.2f;1.5,0.5;;%s]", count % 4 * 2, 0.8 + math.floor( count / 4 + 1 ), member )
.. string.format( "button[%0.2f,%0.2f;0.75,0.5;del_member_%s;X]", count % 4 * 2 + 1.25, 0.8 + math.floor( count / 4 + 1 ), member )
end
count = count + 1
end
if count < member_limit then
formspec = formspec
.. string.format( "field[%0.2f,%0.2f;1.433,0.5;member_name;;]", count % 4 * 2 + 1 / 3, 0.8 + math.floor( count / 4 + 1 ) + 1 / 3 )
.. string.format( "button[%0.2f,%0.2f;0.75,0.5;add_member;+]", count % 4 * 2 + 1.25, 0.8 + math.floor( count / 4 + 1 ) )
end
return formspec
end
local function on_close( pos, player, fields )
if fields.close then
return
elseif fields.ignore_air then
ignore_air = fields.ignore_air == "true"
elseif fields.ignore_water then
ignore_water = fields.ignore_water == "true"
elseif fields.ignore_lava then
ignore_lava = fields.ignore_lava == "true"
elseif fields.lock then
local content_ids = { }
if ignore_water then
content_ids[ minetest.get_content_id( "default:water_flowing" ) ] = true
content_ids[ minetest.get_content_id( "default:water_source" ) ] = true
end
if ignore_lava then
content_ids[ minetest.get_content_id( "default:lava_flowing" ) ] = true
content_ids[ minetest.get_content_id( "default:lava_source" ) ] = true
end
if ignore_air then
content_ids[ minetest.get_content_id( "air" ) ] = true
end
local node_total, lock_count = lock_area( meta, pos, content_ids )
minetest.chat_send_player( player_name, string.format( "Protection area updated (%d of %d nodes locked).", lock_count, node_total ) )
elseif fields.unlock then
unlock_area( meta )
minetest.chat_send_player( player_name, "Protection area updated (all nodes unlocked)." )
elseif fields.add_member then
if string.match( fields.member_name, "^[a-zA-Z0-9_-]+$" ) and string.len( fields.member_name ) <= 25 then
add_member( meta, fields.member_name )
minetest.update_form( player:get_player_name( ), get_formspec( meta ) )
end
elseif not fields.quit then
fields.member_name = nil
local fname = next( fields, nil ) -- use next since we only care about the name of the first button
if fname then
local member_name = string.match( fname, "^del_member_(.+)" )
if member_name then
del_member( meta, member_name )
minetest.update_form( player:get_player_name( ), get_formspec( meta ) )
end
end
end
end
minetest.create_form( pos, player_name, get_formspec( ), on_close )
end
local function open_public_editor( pos, meta, player_name )
local ignore_air = true
local ignore_water = false
local ignore_lava = false
local allow_doors = meta:get_string( "allow_doors" ) == "true"
local allow_chests = meta:get_string( "allow_chests" ) == "true"
local function get_formspec( )
local formspec = "size[8,5]"
.. default.gui_bg
.. default.gui_bg_img
.. default.gui_slots
.. "label[0.0,0.0;Protector Properties (Public)]"
.. "box[0.0,0.6;7.9,0.1;#111111]"
.. "box[0.0,4.1;7.9,0.1;#111111]"
.. "label[0.0,1.0;Permissions:]"
.. "button_exit[6.0,4.5;2.0,0.5;close;Close]"
.. "checkbox[0.0,1.4;allow_doors;Allow Steel Doors;" .. tostring( allow_doors ) .. "]"
.. "checkbox[4.0,1.4;allow_chests;Allow Locked Chests;" .. tostring( allow_chests ) .. "]"
if is_area_locked( meta, pos ) then
local node_total, lock_count = get_area_stats( meta )
formspec = formspec
.. "label[0.0,2.5;Bitmap Mask: (click 'Unlock' to disengage bitmap mask)]"
.. "button_exit[0.0,3.2;2.0,0.5;unlock;Unlock]"
.. string.format( "label[2.0,3.2;Contains %d nodes (%d locked, %d unlocked)]", node_total, lock_count, node_total - lock_count )
else
formspec = formspec
.. "label[0.0,2.5;Bitmap Mask: (click 'Lock' to engage bitmap mask)]"
.. "button_exit[0.0,3.2;2.0,0.5;lock;Lock]"
.. "checkbox[2.0,3.0;ignore_air;Ignore Air;" .. tostring( ignore_air ) .. "]"
.. "checkbox[3.8,3.0;ignore_water;Ignore Water;" .. tostring( ignore_water ) .. "]"
.. "checkbox[6.0,3.0;ignore_lava;Ignore Lava;" .. tostring( ignore_lava ) .. "]"
end
return formspec
end
local function on_close( pos, player, fields )
if fields.close then
return
elseif fields.ignore_air then
ignore_air = fields.ignore_air == "true"
elseif fields.ignore_water then
ignore_water = fields.ignore_water == "true"
elseif fields.ignore_lava then
ignore_lava = fields.ignore_lava == "true"
elseif fields.allow_chests then
allow_chests = fields.allow_chests == "true"
meta:set_string( "allow_chests", allow_chests and "true" or "false" )
elseif fields.allow_doors then
allow_doors = fields.allow_doors == "true"
meta:set_string( "allow_doors", allow_doors and "true" or "false" )
elseif fields.lock then
local content_ids = { }
if ignore_water then
content_ids[ minetest.get_content_id( "default:water_flowing" ) ] = true
content_ids[ minetest.get_content_id( "default:water_source" ) ] = true
end
if ignore_lava then
content_ids[ minetest.get_content_id( "default:lava_flowing" ) ] = true
content_ids[ minetest.get_content_id( "default:lava_source" ) ] = true
end
if ignore_air then
content_ids[ minetest.get_content_id( "air" ) ] = true
end
local node_total, lock_count = lock_area( meta, pos, content_ids )
minetest.chat_send_player( player_name, string.format( "Protection area updated (%d of %d nodes locked).", lock_count, node_total ) )
elseif fields.unlock then
unlock_area( meta )
minetest.chat_send_player( player_name, "Protection area updated (all nodes unlocked)." )
end
end
minetest.create_form( pos, player_name, get_formspec( ), on_close )
end
---------------------------
-- Protection Handlers
---------------------------
-- Info Level:
-- 0 for no info
-- 1 for "This area is owned by <owner> !" if you can't dig
-- 2 for "This area is owned by <owner>.
-- 3 for checking protector overlaps
local function can_dig( radius, target_pos, player_name, is_strict, info_level )
if not player_name then return false end
-- Privileged users can override protection
if minetest.check_player_privs( player_name, "superuser" ) and info_level == 1 then
return true
end
if info_level == 3 then info_level = 1 end
local pos_list = minetest.find_nodes_in_area(
vector.subtract( target_pos, radius or protector_radius ),
vector.add( target_pos, radius or protector_radius ),
{ "protector:protect", "protector:protect2", "protector:protect3" }
)
for _, pos in pairs( pos_list ) do
local meta = minetest.get_meta( pos )
local owner = meta:get_string( "owner" )
local members = meta:get_string( "members" )
local is_public = minetest.get_node( pos ).name == "protector:protect3"
if owner ~= player_name then
if is_strict or not is_public and not is_member( meta, player_name ) or is_area_locked_at( meta, pos, target_pos ) then
if info_level == 1 then
minetest.chat_send_player( player_name, "This area is owned by " .. owner .. "!" )
elseif info_level == 2 then
minetest.chat_send_player( player_name, "This area is owned by " .. owner .. "." )
end
if members ~= "" then
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos ) .. " with members: " .. members .. "." )
else
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos ) .. "." )
end
return false
end
end
if info_level == 2 then
minetest.chat_send_player( player_name, "This area is owned by " .. owner .. "." )
if members ~= "" then
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos ) .. " with members: " .. members .. "." )
else
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos ) .. "." )
end
return false
end
end
if info_level == 2 then
if #positions == 0 then
minetest.chat_send_player( player_name, "This area is not protected." )
end
minetest.chat_send_player( digger, "You can build here." )
end
return true
end
local old_is_protected = minetest.is_protected
minetest.is_protected = function ( pos, player_name )
local owner = minetest.get_meta( pos ):get_string( "owner" )
local player = minetest.get_player_by_name( player_name )
-- owner = _anybody -> always FALSE
-- owner = <digger> -> always FALSE
-- owner = _somebody -> always TRUE
-- owner = <stranger> -> always TRUE
-- owner = _nobody / null -> return protection
-- never allow digging of nodes owned by stranger or by OWNER_SOMEBODY (strictly private ownership)
-- always allow digging of nodes owned by digger or by OWNER_ANYBODY (strictly public ownership)
-- otherwise verify protection rules for nodes owned by OWNER_NOBODY (default ownership, when undefined)
-- prevent long-range dig exploit (evading protectors in unloaded areas)
if vector.distance( pos, player:getpos( ) ) > max_tool_range then
return true
end
if owner == player_name or owner == OWNER_ANYBODY or owner == OWNER_NOBODY and can_dig( protector_radius, pos, player_name, false, 1 ) then
return old_is_protected( pos, player_name )
else
return true
end
end
---------------------------
-- Anti-Grief Hooks
---------------------------
local function allow_place( target_pos, player_name, node_name )
if is_superuser( player_name ) then return true end
local pos_list = minetest.find_nodes_in_area(
vector.subtract( target_pos, protector_radius ),
vector.add( target_pos, protector_radius ),
{ "protector:protect3" }
)
for _, pos in pairs( pos_list ) do
local meta = minetest.get_meta( pos )
local allow_doors = meta:get_string( "allow_doors" ) == "true"
local allow_chests = meta:get_string( "allow_chests" ) == "true"
-- check restrictions of public protector
if node_name == "default:chest_locked" and not allow_chests or node_name == "doors:door_steel" and not allow_doors then
return false
end
end
return true
end
minetest.override_item( "default:chest_locked", {
allow_place = function ( target_pos, player )
local player_name = player:get_player_name( )
if not allow_place( target_pos, player_name, "default:chest_locked" ) then
minetest.chat_send_player( player_name, "You are not allowed to place locked chests here!" )
return false
end
return true
end
} )
minetest.override_item( "doors:door_steel", {
allow_place = function ( target_pos, player )
local player_name = player:get_player_name( )
if not allow_place( target_pos, player_name, "doors:door_steel" ) then
minetest.chat_send_player( player_name, "You are not allowed to place steel doors here!" )
return false
end
return true
end
} )
---------------------------
-- Tool Definitions
---------------------------
minetest.register_tool( "protector:protection_wand", {
description = "Protection Wand",
range = 5,
inventory_image = "protector_wand.png",
on_use = function( itemstack, player, pointed_thing )
local pos = pointed_thing.under or vector.round( vector.offset_y( player:getpos( ) ) ) -- if pointing at air, get player position instead
local player_name = player:get_player_name( )
-- find the protector nodes
local pos_list = minetest.find_nodes_in_area(
vector.subtract( pos, protector_radius ),
vector.add( pos, protector_radius ),
{ "protector:protect", "protector:protect2", "protector:protect3" }
)
if #pos_list == 0 then
minetest.chat_send_player( player_name, "This area is not protected." )
return
end
for i = 1, math.min( 5, #pos_list ) do
local owner = minetest.get_meta( pos_list[ i ] ):get_string( "owner" ) or ""
local members = minetest.get_meta( pos_list[ i ] ):get_string( "members" ) or ""
if i == 1 then
minetest.chat_send_player( player_name, "This area is owned by " .. owner .. "." )
end
if members ~= "" then
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos_list[ i ] ) .. " with members: " .. members .. "." )
else
minetest.chat_send_player( player_name, "Protector located at " .. minetest.pos_to_string( pos_list[ i ] ) .. "." )
end
end
end,
} )
minetest.register_craftitem( "protector:protection_mask", {
description = "Protection Mask (Point to protector and use)",
inventory_image = "protector_mask.png",
wield_image = "protector_mask.png",
groups = { flammable = 3 },
on_use = function( cur_stack, player, pointed_thing )
if pointed_thing.type == "node" then
local player_name = player:get_player_name( )
local player_inv = player:get_inventory( )
local node_meta = minetest.get_meta( pointed_thing.under )
local node_name = minetest.get_node( pointed_thing.under ).name
if node_name ~= "protector:protect" and node_name ~= "protector:protect2" and node_name ~= "protector:protect3" then
return cur_stack
end
-- only owner of protector is permitted to copy mask
if not is_superuser( player_name ) and not is_owner( node_meta, player_name ) then
minetest.chat_send_player( player_name, "Access denied. Failed to copy protection mask." )
return cur_stack
end
local new_stack = ItemStack( "protector:protection_mask_saved" )
local new_stack_meta = new_stack:get_meta( )
local bitmap = get_bitmap_raw( node_meta ) or create_bitmap( true )
local node_total, lock_count = set_bitmap_raw( new_stack_meta, bitmap )
local title = string.format( "%d of %d nodes locked", lock_count, node_total )
new_stack_meta:set_string( "title", title )
new_stack_meta:set_string( "description", "Protection Mask (" .. title .. ")" )
new_stack_meta:set_string( "owner", player_name )
minetest.chat_send_player( player_name, "Protection mask copied (" .. title .. ")" )
cur_stack:take_item( )
if cur_stack:is_empty( ) then
cur_stack:replace( new_stack )
elseif player_inv:room_for_item( "main", new_stack ) then
player_inv:add_item( "main", new_stack )
else
minetest.add_item( player:getpos( ), new_stack )
end
end
return cur_stack
end
} )
minetest.register_craftitem( "protector:protection_mask_saved", {
inventory_image = "protector_mask_saved.png",
wield_image = "protector_mask_saved.png",
stack_max = 1,
groups = { flammable = 3, not_in_creative_inventory = 1 },
on_use = function( cur_stack, player, pointed_thing )
if pointed_thing.type == "node" then
local player_name = player:get_player_name( )
local node_meta = minetest.get_meta( pointed_thing.under )
local node_name = minetest.get_node( pointed_thing.under ).name
if node_name ~= "protector:protect" and node_name ~= "protector:protect2" and node_name ~= "protector:protect3" then
return cur_stack
end
-- only owner of protector is permitted to paste mask
if not is_superuser( player_name ) and not is_owner( node_meta, player_name ) then
minetest.chat_send_player( player_name, "Access denied. Failed to paste protection mask." )
return cur_stack
end
local cur_stack_meta = cur_stack:get_meta( )
local bitmap = get_bitmap_raw( cur_stack_meta )
local node_total, lock_count = set_bitmap_raw( node_meta, bitmap )
minetest.chat_send_player( player_name, string.format( "Protection mask pasted (%d of %d nodes locked).", lock_count, node_total ) )
end
return cur_stack
end
} )
---------------------------
-- Node Definitions
---------------------------
local function on_place( itemstack, placer, pointed_thing )
if pointed_thing.type == "node" then
if not can_dig( protector_radius * 2, pointed_thing.above, placer:get_player_name( ), true, 3 ) then
minetest.chat_send_player( placer:get_player_name( ), "Overlaps into above player's protected area." )
else
return minetest.item_place( itemstack, placer, pointed_thing )
end
end
return itemstack
end
minetest.register_node( "protector:protect", {
description = "Protection Stone (Shared)",
tiles = {
"protector_top1.png",
"protector_top1.png",
"protector_side1.png"
},
sounds = default.node_sound_stone_defaults( ),
groups = { dig_immediate = 2, unbreakable = 1 },
is_ground_content = false,
paramtype = "light",
light_source = 4,
can_dig = function( pos, player )
return can_dig( 1, pos, player:get_player_name( ), true, 1 )
end,
on_place = on_place,
after_place_node = function( pos, placer )
local meta = minetest.get_meta( pos )
local player_name = placer:get_player_name( ) or "singleplayer"
meta:set_string( "owner", player_name )
meta:set_string( "infotext", "Protection (owned by " .. player_name .. ")" )
end,
on_rightclick = function( pos, node, clicker, itemstack )
local meta = minetest.get_meta( pos )
local player_name = clicker:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
open_shared_editor( pos, meta, player_name )
end
end,
on_punch = function( pos, node, puncher )
local meta = minetest.get_meta( pos )
local player_name = puncher:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
minetest.add_entity( pos, "protector:display")
end
end,
on_blast = function() end,
} )
minetest.register_node( "protector:protect2", {
description = "Protection Badge (Shared)",
tiles = { "protector_logo.png" },
wield_image = "protector_logo.png",
inventory_image = "protector_logo.png",
sounds = default.node_sound_stone_defaults( ),
groups = { dig_immediate = 2, unbreakable = 1 },
paramtype = "light",
paramtype2 = "wallmounted",
legacy_wallmounted = true,
light_source = 4,
drawtype = "nodebox",
sunlight_propagates = true,
walkable = false,
node_box = {
type = "wallmounted",
wall_top = { -0.375, 0.4375, -0.5, 0.375, 0.5, 0.5 },
wall_bottom = { -0.375, -0.5, -0.5, 0.375, -0.4375, 0.5 },
wall_side = { -0.5, -0.5, -0.375, -0.4375, 0.5, 0.375 },
},
selection_box = { type = "wallmounted" },
can_dig = function( pos, player )
return can_dig( 1, pos, player:get_player_name( ), true, 1 )
end,
on_place = on_place,
after_place_node = function( pos, placer )
local meta = minetest.get_meta( pos )
local player_name = placer:get_player_name( ) or "singleplayer"
meta:set_string( "owner", player_name )
meta:set_string( "infotext", "Protection (owned by " .. player_name .. ")" )
meta:set_string( "members", "" )
end,
on_rightclick = function( pos, node, clicker, itemstack )
local meta = minetest.get_meta( pos )
local player_name = clicker:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
open_shared_editor( pos, meta, player_name )
end
end,
on_punch = function( pos, node, puncher )
local meta = minetest.get_meta( pos )
local player_name = puncher:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
minetest.add_entity( pos, "protector:display" )
end
end,
on_blast = function( ) end,
} )
minetest.register_node( "protector:protect3", {
description = "Protection Stone (Public)",
tiles = {
"protector_top2.png",
"protector_top2.png",
"protector_side2.png"
},
sounds = default.node_sound_stone_defaults( ),
groups = { dig_immediate = 2, unbreakable = 1 },
is_ground_content = false,
paramtype = "light",
light_source = 4,
can_dig = function( pos, player )
return can_dig( 1, pos, player:get_player_name( ), true, 1 )
end,
on_place = on_place,
after_place_node = function( pos, placer )
local meta = minetest.get_meta( pos )
local player_name = placer:get_player_name( ) or "singleplayer"
meta:set_string( "owner", player_name )
meta:set_string( "infotext", "Protection (owned by " .. player_name .. ")" )
meta:set_string( "allow_doors", "false" )
meta:set_string( "allow_chests", "false" )
local node_total, lock_count = lock_area( meta, pos, {
[minetest.get_content_id( "default:water_flowing" )] = true,
[minetest.get_content_id( "default:water_source" )] = true,
[minetest.get_content_id( "default:lava_flowing" )] = true,
[minetest.get_content_id( "default:lava_source" )] = true,
[minetest.get_content_id( "air" )] = true
} )
minetest.chat_send_player( player_name, string.format( "Protection area updated (%d of %d nodes locked).", lock_count, node_total ) )
end,
on_rightclick = function( pos, node, clicker, itemstack )
local meta = minetest.get_meta( pos )
local player_name = clicker:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
open_public_editor( pos, meta, player_name )
end
end,
on_punch = function( pos, node, puncher )
local meta = minetest.get_meta( pos )
local player_name = puncher:get_player_name( )
if is_owner( meta, player_name ) or is_superuser( player_name ) then
minetest.add_entity( pos, "protector:display" )
end
end,
on_blast = function() end,
} )
---------------------------
-- Entity Definition
---------------------------
minetest.register_entity( "protector:display", {
physical = false,
collisionbox = { 0, 0, 0, 0, 0, 0 },
visual = "wielditem",
-- wielditem is scaled to 1.5 times original node size?
visual_size = { x = 1.0 / 1.5, y = 1.0 / 1.5 },
textures = { "protector:display_node" },
timer = 0,
on_step = function( self, dtime )
self.timer = self.timer + dtime
if self.timer > 7 then
self.object:remove( )
end
end,
} )
local r = protector_radius
-- NB: this node definition is only a basis for the entity above
minetest.register_node( "protector:display_node", {
tiles = { "protector_display.png" },
use_texture_alpha = true,
walkable = false,
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
-- west face
{ -r + 0.55, -r + 0.55, -r + 0.55, -r + 0.45, r + 0.55, r + 0.55 },
-- north face
{ -r + 0.55, -r + 0.55, r + 0.45, r + 0.55, r + 0.55, r + 0.55 },
-- east face
{ r + 0.45, -r + 0.55, -r + 0.55, r + 0.55, r + 0.55, r + 0.55 },
-- south face
{ -r + 0.55, -r + 0.55, -r + 0.55, r + 0.55, r + 0.55, -r + 0.45 },
-- top face
{ -r + 0.55, r + 0.45, -r + 0.55, r + 0.55, r + 0.55, r + 0.55 },
-- bottom face
{ -r + 0.55, -r + 0.55, -r + 0.55, r + 0.55, -r + 0.45, r +0.55 },
-- center (surround protector)
{ -0.55, -0.55, -0.55, 0.55, 0.55, 0.55 },
},
},
selection_box = {
type = "regular",
},
paramtype = "light",
groups = { dig_immediate = 3, not_in_creative_inventory = 1 },
drop = "",
} )
---------------------------
-- Crafting Recipes
---------------------------
minetest.register_craft( {
output = "protector:protect3",
recipe = {
{ "default:stone", "default:stone", "default:stone" },
{ "default:stone", "default:mese", "default:stone" },
{ "default:stone", "default:stone", "default:stone" },
}
} )
minetest.register_craft( {
output = "protector:protect2",
recipe = {
{ "default:stone", "default:copper_ingot", "default:stone" },
{ "default:copper_ingot", "default:mese", "default:copper_ingot" },
{ "default:stone", "default:copper_ingot", "default:stone" },
}
} )
minetest.register_craft( {
output = "protector:protect",
recipe = {
{ "default:stone", "default:steel_ingot", "default:stone" },
{ "default:steel_ingot", "default:mese", "default:steel_ingot" },
{ "default:stone", "default:steel_ingot", "default:stone" },
}
} )
minetest.register_craft( {
output = "protector:protection_wand",
recipe = {
{ "default:mese_crystal" },
{ "default:stick" },
}
} )
minetest.register_craft( {
output = "protector:protection_mask",
recipe = {
{ "", "default:steel_ingot", "" },
{ "default:steel_ingot", "default:mese_crystal", "default:steel_ingot" },
{ "", "default:steel_ingot", "" },
}
} )