- initial beta version
- included support files for public release
This commit is contained in:
Leslie Krause 2019-04-09 10:35:00 -04:00
commit 46999c1eda
14 changed files with 1057 additions and 0 deletions

129
README.txt Normal file
View File

@ -0,0 +1,129 @@
Protector Redux Mod v1.0
By Leslie Krause
Protector Redux offers an efficient and flexible node-based protection scheme for players
on survival and creative servers. The mod was inspired by TenPlus1's "Protector Redo",
which continues the legacy of Zeg9 and glomie's original protection mods.
Repository
----------------------
* Browse source code:
https://bitbucket.org/sorcerykid/protector
* Download archive:
https://bitbucket.org/sorcerykid/protector/get/master.zip
https://bitbucket.org/sorcerykid/protector/get/master.tar.gz
Revision History
----------------------
Version 1.0b (09-Apr-2019)
- initial beta version
Dependencies
----------------------
* Default Mod (required)
https://github.com/minetest/minetest_game/default
* ActiveFormspecs Mod (required)
https://bitbucket.org/sorcerykid/formspecs
Compatability
----------------------
Minetest 0.4.15+ required
Installation
----------------------
1) Unzip the archive into the mods directory of your game
2) Rename the protector-master directory to "protector"
Source Code License
----------------------
The MIT License (MIT)
Copyright (c) 2019, Leslie Krause
Copyright (c) 2016, TenPlus1
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
For more details:
https://opensource.org/licenses/MIT
Multimedia License (textures, sounds, and models)
----------------------------------------------------------
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
/textures/protector_display.png
by TenPlus1
modified by maikerumine
/textures/protector_logo.png
by TenPlus1
/textures/protector_mask.png
by sorcerykid
/textures/protector_mask_saved.png
by sorcerykid
/textures/protector_side1.png (Originally WTFPL)
by Zeg9
/textures/protector_side2.png (Originally WTFPL)
by by AndrejIT
/textures/protector_top1.png (Originally WTFPL)
by Hugo Locurcio
/textures/protector_top2.png (Originally WTFPL)
by AndrejIT
/textures/protector_wand.png (Unspecified License)
by Anonymous_moose
http://forum.minetest.net/viewtopic.php?f=95&t=12703
You are free to:
Share — copy and redistribute the material in any medium or format.
Adapt — remix, transform, and build upon the material for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and
indicate if changes were made. You may do so in any reasonable manner, but not in any way
that suggests the licensor endorses you or your use.
No additional restrictions — You may not apply legal terms or technological measures that
legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public
domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary
for your intended use. For example, other rights such as publicity, privacy, or moral
rights may limit how you use the material.
For more details:
http://creativecommons.org/licenses/by-sa/3.0/

2
depends.txt Normal file
View File

@ -0,0 +1,2 @@
default
formspecs

2
description.txt Normal file
View File

@ -0,0 +1,2 @@
Protector Redux offers an efficient and flexible node-based protection scheme for players
on survival and creative servers.

920
init.lua Normal file
View File

@ -0,0 +1,920 @@
--------------------------------------------------------
-- 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", "" },
}
} )

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = protector
title = Protector Redux
author = sorcerykid
license = MIT

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

BIN
textures/protector_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

BIN
textures/protector_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

BIN
textures/protector_top1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

BIN
textures/protector_top2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

BIN
textures/protector_wand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B