46999c1eda
- initial beta version - included support files for public release
921 lines
29 KiB
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", "" },
|
|
}
|
|
} )
|
|
|