From df4356d9781d9b40bc86482c7fbcb82cacbefa80 Mon Sep 17 00:00:00 2001 From: Leslie Krause Date: Tue, 25 Dec 2018 07:34:00 -0500 Subject: [PATCH] Build 06 - added dependency for formspecs and ownership mods - introduced packages with specialized behavior - created textures for newly registered items - fixed regex during validation of recipient names - fixed erroneous checks against unowned nodes - general code refactoring and reformatting --- README.txt | 94 +++++++++++++-- config.lua | 31 ++--- depends.txt | 2 + init.lua | 201 ++++++++++++++++++++++++++++----- textures/package_sealed.png | Bin 0 -> 295 bytes textures/package_sealed2.png | Bin 0 -> 231 bytes textures/package_unsealed.png | Bin 0 -> 317 bytes textures/package_unsealed2.png | Bin 0 -> 246 bytes textures/package_viewer.png | Bin 0 -> 3834 bytes 9 files changed, 275 insertions(+), 53 deletions(-) create mode 100644 textures/package_sealed.png create mode 100644 textures/package_sealed2.png create mode 100644 textures/package_unsealed.png create mode 100644 textures/package_unsealed2.png create mode 100644 textures/package_viewer.png diff --git a/README.txt b/README.txt index babee53..c8bba47 100644 --- a/README.txt +++ b/README.txt @@ -1,15 +1,97 @@ -========================================================== -Giftbox Mod v2.1 by sorcerykid +Giftbox Mod v2.2 +By Leslie E. Krause https://forum.minetest.net/viewtopic.php?f=9&t=19133 +Repository +---------------------- -License of source code +Browse source code: + https://bitbucket.org/sorcerykid/giftbox + +Download archive: + https://bitbucket.org/sorcerykid/giftbox/get/master.zip + https://bitbucket.org/sorcerykid/giftbox/get/master.tar.gz + +Revision History +---------------------- + +Version 1.0b (12-Jan-2018) + - initial version within doors mod + +Version 2.0 (31-Aug-2018) + - separated common routines into standalone mod + - included support files for public release + +Version 1.0a (Build 01) + - initial build + +Version 1.1a (Build 02) + - moved configuration settings into separate file + - made infotext of admin giftbox configurable + - made background of admin giftbox configurable + - changed debug log messages into tokenized strings + +Version 2.0b (Build 03) + - merged colored giftboxes from mt_seasons mod + - added aliases for original colored giftboxes + - improved end-user customizations of giftbox + +Version 2.0 (Build 04) + - renamed admin giftbox to "present" for distinction + - created textures for newly registered nodes + - renamed associated texture files of present + - replaced owner meta for present with sender instead + - added protection check to inventory take of present + - disabled formspec for recipient of empty present + - added sender/receiver checks when digging giftbox + - made default infotext of giftbox configurable + - changed owner of giftbox to placer for simplicity + - set default receiver of giftbox during placement + - switched all prior references of owner to receiver + - registered alias for original admin giftbox + +Version 2.1 (Build 05) + - escaped giftbox message in formspec string + - improved validation of giftbox message input + +Version 2.2 (Build 06) + - added dependency for formspecs and ownership mods + - introduced packages with specialized behavior + - created textures for newly registered items + - fixed regex during validation of recipient names + - fixed erroneous checks against unowned nodes + - general code refactoring and reformatting + +Dependencies +---------------------- + +Default Mod (required) + https://github.com/minetest/minetest_game/default + +ActiveFormspecs Mod (required) + https://bitbucket.org/sorcerykid/formspecs + +Basic Ownership Mod (required) + https://bitbucket.org/sorcerykid/ownership + +Compatability +---------------------- + +Minetest 0.4.15+ required + +Installation +---------------------- + + 1) Unzip the archive into the mods directory of your game + 2) Rename the giftbox-master directory to "giftbox" + +Source Code License ---------------------------------------------------------- GNU Lesser General Public License v3 (LGPL-3.0) -Copyright (c) 2016-2017, Leslie E. Krause +Copyright (c) 2016-2018, Leslie E. Krause Original source code by maikerumine: https://github.com/maikerumine/just_test_tribute/blob/master/mods/mt_seasons/nodes.lua @@ -24,9 +106,7 @@ See the GNU Lesser General Public License for more details. http://www.gnu.org/licenses/lgpl-2.1.html - - -License of media (textures, sounds, and models) +Multimedia License (textures, sounds, and models) ---------------------------------------------------------- Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) diff --git a/config.lua b/config.lua index 2a1f1b2..04e150d 100644 --- a/config.lua +++ b/config.lua @@ -2,6 +2,7 @@ giftbox.present_infotext = "Christmas Present" giftbox.present_greeting = "present_greeting.png" +giftbox.package_viewer = "package_viewer.png" giftbox.present_items = { "default:torch 60", @@ -35,8 +36,9 @@ giftbox.present_items = { "default:meselamp 10", } -giftbox.giftbox_message_length_min = 5 -giftbox.giftbox_message_length_max = 150 +giftbox.parcel_public_description = "Parcel" +giftbox.parcel_private_description = "Parcel for %s" + giftbox.giftbox_public_infotext1 = "Gift Box" giftbox.giftbox_public_infotext2 = "'%s'" giftbox.giftbox_private_infotext1 = "Gift Box for %s" @@ -44,27 +46,16 @@ giftbox.giftbox_private_infotext2 = "Dear %s: '%s'" giftbox.giftbox_drops = { -- digging gift box allows for a single drop of items with a given a rarity - { items = { "default:mese" }, rarity = 75 }, - { items = { "protector:protect" }, rarity = 75 }, + { items = { "default:sword_diamond" }, rarity = 50 }, + { items = { "default:sword_bronze" }, rarity = 25 }, - { items = { "default:diamondblock" }, rarity = 50 }, - { items = { "default:goldblock" }, rarity = 50 }, + { items = { "default:pick_diamond" }, rarity = 50 }, + { items = { "default:pick_bronze" }, rarity = 25 }, - { items = { "default:coalblock 10" }, rarity = 25 }, - { items = { "default:obsidian 20" }, rarity = 25 }, - - { items = { "default:sword_diamond" }, rarity = 20 }, - { items = { "default:sword_bronze" }, rarity = 10 }, - - { items = { "default:pick_diamond" }, rarity = 20 }, - { items = { "default:pick_bronze" }, rarity = 10 }, - - { items = { "default:gold_lump 10" }, rarity = 5 }, - { items = { "default:coal_lump 20" }, rarity = 5 }, - - { items = { "default:orange 20" }, rarity = 5 }, - { items = { "default:apple 30" }, rarity = 5 }, + { items = { "default:gold_lump 5" }, rarity = 10 }, + { items = { "default:coal_lump 10" }, rarity = 10 }, -- default drop must be placed last and have rarity of 0 to avoid empty drops { items = { "farming:gingerbread_cookie 5", "farming:candycane 10" }, rarity = 0 }, } + diff --git a/depends.txt b/depends.txt index 4ad96d5..3664608 100644 --- a/depends.txt +++ b/depends.txt @@ -1 +1,3 @@ default +formspecs +ownership diff --git a/init.lua b/init.lua index 0939318..4de4cd6 100644 --- a/init.lua +++ b/init.lua @@ -1,10 +1,10 @@ -------------------------------------------------------- --- Minetest :: Giftbox Mod v2.1 (giftbox) +-- Minetest :: Giftbox Mod v2.2 (giftbox) -- -- See README.txt for licensing and other information. --- Copyright (c) 2016-2017, Leslie Ellen Krause +-- Copyright (c) 2016-2018, Leslie E. Krause -- --- ./games/just_test_tribute/mods/giftbox/init.lua +-- ./games/minetest_game/mods/giftbox/init.lua -------------------------------------------------------- giftbox = { } @@ -30,18 +30,18 @@ local box_colors = { "black", "blue", "cyan", "green", "magenta", "red", "white" minetest.register_node( "giftbox:present", { description = "Gift Box", - tiles = { "present_top.png", "present_bottom.png", "present_side.png", - "present_side.png", "present_side.png", "present_side.png" }, - is_ground_content = false, - groups = { choppy = 2, oddly_breakable_by_hand = 2 }, - sounds = default.node_sound_wood_defaults( ), + tiles = { "present_top.png", "present_bottom.png", "present_side.png", + "present_side.png", "present_side.png", "present_side.png" }, + is_ground_content = false, + groups = { choppy = 2, oddly_breakable_by_hand = 2 }, + sounds = default.node_sound_wood_defaults( ), drop = { }, - after_place_node = function( pos, player ) + after_place_node = function ( pos, player ) minetest.get_meta( pos ):set_string( "infotext", giftbox.present_infotext .. " (placed by " .. player:get_player_name( ) .. ")" ) end, - on_construct = function ( pos ) + on_construct = function ( pos ) local meta = minetest.get_meta( pos ) meta:get_inventory( ):set_size( "main", 1 ) meta:set_string( "oldtime", os.time( ) ) @@ -50,7 +50,7 @@ minetest.register_node( "giftbox:present", { can_dig = function ( pos, player ) return not minetest.is_protected( pos, player:get_player_name( ) ) and default.is_empty( pos ) end, - allow_metadata_inventory_take = function( pos, listname, index, stack, player ) + allow_metadata_inventory_take = function ( pos, listname, index, stack, player ) -- only allow receiver to take item and remove node -- of course, placer can already bypass protection @@ -59,16 +59,16 @@ minetest.register_node( "giftbox:present", { end return stack:get_count( ) end, - allow_metadata_inventory_put = function( pos, listname, index, stack, player ) + allow_metadata_inventory_put = function ( pos, listname, index, stack, player ) return 0 end, - on_metadata_inventory_take = function( pos, listname, index, stack, player ) + on_metadata_inventory_take = function ( pos, listname, index, stack, player ) if default.is_empty( pos ) then minetest.remove_node( pos ) end minetest.log( "action", string.format( default.STATUS_CONTAINER_GET, player:get_player_name( ), "giftbox", minetest.pos_to_string( pos ) ) ) end, - on_open = function( pos, clicker ) + on_open = function ( pos, clicker ) local pname = clicker:get_player_name( ) local spos = pos.x .. "," .. pos.y .. "," .. pos.z @@ -83,10 +83,10 @@ minetest.register_node( "giftbox:present", { "list[nodemeta:%s;main;6,0.5;1,1;]" slot = 0 - for _, name in ipairs( giftbox.present_items ) do + for _, name in ipairs( giftbox.present_items ) do formspec = formspec .. "item_image_button[ " .. ( slot % 8 ) .. "," .. ( math.floor ( slot / 8 ) + 2 ) .. ";1,1;" .. name .. ";add " .. name .. ";]" slot = slot + 1 - end + end return string.format( formspec, spos ) elseif not minetest.is_protected( pos, pname ) and not default.is_empty( pos ) then -- only show formspec if placer remembered to select item @@ -100,11 +100,11 @@ minetest.register_node( "giftbox:present", { default.gui_slots .. "list[nodemeta:%s;main;7,4;1,1;]" .. "list[current_player;main;1,5.5;8,1;]" .. - default.get_hotbar_bg(1,5.5) + default.get_hotbar_bg( 1, 5.5 ) return string.format( formspec, spos ) end end, - on_close = function( pos, player, fields ) + on_close = function ( pos, player, fields ) local fname, item fname = next( fields, nil ) -- use next since we only care about the name of a single button @@ -116,7 +116,156 @@ minetest.register_node( "giftbox:present", { end end end, -}) +} ) + +minetest.register_craftitem( "giftbox:package_unsealed", { + description = "Package (To seal, point to an object and use)", + inventory_image = "package_unsealed.png", + wield_image = "package_unsealed2.png", + groups = { flammable = 3 }, + on_use = function( cur_stack, player, pointed_thing ) + if pointed_thing.type == "object" and not pointed_thing.ref:is_player( ) then + local player_name = player:get_player_name( ) + local player_inv = player:get_inventory( ) + + -- ignore objects that are not builtin-item (sanity check) + local entity = pointed_thing.ref:get_luaentity( ) + if entity.name ~= "__builtin:item" then return end + + local item_name = ItemStack( entity.itemstring ):get_name( ) + if item_name == "giftbox:package_unsealed" or item_name == "giftbox:package_sealed" then + minetest.chat_send_player( player_name, "This item cannot be stored inside a package." ) + return + end + + cur_stack:take_item( ) + pointed_thing.ref:remove( ) + + -- produce a sealed package containing pointed_thing + local new_stack = ItemStack( "giftbox:package_sealed" ) + local meta = new_stack:get_meta( ) + + meta:set_string( "description", giftbox.parcel_public_description .. + " (from " .. player_name .. " on " .. os.date( "%x" ) .. ")" ) + meta:set_string( "timestamp", os.time( ) ) + meta:set_string( "receiver", default.OWNER_NOBODY ) + meta:set_string( "owner", player_name ) + meta:set_string( "is_anonymous", "false" ) + meta:set_string( "itemstring", entity.itemstring ) + + if 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( "giftbox:package_sealed", { + inventory_image = "package_sealed.png", + wield_image = "package_sealed2.png", + groups = { flammable = 3, not_in_creative_inventory = 1 }, + on_use = function( itemstack, player, pointed_thing ) + local player_name = player:get_player_name( ) + local meta = itemstack:get_meta( ) + local receiver = meta:get_string( "receiver" ) + local owner = meta:get_string( "owner" ) + + if owner == player_name or minetest.check_player_privs( player_name, { give = true } ) then + local formspec = + "size[8,3.5]" .. + default.gui_bg .. + default.gui_bg_img .. + "checkbox[4.8,1.8;is_anonymous;Anonymous Sender;" .. meta:get_string( "is_anonymous" ) .. "]" .. + "label[0.1,2.0;Recipient:]" .. + "field[1.8,2.4;3.0,0.25;receiver;;" .. receiver .. "]" .. + "label[2.9,0.0;Package Contents:]" .. + "image[3.5,0.6;1,1;gui_furnace_arrow_bg.png^[transformR270]" .. + "item_image_button[2.5,0.6;1,1;giftbox:package_sealed;restore;]" .. + "item_image_button[4.5,0.6;1,1;" .. ItemStack( meta:get_string( "itemstring" ) ):get_name( ) .. ";revert;]" .. + "label[0.1,2.9;Stored on " .. os.date( "%x %X", tonumber( meta:get_string( "timestamp" ) ) ) .. "]" .. + "button_exit[6.0,3.0;2,0.3;save;Save]" + + minetest.create_form( nil, player_name, formspec, function ( _, player, fields ) + + if fields.is_anonymous then + meta:set_string( "is_anonymous", fields.is_anonymous ) + + elseif fields.revert then + local itemstring = meta:get_string( "itemstring" ) + player:set_wielded_item( ItemStack( itemstring ) ) + minetest.destroy_form( player_name ) + + elseif fields.restore then + player:set_wielded_item( "giftbox:package_unsealed" ) + minetest.destroy_form( player_name ) + + elseif fields.save and fields.receiver then + if fields.receiver == owner then + minetest.chat_send_player( owner, "You cannot send a parcel to yourself." ) + return + elseif fields.receiver ~= default.OWNER_NOBODY and not string.find( fields.receiver, "^[-_A-Za-z0-9]+$" ) then + minetest.chat_send_player( owner, "The specified recipient is invalid." ) + return + end + + -- public vs. private letters and parcels + local description = fields.receiver == default.OWNER_NOBODY and + giftbox.parcel_public_description or + string.format( giftbox.parcel_private_description, fields.receiver ) + + if meta:get_string( "is_anonymous" ) == "false" then + description = description .. " (from " .. owner .. " on " .. os.date( "%x", tonumber( meta:get_string( "timestamp" ) ) ) .. ")" + end + + meta:set_string( "description", description ) + meta:set_string( "receiver", fields.receiver ) + + player:set_wielded_item( itemstack ) + end + end ) + + elseif receiver == player_name or receiver == default.OWNER_NOBODY then + local formspec = + "size[9,7]" .. + default.gui_bg .. + default.gui_bg_img .. + "background[0,0;9,6;" .. giftbox.package_viewer .. "]" .. + "label[0.5,0.5;" .. minetest.colorize( "#000000", os.date( "%x %X", tonumber( meta:get_string( "timestamp" ) ) ) ) .. "]" .. + "button_exit[3.0,6.5;3.0,0.3;open;Open Package]" + + if meta:get_string( "is_anonymous" ) == "false" then + formspec = formspec .. "label[3.5,2.5;" .. minetest.colorize( "#000000", owner ) .. "]" + end + if receiver ~= default.OWNER_NOBODY then + formspec = formspec .. "label[3.5,3.0;" .. minetest.colorize( "#000000", receiver ) .. "]" + end + + minetest.create_form( nil, player_name, formspec, function ( _, player, fields ) + if fields.open then + local itemstring = meta:get_string( "itemstring" ) + player:set_wielded_item( ItemStack( itemstring ) ) + -- TODO: record deliveries of letters and parcels in debug log + end + end ) + else + minetest.chat_send_player( player_name, "Access denied. This parcel does not belong to you." ) + end + + return itemstack + end +} ) + +minetest.register_craft( { + output = "giftbox:package_opened", + recipe = { + { "default:paper", "default:paper", "default:paper" }, + { "default:paper", "", "default:paper" }, + { "default:paper", "default:paper", "default:paper" }, + } +} ) for i, color in ipairs( box_colors ) do minetest.register_node( "giftbox:giftbox_" .. color, { @@ -142,7 +291,7 @@ for i, color in ipairs( box_colors ) do drop = { max_items = 1, items = giftbox.giftbox_drops }, - on_dig = function( pos, node, player ) + on_dig = function ( pos, node, player ) local digger = player:get_player_name( ) local receiver = minetest.get_meta( pos ):get_string( "receiver" ) -- local is_protected = minetest.get_meta( pos ):get_string( "is_protected" ) == "true" @@ -161,25 +310,25 @@ for i, color in ipairs( box_colors ) do minetest.remove_node( pos ) end end, - after_place_node = function( pos, player ) + after_place_node = function ( pos, player ) local placer = player:get_player_name( ) or "singleplayer" local meta = minetest.get_meta( pos ) default.set_owner( pos, placer ) - meta:set_string( "receiver", "" ) + meta:set_string( "receiver", default.OWNER_NOBODY ) meta:set_string( "is_anonymous", "false" ) -- initial item string: Gift Box (placed by sorcerykid) meta:set_string( "infotext", giftbox.giftbox_public_infotext1 .. " (from " .. placer .. ")" ) end, - on_open = function( pos, player, fields ) + on_open = function ( pos, player, fields ) local meta = minetest.get_meta( pos ) local formspec = "size[8,3]" .. default.gui_bg .. default.gui_bg_img .. "button_exit[6,2.5;2,0.3;save;Save]" .. - "checkbox[4.5,1.3;is_anonymous;Anonymous Gift;" .. meta:get_string( "is_anonymous" ) .. "]" .. + "checkbox[4.5,1.3;is_anonymous;Anonymous Sender;" .. meta:get_string( "is_anonymous" ) .. "]" .. "label[0.1,0;Personalize your holiday greeting (or leave blank for the default):]" .. "field[0.4,1;7.8,0.25;message;;" .. minetest.formspec_escape( meta:get_string( "message" ) ) .. "]" .. "label[0.1,1.5;Recipient:]" .. @@ -190,7 +339,7 @@ for i, color in ipairs( box_colors ) do return formspec end end, - on_close = function( pos, player, fields ) + on_close = function ( pos, player, fields ) local owner = player:get_player_name( ) local meta = minetest.get_meta( pos ) @@ -214,7 +363,7 @@ for i, color in ipairs( box_colors ) do elseif fields.receiver == owner then minetest.chat_send_player( owner, "You cannot give a gift to yourself." ) return - elseif fields.receiver ~= "" and not string.find( fields.receiver, "^[_A-Za-z0-9]+$" ) then + elseif fields.receiver ~= default.OWNER_NOBODY and not string.find( fields.receiver, "^[-_A-Za-z0-9]+$" ) then minetest.chat_send_player( owner, "The specified recipient is invalid." ) return end diff --git a/textures/package_sealed.png b/textures/package_sealed.png new file mode 100644 index 0000000000000000000000000000000000000000..67a913835ab47323d9649be5f190d91cf1ecdfd3 GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvg8-ip*E2KIfXta`#{bXE{P*X~ z8D*_=p^;Y`n}C8?fqjjt7f?c=B*-uLKf`}8fGM0R5}gfXPxEwf4AD40_u@g`Lk2vo z7vja=P2dc=%%~N%;>gbE5~gqJ-^!<$39+Bh{BOFSUHbdG2lXD8C4;8DbP0l+XkKMXGGa literal 0 HcmV?d00001 diff --git a/textures/package_sealed2.png b/textures/package_sealed2.png new file mode 100644 index 0000000000000000000000000000000000000000..0a2cd1684149544c6d491417af975691e7a4640a GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvg8-ip*A?{JMe0uxL zvSNp`#+KW*Z3c>91#8+K!Z(W-Tvdz=g&t;ucLK6UZ-$b+k literal 0 HcmV?d00001 diff --git a/textures/package_unsealed.png b/textures/package_unsealed.png new file mode 100644 index 0000000000000000000000000000000000000000..8fae75cc377fedbe302a788d0632aba983f0e0c5 GIT binary patch literal 317 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