safety_deposit/init.lua

261 lines
9.0 KiB
Lua

--------------------------------------------------------
-- Minetest :: Safety Deposit Mod (safety_deposit)
--
-- See README.txt for licensing and other information.
-- Copyright (c) 2020, Leslie E. Krause
--
-- ./games/minetest_game/mods/safety_deposit/init.lua
--------------------------------------------------------
local username = minetest.setting_get( "name" ) -- used as public key for encryption
-----------------------
safety_deposit = { }
safety_deposit.encrypt_metadata = function ( itemstack, data )
local raw_data = cipher.encrypt_to_base64( minetest.serialize( data ), username )
itemstack:set_metadata( raw_data )
end
safety_deposit.decrypt_metadata = function ( itemstack )
local raw_data = itemstack:get_metadata( )
if cipher.is_encrypted( raw_data ) then
return minetest.deserialize( cipher.decrypt_from_base64( raw_data, username ) )
else
return minetest.deserialize( raw_data )
end
end
-----------------------
local disabled_safes = { }
local function open_safe_viewer( pos, player_name, access_code, safe_inv )
local function get_formspec( )
local formspec =
"size[8,7]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
string.format( "list[detached:%s_safe;main;0,0;8,2;]", player_name ) ..
"button[0.0,2.3;2,0.3;get;Get Items]" ..
"button[6.0.,2.3;2,0.3;unlock;Unlock]" ..
"list[current_player;main;0,3;8,1;]" ..
"list[current_player;main;0,4.2;8,3;8]" ..
"listring[nodemeta:%s;books]"..
"listring[current_player;main]" ..
"hidden[context;true]" ..
default.get_hotbar_bg(0,3)
return formspec
end
local function on_close( fs, player, fields )
if fields.quit then
local meta = minetest.get_meta( pos )
local data = { }
for _, itemstack in ipairs( safe_inv:get_list( "main" ) ) do
table.insert( data, itemstack:to_string( ) )
end
local raw_data = cipher.encrypt_to_base64( minetest.serialize( data ), access_code )
meta:set_string( "storage", raw_data )
safe_inv:set_list( "main", { } ) -- reset player's detached inventory
minetest.sound_play( "safe_close", { pos = pos, gain = 1.0, max_hear_distance = 5, loop = false } )
elseif fields.get then
local player_inv = player:get_inventory( )
default.get_contents( safe_inv, player_inv )
elseif fields.unlock then
local meta = minetest.get_meta( pos )
if not safe_inv:is_empty( "main" ) then
minetest.sound_play( "safe_close", { pos = pos, gain = 1.0, max_hear_distance = 5, loop = false } )
minetest.chat_send_player( player_name, "This safe cannot be unlocked until it is empty." )
minetest.destroy_form( player_name, minetest.FORMSPEC_SIGSTOP )
return
end
meta:set_string( "storage", "" )
meta:set_string( "infotext", "Unlocked Safe" )
minetest.sound_play( "safe_unlock", { pos = pos, gain = 1.0, max_hear_distance = 5, loop = false } )
minetest.log_status( "%s unlocks digital safe at %s", player_name, minetest.pos_to_string( pos ) )
minetest.chat_send_player( player_name, "This safe is unlocked. Right-click to set an access code." )
minetest.destroy_form( player_name, minetest.FORMSPEC_SIGSTOP )
end
end
minetest.sound_play( "safe_open", { pos = pos, gain = 1.0, max_hear_distance = 5, loop = false } )
minetest.create_form( nil, player_name, get_formspec( ), on_close )
end
local function open_safe_signin( pos, player_name )
local digits = { }
local function get_formspec( )
local formspec =
string.format( "size[4.0,6.3]" ) ..
default.gui_bg ..
default.gui_bg_img ..
"box[0.0,1.1;3.8,4.5;#222222]" ..
"button[0.5,3.4;1.0,1.0;num7;7]" ..
"button[1.5,3.4;1.0,1.0;num8;8]" ..
"button[2.5,3.4;1.0,1.0;num9;9]" ..
"button[0.5,2.4;1.0,1.0;num4;4]" ..
"button[1.5,2.4;1.0,1.0;num5;5]" ..
"button[2.5,2.4;1.0,1.0;num6;6]" ..
"button[0.5,1.4;1.0,1.0;num1;1]" ..
"button[1.5,1.4;1.0,1.0;num2;2]" ..
"button[2.5,1.4;1.0,1.0;num3;3]" ..
"button[0.5,4.4;2.0,1.0;num0;0]" ..
"button[2.5,4.4;1.0,1.0;clear;C]" ..
"button[1.0,5.7;2.0,1.0;enter;Enter]" ..
string.format( "image[0.0,0.0;1.0,1.0;counter_%s.png]", digits[ 1 ] or "nil" ) ..
string.format( "image[1.0,0.0;1.0,1.0;counter_%s.png]", digits[ 2 ] or "nil" ) ..
string.format( "image[2.0,0.0;1.0,1.0;counter_%s.png]", digits[ 3 ] or "nil" ) ..
string.format( "image[3.0,0.0;1.0,1.0;counter_%s.png]", digits[ 4 ] or "nil" )
return formspec
end
local function on_close( form, player, fields )
if fields.quit then return end
if fields.enter then
local meta = minetest.get_meta( pos )
local access_code = table.concat( digits )
if #access_code < 4 then
minetest.sound_play( "safe_abort", { pos = pos, gain = 0.5, max_hear_distance = 5, loop = false } )
return
end
if meta:get_string( "storage" ) == "" then
-- this safe is unlocked, so set access code and create inventory
local data = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }
local raw_data = cipher.encrypt_to_base64( minetest.serialize( data ), access_code )
meta:set_string( "storage", raw_data )
meta:set_string( "infotext", "Locked Safe" )
minetest.sound_play( "safe_lock", { pos = pos, gain = 1.0, max_hear_distance = 5, loop = false } )
minetest.log_status( "%s locks digital safe at %s", player_name, minetest.pos_to_string( pos ) )
minetest.chat_send_player( player_name, "This safe is ready for use. Your access code is " .. access_code .. ". Do not forget this number!" )
minetest.destroy_form( player_name )
else
-- this safe is locked, so validate the access code
local raw_data = meta:get_string( "storage" )
if not cipher.is_encrypted( raw_data ) then
minetest.chat_send_player( player_name, "This safe cannot be opened." )
minetest.destroy_form( player_name )
return
end
local data = minetest.deserialize( cipher.decrypt_from_base64( raw_data, access_code ) )
if not data then
local safe_id = minetest.hash_node_position( pos )
disabled_safes[ safe_id ] = player_name
minetest.after( 5.0, function ( )
disabled_safes[ safe_id ] = nil
end )
minetest.sound_play( "safe_error", { pos = pos, gain = 0.5, max_hear_distance = 5, loop = false } )
minetest.log_status( "%s is denied access to digital safe at %s", player_name, minetest.pos_to_string( pos ) )
minetest.chat_send_player( player_name, "Incorrect access code." )
minetest.destroy_form( player_name )
return
end
local safe_inv = minetest.get_inventory( { type = "detached", name = player_name .. "_safe" } )
safe_inv:set_list( "main", data )
open_safe_viewer( pos, player_name, access_code, safe_inv )
end
elseif fields.clear then
digits = { }
minetest.sound_play( "safe_entry", { pos = pos, gain = 0.5, max_hear_distance = 5, loop = false } )
minetest.update_form( player_name, get_formspec( ) )
else
local field_name = next( fields, nil ) -- next, since we only care about the name of the first button
local key = string.match( field_name, "^num([0-9])$" )
if key and #digits < 4 then
table.insert( digits, key )
minetest.sound_play( "safe_entry", { pos = pos, gain = 0.5, max_hear_distance = 5, loop = false } )
minetest.update_form( player_name, get_formspec( ) )
else
minetest.sound_play( "safe_abort", { pos = pos, gain = 0.5, max_hear_distance = 5, loop = false } )
end
end
end
minetest.create_form( nil, player_name, get_formspec( ), on_close )
end
minetest.register_on_joinplayer( function( player )
local player_name = player:get_player_name( )
local safe_inv = minetest.create_detached_inventory( player_name .. "_safe", { }, player_name )
safe_inv:set_size( "main", 16 )
end )
minetest.register_node( "safety_deposit:digital_safe", {
-- https://gitlab.com/VanessaE/currency/
description = ( "Digital Safe" ),
inventory_image = "safe_front.png",
paramtype = "light",
paramtype2 = "facedir",
tiles = {
"safe_side.png",
"safe_side.png",
"safe_side.png",
"safe_side.png",
"safe_side.png",
"safe_front.png",
},
is_ground_content = false,
groups = { cracky = 1, level = 3 },
can_dig = function ( pos, player )
local meta = minetest.get_meta( pos )
return meta:get_string( "storage" ) == "" -- allow dig only if safe is empty (and thus unlocked)
end,
on_construct = function ( pos )
local meta = minetest.get_meta( pos )
meta:get_inventory( ):set_size( "main", 8 * 2 )
meta:set_string( "infotext", "Unlocked Safe" )
meta:set_string( "oldtime", os.time( ) )
end,
after_place_node = function ( pos, player )
local player_name = player:get_player_name( )
minetest.chat_send_player( player_name, "This safe is unlocked. Right-click to set an access code." )
end,
on_rightclick = function ( pos, node, clicker )
local player_name = clicker:get_player_name( )
local safe_id = minetest.hash_node_position( pos )
if disabled_safes[ safe_id ] == player_name then
minetest.chat_send_player( player_name, "This safe has been disabled. Please try again later." )
else
open_safe_signin( pos, player_name )
end
end,
} )