--[[ The apartment mod allows players to rent a place with locked objects in - the ownership of the locked objects is transfered to the player who rented the apartment. Copyright (C) 2014 Sokomine This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Version: 1.1 Autor: Sokomine Date: 12.02.14 --]] -- Changelog: -- 16.02.14 Removed MAX_LIGHT var and set to fixed value. -- 16.02.14 Only descriptions and ownership of known objects are changed. -- When digging the panel, the descriptions are reset. -- 14.02.14 Improved formspecs, messages and descriptions of rented and vacant items. -- Players with the apartment_unrent priv can now throw other players out of apartments. -- Apartment names have to be uniq. -- Each player can only rent one apartment at a time. -- Added /aphome command minetest.register_privilege("apartment_unrent", { description = "allows to throw players out of apartments they have rented", give_to_singleplayer = false}); apartment = {} -- will contain information about all apartments of the server in the form: -- { apartment_descr = { pos={x=0,y=0,z=0}, original_owner='', owner=''} apartment.apartments = {}; -- set to false if you do not like your players apartment.enable_aphome_command = true; -- TODO: save and restore ought to be library functions and not implemented in each individual mod! -- called whenever an apartment is added or removed apartment.save_data = function() local data = minetest.serialize( apartment.apartments ); local path = minetest.get_worldpath().."/apartment.data"; local file = io.open( path, "w" ); if( file ) then file:write( data ); file:close(); else print("[Mod apartment] Error: Savefile '"..tostring( path ).."' could not be written."); end end apartment.restore_data = function() local path = minetest.get_worldpath().."/apartment.data"; local file = io.open( path, "r" ); if( file ) then local data = file:read("*all"); apartment.apartments = minetest.deserialize( data ); file:close(); else print("[Mod apartment] Error: Savefile '"..tostring( path ).."' not found."); end end apartment.get_formspec = function( pos, placer ) local meta = minetest.get_meta(pos); local original_owner = meta:get_string( 'original_owner' ); local owner = meta:get_string( 'owner' ); local descr = meta:get_string( 'descr' ); -- misconfigured if( not( original_owner ) or original_owner == '' ) then return 'field[text;;Panel misconfigured. Please dig and place again.] '; end -- if a name has been set if( descr and descr ~= '' ) then local size_txt = 'label[0.0,0.2;It extends]'.. 'label[1.0,0.2;'..(meta:get_string("size_right"))..' m to the right,]'.. 'label[2.6,0.2;'..(meta:get_string("size_left" ))..' m to the left,]'.. 'label[4.0,0.2;'..(meta:get_string("size_up" ))..' m up,]'.. 'label[4.8,0.2;'..(meta:get_string("size_down" ))..' m down,]'.. 'label[0.0,0.5;'..(meta:get_string("size_back" ))..' m in front of you and]'.. 'label[2.3,0.5;'..(meta:get_string("size_front"))..' m behind you.]'.. 'label[3.9,0.5;It has been built by]'.. 'label[0.0,0.8;'..(original_owner or '?')..'.]'; if( original_owner ~= owner and owner ~= '' ) then return 'size[6,3]'.. 'label[2.0,-0.3;Apartment \''..minetest.formspec_escape( descr )..'\']'.. size_txt.. 'label[0.5,1.4;This apartment is rented by:]'.. 'label[3.5,1.4;'..tostring( owner )..']'.. 'button_exit[3,2.5;2,0.5;unrent;Move out]'.. 'button_exit[1,2.5;1,0.5;abort;OK]'; end return 'size[6,3]'.. 'label[2.0,-0.3;Apartment \''..minetest.formspec_escape( descr )..'\']'.. size_txt.. 'label[0.5,1.4;Do you want to rent this]'.. 'label[2.8,1.4;apartment? It\'s free!]'.. 'button_exit[3,2.5;2,0.5;rent;Yes, rent it]'.. 'button_exit[1,2.5;1,0.5;abort;No.]'; end -- defaults that fit to small appartments - change this if needed! local size_up = 2; local size_down = 1; local size_right = 1; local size_left = 2; local size_front = 1; local size_back = 7; -- show configuration formspec if( not( owner ) or owner=='' or owner==original_owner ) then return 'size[7,7]'.. 'label[2.0,-0.3;Apartment Configuration]'.. 'label[0.5,0.5;Name or number for this apartment:]'.. 'field[5.0,1.0;2.0,0.5;descr;;'..tostring( descr )..']'.. 'label[0.5,1.2;The apartment shall extend]'.. 'label[3.3,1.2;this many blocks from here:]'.. 'label[0.5,1.4;(relative to this panel)]'.. 'label[1.3,3.5;left:]' ..'field[2.0,4.0;1.0,0.5;size_left;;' ..tostring( size_left )..']'.. 'label[4.6,3.5;right]' ..'field[4.0,4.0;1.0,0.5;size_right;;'..tostring( size_right )..']'.. 'label[2.8,5.0;front]' ..'field[3.0,5.0;1.0,0.5;size_front;;'..tostring( size_front )..']'.. 'label[2.8,2.1;back:]' ..'field[3.0,3.0;1.0,0.5;size_back;;' ..tostring( size_back )..']'.. 'label[5.8,2.1;up:]' ..'field[6.0,3.0;1.0,0.5;size_up;;' ..tostring( size_up )..']'.. 'label[5.8,5.0;down]' ..'field[6.0,5.0;1.0,0.5;size_down;;' ..tostring( size_down )..']'.. 'button_exit[4,6.0;2,0.5;store;Store and offer]'.. 'button_exit[1,6.0;1,0.5;abort;Abort]'; end end apartment.on_receive_fields = function(pos, formname, fields, player) local meta = minetest.get_meta(pos); local pname = player:get_player_name(); local original_owner = meta:get_string( 'original_owner' ); local owner = meta:get_string( 'owner' ); local descr = meta:get_string( 'descr' ); if( not( fields ) or fields.abort or not( original_owner ) or original_owner=='' or not( fields.quit )) then return; elseif( not( descr ) or descr=='' ) then -- only the player who placed the panel can configure it if( not( fields.store ) or pname ~= original_owner or pname ~= owner) then if( fields.descr and fields.descr ~= '') then minetest.chat_send_player( pname, 'Error: Only the owner of this panel can configure it.'); end return; end local size_left = tonumber( fields.size_left or -1); local size_right = tonumber( fields.size_right or -1); local size_up = tonumber( fields.size_up or -1); local size_down = tonumber( fields.size_down or -1); local size_front = tonumber( fields.size_front or -1); local size_back = tonumber( fields.size_back or -1); -- have all fields been filled int? if( not(fields.store) or not(size_left ) or size_left < 0 or size_left > 10 or not(size_right ) or size_right< 0 or size_right> 10 or not(size_up ) or size_up < 0 or size_up > 10 or not(size_down ) or size_down < 0 or size_down > 10 or not(size_front ) or size_front< 0 or size_front> 10 or not(size_back ) or size_back < 0 or size_back > 10 or not(fields.descr ) or fields.descr == '') then minetest.chat_send_player( pname, 'Error: Not all fields have been filled in or the area is too large.'); return; end -- avoid duplicate names if( apartment.apartments[ fields.descr ] ) then minetest.chat_send_player( pname, 'Error: An apartment by that name exists already (name: '..fields.descr..').'.. 'Please choose a diffrent name/id.'); return; end meta:set_int( 'size_up', size_up ); meta:set_int( 'size_down', size_down ); meta:set_int( 'size_right', size_right ); meta:set_int( 'size_left', size_left ); meta:set_int( 'size_front', size_front ); meta:set_int( 'size_back', size_back ); meta:set_string( 'descr', fields.descr ); meta:set_string( 'formspec', apartment.get_formspec( pos, player )); apartment.rent( pos, original_owner, nil ); apartment.apartments[ fields.descr ] = { pos={x=pos.x, y=pos.y, z=pos.z}, original_owner = original_owner, owner='' }; apartment.save_data(); minetest.chat_send_player( pname, 'Apartment \''..tostring( fields.descr )..'\' is ready for rental.'); return; elseif( fields.rent and pname == original_owner ) then minetest.chat_send_player( pname, 'You cannot rent your own appartment. Dig the panel if you no longer want to rent it.'); return; elseif( fields.rent and owner == pname ) then minetest.chat_send_player( pname, 'You have already rented this apartment.'); return; elseif( fields.rent and owner ~= original_owner ) then minetest.chat_send_player( pname, 'Sorry, this apartment has already been rented to '..tostring( owner )..'.'); return; -- actually rent the appartment elseif( fields.rent ) then if( not( apartment.apartments[ descr ] )) then minetest.chat_send_player( pname, 'Error: This apartment is not registered. Please un-rent it and ask the original buildier '.. 'to dig and place this panel again.'); return; end -- make sure only one apartment can be rented at a time for k,v in pairs( apartment.apartments ) do if( v and v.owner and v.owner==pname ) then minetest.chat_send_player( pname, 'Sorry. You can only rent one apartment at a time. You have already '.. 'rented apartment '..k..'.'); return; end end if( not( apartment.rent( pos, pname, nil ))) then minetest.chat_send_player( pname, 'Sorry. There was an internal error. Please try again later.'); return; end minetest.chat_send_player( pname, 'You have rented apartment \''..tostring( descr )..'\'. Enjoy your stay!'); meta:set_string( 'formspec', apartment.get_formspec( pos, player )); return; elseif( fields.unrent and owner ~= original_owner and owner==pname ) then if( not( apartment.rent( pos, original_owner, nil ) )) then minetest.chat_send_player( pname, 'Something went wrong when giving back the apartment.'); return; end minetest.chat_send_player( pname, 'You have ended your rent of apartment \''..tostring( descr )..'\'. It is free for others to rent again.'); meta:set_string( 'formspec', apartment.get_formspec( pos, player )); return; -- someone else tries to throw the current owner out elseif( fields.unrent and owner ~= original_owner and owner ~= pname ) then if( not( minetest.check_player_privs(pname, {apartment_unrent=true}))) then minetest.chat_send_player( pname, 'You do not have the privilelge to throw other people out of apartments they have rented.'); return; end if( not( apartment.rent( pos, original_owner, nil ) )) then minetest.chat_send_player( pname, 'Something went wrong when giving back the apartment.'); return; end minetest.chat_send_player( pname, 'Player '..owner..' has been thrown out of the apartment. It can now be rented by another player.'); end end -- actually rent the apartment (if possible); return true on success apartment.rent = function( pos, pname, oldmetadata ) local node = minetest.env:get_node(pos); local meta = minetest.get_meta(pos); local original_owner = meta:get_string( 'original_owner' ); local owner = meta:get_string( 'owner' ); local descr = meta:get_string( 'descr' ); if( oldmetadata ) then original_owner = oldmetadata.fields[ "original_owner" ]; owner = oldmetadata.fields[ "owner" ]; descr = oldmetadata.fields[ "descr" ]; meta = {}; end if( not( node ) or not( meta ) or not( original_owner ) or not( owner ) or not( descr )) then return false; end local size_up = 0; local size_down = 0; local size_right = 0; local size_left = 0; local size_front = 0; local size_back = 0; if( not( oldmetadata )) then size_up = meta:get_int( 'size_up' ); size_down = meta:get_int( 'size_down' ); size_right = meta:get_int( 'size_right' ); size_left = meta:get_int( 'size_left' ); size_front = meta:get_int( 'size_front' ); size_back = meta:get_int( 'size_back' ); else size_up = tonumber(oldmetadata.fields[ "size_up" ]); size_down = tonumber(oldmetadata.fields[ "size_down" ]); size_right = tonumber(oldmetadata.fields[ "size_right" ]); size_left = tonumber(oldmetadata.fields[ "size_left" ]); size_front = tonumber(oldmetadata.fields[ "size_front" ]); size_back = tonumber(oldmetadata.fields[ "size_back" ]); end if( not( size_up ) or not( size_down ) or not( size_right ) or not( size_left ) or not( size_front ) or not( size_back )) then return false; end local rented_by = 'rented by '..pname; if( pname == original_owner ) then rented_by = '- vacant -'; elseif( pname == '' ) then rented_by = 'owned by '..original_owner; end -- else we might run into trouble if we use it in formspecs local original_descr = descr; descr = minetest.formspec_escape( descr ); local x1 = pos.x; local y1 = pos.y; local z1 = pos.z; local x2 = pos.x; local y2 = pos.y; local z2 = pos.z; if( oldmetadata and oldmetadata.param2 ) then node.param2 = oldmetadata.param2; end if( node.param2 == 0 ) then -- z gets larger x1 = x1 - size_left; x2 = x2 + size_right; z1 = z1 - size_front; z2 = z2 + size_back; elseif( node.param2 == 1 ) then -- x gets larger z1 = z1 - size_right; z2 = z2 + size_left; x1 = x1 - size_front; x2 = x2 + size_back; elseif( node.param2 == 2 ) then -- z gets smaller x1 = x1 - size_right; x2 = x2 + size_left; z1 = z1 - size_back; z2 = z2 + size_front; elseif( node.param2 == 3 ) then -- x gets smaller z1 = z1 - size_left; z2 = z2 + size_right; x1 = x1 - size_back; x2 = x2 + size_front; end y1 = y1 - size_down; y2 = y2 + size_up; local px = x1; local py = x1; local pz = z1; for px = x1, x2 do for py = y1, y2 do for pz = z1, z2 do local m = minetest.get_meta( {x=px, y=py, z=pz}); if( m ) then local s = m:get_string( 'owner' ); -- doors are diffrent if( not( s ) or s=='' ) then s = m:get_string( 'doors_owner' ); end -- change owner to the new player if( s and s ~= '' and (s==original_owner or s==owner)) then -- change the actual owner -- set a fitting infotext local itext = 'Object in Ap. '..descr..' ('..rented_by..')'; n = minetest.get_node( {x=px, y=py, z=pz} ); if( n.name == 'default:chest_locked' ) then if( pname == '' ) then itext = "Locked Chest (owned by "..original_owner..")"; else itext = "Locked Chest in Ap. "..descr.." ("..rented_by..")"; end elseif( n.name == 'apartment:apartment' ) then if( pname==original_owner ) then itext = "Rent apartment "..descr.." here by right-clicking this panel!"; else itext = "Apartment rental control panel for apartment "..descr.." ("..rented_by..")"; end elseif( n.name == 'doors:door_steel_b_1' or n.name == 'doors:door_steel_t_1' or n.name == 'doors:door_steel_b_2' or n.name == 'doors:door_steel_t_2' ) then if( pname=='' ) then itext = "Locked Door (owned by "..original_owner..")"; elseif( pname==original_owner ) then itext = "Apartment "..descr.." (vacant)"; else itext = "Apartment "..descr.." ("..rented_by..")"; end -- doors use another meta text m:set_string( 'doors_owner', pname ); elseif( n.name == "technic:iron_locked_chest" ) then if( pname=='' ) then itext = "Iron Locked Chest (owned by "..original_owner..")"; else itext = "Iron Locked Chest in Ap. "..descr.." (" ..rented_by..")"; end elseif( n.name == "technic:copper_locked_chest" ) then if( pname=='' ) then itext = "Copper Locked Chest (owned by "..original_owner..")"; else itext = "Copper Locked Chest in Ap. "..descr.." ("..rented_by..")"; end elseif( n.name == "technic:gold_locked_chest" ) then if( pname=='' ) then itext = "Gold Locked Chest (owned by "..original_owner..")"; else itext = "Gold Locked Chest in Ap. "..descr.." (" ..rented_by..")"; end elseif( n.name == "inbox:empty" ) then if( pname=='' ) then itext = original_owner.."'s Mailbox"; else itext = pname.."'s Mailbox"; end elseif( n.name == "locks:shared_locked_chest") then itext = "Shared locked chest ("..rented_by..")"; elseif( n.name == "locks:shared_locked_furnace" or n.name == "locks:shared_locked_furnace_active") then itext = "Shared locked furnace ("..rented_by..")"; elseif( n.name == "locks:shared_locked_sign_wall") then itext = "Shared locked sign ("..rented_by..")"; elseif( n.name == "locks:door" or n.name == "locks:door_top_1" or n.name == "locks:door_top_2" or n.name == "locks:door_bottom_1" or n.name == "locks:door_bottom_2") then itext = "Shared locked door ("..rented_by..")"; else itext = ''; end -- only set ownership of nodes the mod knows how to handle if( itext ) then m:set_string( "infotext", itext ); if( pname == '' ) then m:set_string( 'owner', original_owner ); else m:set_string( 'owner', pname ); end end end end end end end -- here, we need the original descr again if( apartment.apartments[ original_descr ] ) then if( original_owner == pname ) then apartment.apartments[ original_descr ].owner = ''; else apartment.apartments[ original_descr ].owner = pname; end apartment.save_data(); end return true; end minetest.register_node("apartment:apartment", { drawtype = "nodebox", description = "apartment management panel", tiles = {"default_chest_top.png^door_steel.png"}, paramtype = "light", paramtype2 = "facedir", light_source = 14, groups = {cracky=2}, node_box = { type = "fixed", fixed = { {-0.40, -0.4, 0.50, 0.40, 0.40, 0.30}, } }, selection_box = { type = "fixed", fixed = { {-0.40, -0.4, 0.50, 0.40, 0.40, 0.30}, } }, on_construct = function(pos) local meta = minetest.env:get_meta(pos); meta:set_string('infotext', 'Apartment Management Panel (unconfigured)'); meta:set_string('original_owner', '' ); meta:set_string('owner', '' ); meta:set_string('descr', '' ); meta:set_int( 'size_up', 0 ); meta:set_int( 'size_down', 0 ); meta:set_int( 'size_right', 0 ); meta:set_int( 'size_left', 0 ); meta:set_int( 'size_front', 0 ); meta:set_int( 'size_back', 0 ); end, after_place_node = function(pos, placer) local meta = minetest.get_meta(pos); local pname = (placer:get_player_name() or ""); meta:set_string("original_owner", pname ); meta:set_string("owner", pname ); meta:set_string('infotext', 'Apartment Management Panel (owned by '..pname..')' ); meta:set_string("formspec", apartment.get_formspec( pos, placer )); end, on_receive_fields = function( pos, formname, fields, player ) return apartment.on_receive_fields(pos, formname, fields, player); end, can_dig = function(pos,player) local meta = minetest.get_meta(pos); local owner = meta:get_string('owner'); local original_owner = meta:get_string( 'original_owner' ); local pname = player:get_player_name(); if( not( original_owner ) or original_owner == '' ) then return true; end if( original_owner ~= pname ) then minetest.chat_send_player( pname, 'Sorry. Only the original owner of this apartment control panel can dig it.'); return false; end if( original_owner ~= owner ) then minetest.chat_send_player( pname, 'The apartment is currently rented to '..tostring( owner )..'. Please end that first.'); return false; end return true; end, after_dig_node = function(pos, oldnode, oldmetadata, digger) if( not( oldmetadata ) or oldmetadata=="nil" or not(oldmetadata.fields)) then minetest.chat_send_player( digger:get_player_name(), "Error: Could not find information about the apartment panel that is to be removed."); return; end local descr = oldmetadata.fields[ "descr" ]; if( apartment.apartments[ descr ] ) then -- actually remove the apartment oldmetadata.param2 = oldnode.param2; apartment.rent( pos, '', oldmetadata ); apartment.apartments[ descr ] = nil; apartment.save_data(); minetest.chat_send_player( digger:get_player_name(), "Removed apartment "..descr.." successfully."); end end, }) if( apartment.enable_aphome_command ) then minetest.register_chatcommand("aphome", { params = "", description = "Teleports you back to the apartment you rented.", privs = {}, func = function(name, param) if( not( name )) then return; end local player = minetest.env:get_player_by_name(name); for k,v in pairs( apartment.apartments ) do -- found the apartment the player rented if( v and v.owner and v.owner==name ) then player:moveto( v.pos, false); minetest.chat_send_player(name, "Welcome back to your apartment "..k.."."); return; end end minetest.chat_send_player(name, "Please rent an apartment first."); end }) end -- upon server start, read the savefile apartment.restore_data();