From 6f0d99680255d3c773420c26f4604b5457b531f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20P=C3=A9rez-Cerezo?= Date: Sun, 11 Oct 2020 09:59:55 +0200 Subject: Squashed 'apartment/' content from commit 3a79693 git-subtree-dir: apartment git-subtree-split: 3a79693bef3c1531dca61ede91cb31922d5f3007 --- README.md | 2 + handle_schematics.lua | 670 +++++++++++++++++++++ init.lua | 966 +++++++++++++++++++++++++++++++ schems/apartment_4x10_0_270.mts | Bin 0 -> 497 bytes schems/apartment_4x11_0_180.mts | Bin 0 -> 489 bytes schems/apartment_4x6_0_90.mts | Bin 0 -> 349 bytes textures/apartment_controls_occupied.png | Bin 0 -> 2853 bytes textures/apartment_controls_vacant.png | Bin 0 -> 2815 bytes 8 files changed, 1638 insertions(+) create mode 100644 README.md create mode 100644 handle_schematics.lua create mode 100644 init.lua create mode 100644 schems/apartment_4x10_0_270.mts create mode 100644 schems/apartment_4x11_0_180.mts create mode 100644 schems/apartment_4x6_0_90.mts create mode 100644 textures/apartment_controls_occupied.png create mode 100644 textures/apartment_controls_vacant.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..fbcf732 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ + +Still experimental. Use /giveme apartment:apartment to get a control panel. diff --git a/handle_schematics.lua b/handle_schematics.lua new file mode 100644 index 0000000..e72ed60 --- /dev/null +++ b/handle_schematics.lua @@ -0,0 +1,670 @@ + +local handle_schematics = {} + +-- node name used to indicate where the building will eventually be placed +handle_schematics.SCAFFOLDING = 'random_buildings:support'; + +handle_schematics.AUTODECAY = 'apartment:autodecay'; + +handle_schematics.ENABLE_SLOW_DECAY = false + +-- taken from https://github.com/MirceaKitsune/minetest_mods_structures/blob/master/structures_io.lua (Taokis Sructures I/O mod) +-- gets the size of a structure file +-- nodenames: contains all the node names that are used in the schematic +-- on_constr: lists all the node names for which on_construct has to be called after placement of the schematic +handle_schematics.analyze_mts_file = function( path ) + local size = { x = 0, y = 0, z = 0, version = 0 } + local version = 0; + + local file = io.open(path..'.mts', "r") + if (file == nil) then + return nil + end + + -- thanks to sfan5 for this advanced code that reads the size from schematic files + local read_s16 = function(fi) + return string.byte(fi:read(1)) * 256 + string.byte(fi:read(1)) + end + + local function get_schematic_size(f) + -- make sure those are the first 4 characters, otherwise this might be a corrupt file + if f:read(4) ~= "MTSM" then + return nil + end + -- advance 2 more characters + local version = read_s16(f); --f:read(2) + -- the next characters here are our size, read them + return read_s16(f), read_s16(f), read_s16(f), version + end + + size.x, size.y, size.z, size.version = get_schematic_size(file) + + -- read the slice probability for each y value that was introduced in version 3 + if( size.version >= 3 ) then + -- the probability is not very intresting for buildings so we just skip it + file:read( size.y ); + end + + + -- this list is not yet used for anything + local nodenames = {}; + -- this list is needed for calling on_construct after place_schematic + local on_constr = {}; + -- nodes that require after_place_node to be called + local after_place_node = {}; + + -- after that: read_s16 (2 bytes) to find out how many diffrent nodenames (node_name_count) are present in the file + local node_name_count = read_s16( file ); + + for i = 1, node_name_count do + + -- the length of the next name + local name_length = read_s16( file ); + -- the text of the next name + local name_text = file:read( name_length ); + + table.insert( nodenames, name_text ); + -- in order to get this information, the node has to be defined and loaded + if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].on_construct) then + table.insert( on_constr, name_text ); + end + -- some nodes need after_place_node to be called for initialization + if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].after_place_node) then + table.insert( after_place_node, name_text ); + end + end + + file.close(file) + + local rotated = 0; + local burried = 0; + local parts = path:split('_'); + if( parts and #parts > 2 ) then + if( parts[#parts]=="0" or parts[#parts]=="90" or parts[#parts]=="180" or parts[#parts]=="270" ) then + rotated = tonumber( parts[#parts] ); + burried = tonumber( parts[ #parts-1 ] ); + if( not( burried ) or burried>20 or burried<0) then + burried = 0; + end + end + end + return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = on_constr, after_place_node = after_place_node, rotated=rotated, burried=burried }; +end + + +-- depending on the orientation (param2) of the build chest, the start position of the building may have to be moved; +-- this function makes sure that the building will always extend to the right and in front of the build chest +handle_schematics.translate_param2_to_rotation = function( param2, mirror, start_pos, orig_max, rotated, burried ) + + local max = {x=orig_max.x, y=orig_max.y, z=orig_max.z}; + -- if the schematic has been saved in a rotated way, swapping x and z may be necessary + if( rotated==90 or rotated==270) then + max.x = orig_max.z; + max.z = orig_max.x; + end + + -- the building may have a cellar or something alike + if( burried > 0 ) then + start_pos.y = start_pos.y - burried; + end + + -- make sure the building always extends forward and to the right of the player + local rotate = 0; + if( param2 == 0 ) then rotate = 270; if( mirror==1 ) then start_pos.x = start_pos.x - max.x + max.z; end -- z gets larger + elseif( param2 == 1 ) then rotate = 0; start_pos.z = start_pos.z - max.z; -- x gets larger + elseif( param2 == 2 ) then rotate = 90; start_pos.z = start_pos.z - max.x; + if( mirror==0 ) then start_pos.x = start_pos.x - max.z; -- z gets smaller + else start_pos.x = start_pos.x - max.x; end + elseif( param2 == 3 ) then rotate = 180; start_pos.x = start_pos.x - max.x; -- x gets smaller + end + + if( param2 == 1 or param2 == 0) then + start_pos.z = start_pos.z + 1; + elseif( param2 == 1 or param2 == 2 ) then + start_pos.x = start_pos.x + 1; + end + if( param2 == 1 ) then + start_pos.x = start_pos.x + 1; + end + + rotate = rotate + rotated; + -- make sure the rotation does not reach or exceed 360 degree + if( rotate >= 360 ) then + rotate = rotate - 360; + end + -- rotate dimensions when needed + if( param2==0 or param2==2) then + local tmp = max.x; + max.x = max.z; + max.z = tmp; + end + + return { rotate=rotate, start_pos = {x=start_pos.x, y=start_pos.y, z=start_pos.z}, + end_pos = {x=(start_pos.x+max.x-1), y=(start_pos.y+max.y-1), z=(start_pos.z+max.z-1) }, + max = {x=max.x, y=max.y, z=max.z}}; +end + + + +-- call on_construct and after_place_node for nodes that require it; +-- set up steel doors in a usable way; +-- set up apartments from the apartment mod; +-- placer is the player who initialized the placement of the schematic (placer will be passed on to after_place_node etc) +handle_schematics.update_nodes = function( start_pos, end_pos, on_constr, after_place_node, placer, extra_params ) + + local p={}; + local i=0; + local v=0; + + -- call on_construct for all the nodes that require it + for i, v in ipairs( on_constr ) do + + -- there are only very few nodes which need this special treatment + local nodes = minetest.find_nodes_in_area( start_pos, end_pos, v); + + for _, p in ipairs( nodes ) do + minetest.registered_nodes[ v ].on_construct( p ); + end + end + + if( placer ) then + for i, v in ipairs( after_place_node ) do + + -- there are only very few nodes which need this special treatment + local nodes = minetest.find_nodes_in_area( start_pos, end_pos, v); + + for _, p in ipairs( nodes ) do + minetest.registered_nodes[ v ].after_place_node( p, placer, nil, nil ); + end + end + + local player_name = placer:get_player_name(); + + -- steel doors are annoying because the cannot be catched with the functions above + local doornodes = minetest.find_nodes_in_area( start_pos, end_pos, + {'doors:door_steel_b_1','doors:door_steel_b_2', + 'doors:door_steel_t_1','doors:door_steel_t_2'}); + for _, p in ipairs( doornodes ) do + local meta = minetest.get_meta( p ); + meta:set_string("doors_owner", player_name ); + meta:set_string("infotext", "Owned by "..player_name) + end + + + -- prepare apartment rental panels + local nodes = minetest.find_nodes_in_area( start_pos, end_pos, {'apartment:apartment'} ); + if( extra_params and extra_params.apartment_type and extra_params.apartment_name ) then + for _, p in ipairs(nodes ) do + local meta = minetest.get_meta( p ); + meta:set_string( 'original_owner', player_name ); + + -- lua can't count variables of this type on its own... + local nr = 1; + for _, _ in pairs( apartment.apartments ) do + nr = nr+1; + end + -- this depends on relative position and param2 of the formspec + local fields = { + quit=true, store=true, + + size_up = math.abs( end_pos.y - p.y-1), + size_down = math.abs(start_pos.y - p.y), + + norm_right = math.abs( end_pos.x - p.x-1), + norm_left = math.abs(start_pos.x - p.x), + norm_back = math.abs( end_pos.z - p.z-1), + norm_front = math.abs(start_pos.z - p.z), + + category = extra_params.apartment_type, + -- numbering them all seems best + descr = extra_params.apartment_name + }; + + -- up and down are independent of rotation + fields.size_up = math.abs( end_pos.y - p.y-1); + fields.size_down = math.abs(start_pos.y - p.y); + + local node = minetest.get_node( p ); + if( node.param2 == 0 ) then -- z gets larger + fields.size_left = fields.norm_left; fields.size_right = fields.norm_right; + fields.size_back = fields.norm_back; fields.size_front = fields.norm_front; + + elseif( node.param2 == 1 ) then -- x gets larger + fields.size_left = fields.norm_back; fields.size_right = fields.norm_front; + fields.size_back = fields.norm_right; fields.size_front = fields.norm_left; + + elseif( node.param2 == 2 ) then -- z gets smaller + fields.size_left = fields.norm_right; fields.size_right = fields.norm_left; + fields.size_back = fields.norm_front; fields.size_front = fields.norm_back; + + elseif( node.param2 == 3 ) then -- x gets smaller + fields.size_left = fields.norm_front; fields.size_right = fields.norm_back; + fields.size_back = fields.norm_left; fields.size_front = fields.norm_right; + end + + -- configure and prepare the apartment + apartment.on_receive_fields( p, nil, fields, placer); + end + end + end +end + + +-- this is lua...it doesn't contain the basic functions +handle_schematics.table_contains = function( table, value ) + local i = 1; + local v; + for i, v in ipairs( table ) do + if( v==value ) then + return true; + end + end + return false; +end + + +handle_schematics.place_schematic = function( pos, param2, path, mirror, replacement_function, replacement_param, placer, do_copies, extra_params ) + + local node = minetest.env:get_node( pos ); + if( not( node ) or not( node.param2 ) or node.name=="air") then + if( not( param2 )) then + return false; + end + node = {name="", param2 = param2 }; + end + + local building_data = handle_schematics.analyze_mts_file( path ); + if( not( building_data ) or not( building_data.size )) then + if( placer ) then + minetest.chat_send_player( placer:get_player_name(), 'Could not place schematic. Please check the filename.'); + end + return; + end + local position_data = handle_schematics.translate_param2_to_rotation( node.param2, mirror, pos, building_data.size, building_data.rotated, building_data.burried ); + + local replacements = {}; + if( replacement_function ) then + replacements = replacement_function( building_data.nodenames, replacement_param ); + elseif( replacement_param and not replacement_param.even ) then + replacements = replacement_param; + end + + + local force_place = true; + -- when building scaffolding, do not replace anything yet + if( replacement_function and replacement_function == handle_schematics.replacement_function_scaffolding ) then + force_place = false; + end + + + -- it is possible that replacements require calls to on_constr/after_place_node + -- and that the nodes that are introduced through replacements where not present in the original schematic + local all_replacements = {}; + for i, v in ipairs( replacements ) do + table.insert( all_replacements, v[2] ); + end + if( replacement_param and replacement_param.even and replacement_param.odd ) then + for i, v in ipairs( replacement_param.even ) do + table.insert( all_replacements, v[2] ); + end + for i, v in ipairs( replacement_param.odd ) do + table.insert( all_replacements, v[2] ); + end + end + for i, v in ipairs( all_replacements ) do + + if( minetest.registered_nodes[ v ] and minetest.registered_nodes[ v ].on_construct + and not(handle_schematics.table_contains( building_data.on_constr, v ))) then + table.insert( building_data.on_constr, v ); + end + -- some nodes need after_place_node to be called for initialization + if( minetest.registered_nodes[ v ] and minetest.registered_nodes[ v ].after_place_node + and not(handle_schematics.table_contains( building_data.after_place_node, v ))) then + table.insert( building_data.after_place_node, v ); + end + end + + + -- apartments need a name if they are to be configured + if( extra_params and not( extra_params.apartment_type )) then + extra_params.apartment_type = 'apartment'; + end + + -- actually place the schematic + if( not( do_copies ) or not( do_copies.h ) or not( do_copies.v )) then + minetest.place_schematic( position_data.start_pos, path..'.mts', tostring(position_data.rotate), replacements, force_place ); + + handle_schematics.update_nodes( position_data.start_pos, position_data.end_pos, + building_data.on_constr, building_data.after_place_node, placer, + extra_params ); + else + -- place multiple copies + local vector = {h=-1,v=1}; + if( node.param2 == 0 or node.param2 == 3) then --node.param2 == 1 or node.param2 == 3 ) then + vector.h = 1; + end + + -- it looks best if every second house is built out of another material + local replacements_even = replacements; + local replacements_odd = replacements; + if( replacement_param and replacement_param.even and replacement_param.odd ) then + replacements_even = replacement_param.even; + replacements_odd = replacement_param.odd; + end + + local p = {x=position_data.start_pos.x , y=position_data.start_pos.y, z=position_data.start_pos.z }; + for j=1,do_copies.v do + p.x = position_data.start_pos.x; + p.z = position_data.start_pos.z; + for i=1,do_copies.h do -- horizontal copies + + + local key = ''; + local val = {}; + local p_end = {x=(p.x+position_data.max.x), y=(p.y+position_data.max.y), z=(p.z+position_data.max.z)}; + + for key,val in pairs( apartment.apartments ) do + if( val and val.pos + and (val.pos.x >= p.x) and (val.pos.x <= p_end.x) + and (val.pos.y >= p.y) and (val.pos.y <= p_end.y) + and (val.pos.z >= p.z) and (val.pos.z <= p_end.z)) then + +-- TODO: add FAIL if the apartment is still rented + if( placer ) then + minetest.chat_send_player( placer:get_player_name(), 'Removing Apartment '..tostring( key ).. + ' (new usage for that place). Position: '..minetest.serialize( val.pos )); + end + print( 'Removing Apartment '..tostring( key )..' (new usage for that place). Position: '..minetest.serialize( val.pos )); + apartment.apartments[ key ] = nil; + end + end + -- switch replacements between houses + if( i%2==0 ) then + minetest.place_schematic( p, path..'.mts', tostring(position_data.rotate), replacements_even, force_place ); + else + minetest.place_schematic( p, path..'.mts', tostring(position_data.rotate), replacements_odd, force_place ); + end + + -- generate apartment name + if( extra_params and extra_params.apartment_type and extra_params.apartment_house_name ) then + local nr = i-1; + local apartment_name = ''; + + -- find the first free number for an apartment with this apartment_house_name + while( nr < 99 and apartment_name == '' ) do + nr = nr+1; + apartment_name = extra_params.apartment_house_name..' '..tostring(j); + if( nr < 10 ) then + apartment_name = apartment_name..'0'..tostring(nr); + elseif( nr<100 ) then + apartment_name = apartment_name..tostring(nr); + else + apartment_name = ''; + end + + -- avoid duplicates + if( apartment.apartments[ apartment_name ] ) then + apartment_name = ''; + end + end + if( apartment_name ) then + extra_params.apartment_name = apartment_name; + else + extra_params.apartment_name = nil; + extra_params.apartment_type = nil; + end + + end + -- replacements_even/replacements_odd ought to affect only DECORATIVE nodes - and none that have on_construct/after_place_node! + handle_schematics.update_nodes( p, {x=p.x+position_data.max.x, y=p.y+position_data.max.y, z=p.z+position_data.max.z}, + building_data.on_constr, building_data.after_place_node, placer, extra_params ); + + if( node.param2 == 0 or node.param2 == 2 ) then + p.x = p.x + vector.h*position_data.max.x; + else + p.z = p.z + vector.h*position_data.max.z; + end + end + p.y = p.y + vector.v*position_data.max.y; + end + + if( node.param2 == 0 or node.param2 == 2 ) then + position_data.end_pos.x = position_data.start_pos.x + vector.h*position_data.max.x*do_copies.h; + else + position_data.end_pos.z = position_data.start_pos.z + vector.h*position_data.max.z*do_copies.v; + end + position_data.end_pos.y = position_data.start_pos.y + vector.v*position_data.max.y*do_copies.v; + end + return {start_pos = position_data.start_pos, end_pos = position_data.end_pos }; +end + + + +-- replace all nodes with scaffolding ones so that the player can see where the real building will be placed +handle_schematics.replacement_function_scaffolding = function( nodenames ) + + local replacements = {}; + for _,v in ipairs( nodenames ) do + table.insert( replacements, { v, handle_schematics.SCAFFOLDING }) + end + return replacements; +end + + +-- places nodes that look like leaves at the positions where the building was; +-- those nodes will decay using an abm; +-- this gradual disappearance of the building helps to understand the player what +-- just happend (=building was removed) and where it happened +handle_schematics.replacement_function_decay = function( nodenames ) + + local replacements = {}; + for _,v in ipairs( nodenames ) do + if( handle_schematics.ENABLE_SLOW_DECAY ) then + table.insert( replacements, { v, handle_schematics.AUTODECAY }) + else + table.insert( replacements, { v, 'air' }) + end + end + return replacements; +end + + + +handle_schematics.update_apartment_spawner_formspec = function( pos ) + + local meta = minetest.get_meta( pos ); + + if( not( meta ) or not( meta:get_string('path')) or meta:get_string('path')=='') then + return 'size[9,7]'.. + 'label[2.0,-0.3;Apartment Spawner]'.. + 'label[0.5,0.5;Load schematic from file:]'.. + 'field[5.0,0.9;4.0,0.5;path;;apartment_4x11_0_180]'.. + 'label[0.5,1.5;Name for this apartment house:]'.. + 'field[5.0,1.9;4.0,0.5;apartment_house_name;;Enter house name]'.. + 'label[0.5,2.0;Category (i.e. house, shop):]'.. + 'field[5.0,2.4;4.0,0.5;apartment_type;;apartment]'.. + 'label[0.5,2.5;Horizontal copies (to the left):]'.. + 'field[5.0,2.9;0.5,0.5;h;;1]'.. + 'label[0.5,3.0;Vertical copies (upwards):]'.. + 'field[5.0,3.4;0.5,0.5;v;;1]'.. + 'label[0.5,3.5;Replace clay in 1st building:]'.. + 'field[5.0,3.9;4.0,0.5;replacement_1;;default:sandstonebrick]'.. + 'label[0.5,4.0;Replace clay in 2nd building:]'.. + 'field[5.0,4.4;4.0,0.5;replacement_2;;default:brick]'.. + 'button_exit[4,6.0;2,0.5;store;Spawn building]'.. + 'button_exit[1,6.0;1,0.5;abort;Abort]'; + end + return 'size[9,7]'.. + 'label[2.0,-0.3;Information about the spawned Apartment]'.. + 'label[0.5,0.5;The schematic was loaded from:]'.. + 'label[5.0,0.5;'..tostring( meta:get_string('path'))..']'.. + 'label[0.5,1.5;Name for this apartment house:]'.. + 'label[5.0,1.5;'..tostring( meta:get_string('apartment_house_name'))..']'.. + 'label[0.5,2.0;Category (i.e. house, shop):]'.. + 'label[5.0,2.0;'..tostring( meta:get_string('apartment_type'))..']'.. + 'label[0.5,2.5;Horizontal copies (to the left):]'.. + 'label[5.0,2.5;'..tostring( meta:get_string('h'))..']'.. + 'label[0.5,3.0;Vertical copies (upwards):]'.. + 'label[5.0,3.0;'..tostring( meta:get_string('v'))..']'.. + 'label[0.5,3.5;Replace clay in 1st building:]'.. + 'label[5.0,3.5;'..tostring( meta:get_string('replacement_1'))..']'.. + 'label[0.5,4.0;Replace clay in 2nd building:]'.. + 'label[5.0,4.0;'..tostring( meta:get_string('replacement_2'))..']'.. + 'label[0.5,4.5;This building was spawned by:]'.. + 'label[5.0,4.5;'..tostring( meta:get_string('placed_by'))..']'.. + 'button_exit[4,6.0;2,0.5;store;Remove building]'.. + 'button_exit[1,6.0;1,0.5;abort;OK]'; +end + + +handle_schematics.on_receive_fields = function(pos, formname, fields, sender) + + local meta = minetest.get_meta( pos ); + + if( not( sender )) then + return; + end + + pname = sender:get_player_name(); + if( not( minetest.check_player_privs(pname, {apartment_unrent=true}))) then + minetest.chat_send_player( pname, 'You do not have the necessary privileges.'); + return; + end + + if( meta and (not( meta:get_string('path')) or meta:get_string('path')=='') and fields.store) then + + -- check if all params have been supplied + if( not( fields.path ) + or not( fields.apartment_house_name ) or not( fields.apartment_type ) + or not( fields.h ) or not( fields.v ) + or not( fields.replacement_1 ) or not( fields.replacement_2 )) then + minetest.chat_send_player( pname, 'Please fill all fields with information.'); + return; + end + + fields.h = tonumber( fields.h ); + if( fields.h < 1 or fields.h > 20 ) then + fields.h = 1; + end + fields.h = tostring( fields.h ); + fields.v = tonumber( fields.v ); + if( fields.v < 1 or fields.v > 20 ) then + fields.v = 1; + end + fields.v = tostring( fields.v ); + + meta:set_string('path', fields.path ); + meta:set_string('apartment_house_name', fields.apartment_house_name ); + meta:set_string('apartment_type', fields.apartment_type ); + meta:set_string('h', fields.h ); + meta:set_string('v', fields.v ); + meta:set_string('replacement_1', fields.replacement_1 ); + meta:set_string('replacement_2', fields.replacement_2 ); + meta:set_string('placed_by', pname ); + + meta:set_string('formspec', handle_schematics.update_apartment_spawner_formspec( pos )); + minetest.chat_send_player( pname, 'Placing building '..tostring( fields.path )); + + local path = minetest.get_modpath("apartment")..'/schems/'..fields.path; + local mirror = 0; + local replacement_function = nil; + local replacement_param = { odd={{'default:clay',fields.replacement_1}}, + even={{'default:clay',fields.replacement_2}}}; + + local res = {}; + res = handle_schematics.place_schematic( pos, nil, path, mirror, + replacement_function, replacement_param, + sender, {h=fields.h,v=fields.v}, + { apartment_type = fields.apartment_type, apartment_house_name = fields.apartment_house_name}) + if( res and res.start_pos ) then + meta:set_string('start_pos', minetest.serialize( res.start_pos )); + meta:set_string('end_pos', minetest.serialize( res.end_pos )); + end + return; + end + -- TODO + minetest.chat_send_player( pname, 'Dig this spawner in order to remove the building.'); +end + + + +minetest.register_node("apartment:build_chest", { + description = "Apartment spawner", + tiles = {"default_chest_side.png", "default_chest_top.png^door_steel.png", "default_chest_side.png", + "default_chest_side.png", "default_chest_side.png", "default_chest_lock.png^door_steel.png"}, + paramtype2 = "facedir", + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + legacy_facedir_simple = true, + + after_place_node = function(pos, placer, itemstack) + local meta = minetest.get_meta( pos ); + meta:set_string('formspec', handle_schematics.update_apartment_spawner_formspec( pos )); + end, + + on_receive_fields = function( pos, formname, fields, sender ) + handle_schematics.on_receive_fields(pos, formname, fields, sender) + end, + + -- if the building chest is removed, remove the building as well - and place nodes looking like leaves and autodecaying in order + -- to indicate where the building has been + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta( pos ); + + if( oldmetadata and oldmetadata.fields and oldmetadata.fields.path ) then + + local replacement_function = handle_schematics.replacement_function_decay; + local replacement_param = nil; + local path = minetest.get_modpath("apartment")..'/schems/'..oldmetadata.fields.path; + + minetest.chat_send_player( digger:get_player_name(), 'Removing building '..tostring( oldmetadata.fields.path )); + handle_schematics.place_schematic( pos, oldnode.param2, path, 0, + replacement_function, replacement_param, digger, + {h=oldmetadata.fields.h,v=oldmetadata.fields.v} ) + end + end, + + -- check if digging is allowed + can_dig = function(pos,player) + + if( not( player )) then + return false; + end + local pname = player:get_player_name(); + if( not( minetest.check_player_privs(pname, {apartment_unrent=true}))) then + minetest.chat_send_player( pname, 'You do not have the apartment_unrent priv which is necessary to dig this node.'); + return false; + end + local meta = minetest.get_meta( pos ); + local old_placer = meta:get_string('placed_by'); + if( old_placer and old_placer ~= '' and old_placer ~= pname ) then + minetest.chat_send_player( pname, 'Only '..tostring( old_placer )..' can dig this node.'); + return false; + end + return true; + end, + +}) + + +if handle_schematics.ENABLE_SLOW_DECAY then + minetest.register_node( handle_schematics.AUTODECAY, { + description = "decaying building", + drawtype = "allfaces_optional", + visual_scale = 1.3, + tiles = {"default_leaves.png"}, + paramtype = "light", + waving = 1, + is_ground_content = false, + groups = {snappy=3}, + }) + + minetest.register_abm({ + nodenames = {handle_schematics.AUTODECAY}, + -- A low interval and a high inverse chance spreads the load + interval = 2, + chance = 3, + action = function(p0, node, _, _) + minetest.remove_node( p0 ); + end + }) +end diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..9cf7c5a --- /dev/null +++ b/init.lua @@ -0,0 +1,966 @@ + +--[[ + 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.3 + Autor: Sokomine + Date: 12.02.14 +--]] + +-- Changelog: +-- 15.06.14 Added abm to turn apartment:apartment into either apartment:apartment_free or apartment:apartment_occupied +-- so that it becomes visible weather an apartment is free or not +-- 15.05.14 Added diffrent panel for occupied apartments. Added textures created by VanessaE. +-- 24.02.14 Buildings can now be removed again (dig the spawn chest) +-- 25.02.14 Buildings can now be saved. Just prefix the apartment name with save_as_ +-- start_pos and end_pos of apartments are now saved (necessary for the above mentioned save function). +-- Building spawner chest is now working. +-- 22.02.14 Added code for spawning several apartments at the same time. +-- 18.02.14 Added support for further nodes (shops, travelnet, ..). +-- Cleaned up formspec that contains apartment information. +-- Introduced diffrent categories so that i.e. a shop and an apartment can be rented at the same time. +-- 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 = {} + +dofile(minetest.get_modpath("apartment")..'/handle_schematics.lua'); + +-- 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 = 'textarea[0.0,0.8;6.5,1.2;info;;'..minetest.formspec_escape( + 'It extends '.. + (meta:get_string("size_right") or '?')..' m to the right, '.. + (meta:get_string("size_left" ) or '?')..' m to the left, '.. + (meta:get_string("size_up" ) or '?')..' m up, '.. + (meta:get_string("size_down" ) or '?')..' m down,\n'.. + (meta:get_string("size_back" ) or '?')..' m in front of you and '.. + (meta:get_string("size_front") or '?')..' m behind you. '.. + 'It has been built by\n'..(original_owner or '?').. + '. Building category: '..tostring( meta:get_string('category'))..'.')..']'; + + if( original_owner ~= owner and owner ~= '' ) then + return 'size[6.5,3]'.. + 'label[2.0,-0.3;Apartment \''..minetest.formspec_escape( descr )..'\']'.. + size_txt.. + 'label[0.5,1.7;This apartment is rented by:]'.. + 'label[3.5,1.7;'..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.3,1.8;Do you want to rent this]'.. + 'label[3.0,1.8;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,0.9;2.0,0.5;descr;;'..tostring( descr )..']'.. + + 'label[0.5,0.8;Category (i.e. house, shop):]'.. + 'field[5.0,1.4;2.0,0.5;category;;apartment]'.. + + 'label[0.5,1.7;The apartment shall extend]'.. + 'label[3.4,1.7;this many blocks from here:]'.. + 'label[0.5,2.1;(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' ); + local category = meta:get_string( 'category' ); + + 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.category ) + 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( 'category', fields.category ); + + meta:set_string( 'formspec', apartment.get_formspec( pos, player )); + + apartment.rent( pos, original_owner, nil, player ); + + apartment.apartments[ fields.descr ] = { pos={x=pos.x, y=pos.y, z=pos.z}, original_owner = original_owner, owner='', category = fields.category, + start_pos = apartment.apartments[ fields.descr ].start_pos, + end_pos = apartment.apartments[ fields.descr ].end_pos }; + apartment.save_data(); + + minetest.chat_send_player( pname, 'Apartment \''..tostring( fields.descr )..'\' (category: '..tostring( fields.category )..') is ready for rental.'); + + -- this way, schematics can be created + if( minetest.check_player_privs(pname, {apartment_unrent=true}) + and string.sub( fields.descr, 1, string.len( 'save_as_' ))=='save_as_') then + + local filename = string.sub( fields.descr, string.len( 'save_as' )+2); + if( filename and filename ~= '' ) then + -- param2 needs to be translated init initial rotation as well + local node = minetest.get_node( pos ); + if( node.param2 == 0 ) then + filename = filename..'_0_90'; + elseif( node.param2 == 3 ) then + filename = filename..'_0_180'; + elseif( node.param2 == 1 ) then + filename = filename..'_0_0'; + elseif( node.param2 == 2 ) then + filename = filename..'_0_270'; + end + filename = minetest.get_modpath("apartment")..'/schems/'..filename..'.mts'; + -- really save it with probability_list and slice_prob_list both as nil + minetest.create_schematic( apartment.apartments[ fields.descr ].start_pos, + apartment.apartments[ fields.descr ].end_pos, + nil, filename, nil); + minetest.chat_send_player( pname, 'Created schematic \''..tostring( filename )..'\' for use with the apartment spawner. Saved from '.. + minetest.serialize( apartment.apartments[ fields.descr ].start_pos )..' to '.. + minetest.serialize( apartment.apartments[ fields.descr ].end_pos )..'.'); + end + end + 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 + and v.category and category and v.category == category) then + minetest.chat_send_player( pname, 'Sorry. You can only rent one apartment per category at a time. You have already '.. + 'rented apartment '..k..'.'); + return; + end + end + + if( not( apartment.rent( pos, pname, nil, player ))) 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, player ) )) 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, player ) )) 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.'); + meta:set_string( 'formspec', apartment.get_formspec( pos, player )); + end +end + + +-- actually rent the apartment (if possible); return true on success +-- the "actor" field is only for such cases in which an object is needed for update functiones (i.e. travelnet) +apartment.rent = function( pos, pname, oldmetadata, actor ) + 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; + + if( not( apartment.apartments[ original_descr ] )) then + apartment.apartments[ original_descr ] = {}; + end + apartment.apartments[ original_descr ].start_pos = {x=x1, y=y1, z=z1}; + apartment.apartments[ original_descr ].end_pos = {x=x2, y=y2, z=z2}; + + 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 + if ( not s or s == '' )then + s = original_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 == 'doors:door_steel_b_1' or n.name == 'doors:door_steel_t_1' + or n.name == 'doors:door_steel_a' or n.name == 'doors:door_steel_b' + 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 == "locked_sign:sign_wall_locked" ) then + itext = "\"\" ("..rented_by..")"; + + -- only change the one panel that controls this apartment - not any others in the way + elseif((n.name == 'apartment:apartment_free' and px==pos.x and py==pos.y and pz==pos.z) + or(n.name == 'apartment:apartment_occupied' and px==pos.x and py==pos.y and pz==pos.z)) 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 == "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..")"; + + elseif( n.name == "chests_0gb_us:shared" ) then + itext = "Shared Chest ("..rented_by..")"; + elseif( n.name == "chests_0gb_us:secret" ) then + itext = "Secret Chest ("..rented_by..")"; + elseif( n.name == "chests_0gb_us:dropbox") then + itext = "Dropbox ("..rented_by..")"; + + elseif( n.name == "itemframes:frame" ) then + itext = "Item frame ("..rented_by..")"; + elseif( n.name == "itemframes:pedestral" ) then + itext = "Pedestral frame ("..rented_by..")"; + + + -- money mod - shop and barter shop; admin shops do not change ownership + elseif( n.name == "money:shop" ) then + if( m:get_string('infotext')=="Untuned Shop" + or m:get_string('infotext')=="Detuned Shop" + or not( m:get_string('shopname' )) + or m:get_string('infotext')=="Untuned Shop (owned by "..(m:get_string('owner') or "")..")") then + itext = "Untuned Shop ("..rented_by..")"; + else + itext = "Shop \""..m:get_string('shopname').."\" ("..rented_by..")"; + end + elseif( n.name == "money:barter_shop" ) then + if( m:get_string('infotext')=="Untuned Barter Shop" + or m:get_string('infotext')=="Detuned Barter Shop" + or not( m:get_string('shopname' )) + or m:get_string('infotext')=="Untuned Barter Shop (owned by "..(m:get_string('owner') or "")..")") then + itext = "Untuned Barter Shop ("..rented_by..")"; + else + itext = "Barter Shop \""..m:get_string('shopname').."\" ("..rented_by..")"; + end + + elseif( n.name == "currency:safe") then + itext = "Safe ("..rented_by..")"; + elseif( n.name == "currency:shop") then + itext = "Exchange shop ("..rented_by..")"; + + elseif( n.name == "bitchange:bank" ) then + itext = "Bank ("..rented_by..")"; + elseif( n.name == "bitchange:moneychanger" ) then + itext = "Moneychanger ("..rented_by..")"; + elseif( n.name == "bitchange:warehouse" ) then + itext = "Warehouse ("..rented_by..")"; + elseif (n.name == "smartshop:shop") then + itext = "Shop " .. rented_by + m:set_int("creative", 0) + m:set_int("type",1) + elseif( n.name == "bitchange:shop" ) then + if( m:get_string('title') and m:get_string('title') ~= '' ) then + itext = "Exchange shop \""..( m:get_string('title')).."\" ("..rented_by..")"; + else + itext = "Exchange shop ("..rented_by..")"; + end + + elseif( n.name == "vendor:vendor" + or n.name == "vendor:depositor") then + if( pname == '' ) then + m:set_string( 'owner', original_owner ); + else + m:set_string( 'owner', pname ); + end + vendor.refresh( {x=px, y=py, z=pz}, nil); + -- everything has been set alrady + itext = ''; + + -- un-configure the travelnet + elseif( n.name == "travelnet:travelnet" + or n.name == "travelnet:elevator" + or n.name == "locked_travelnet:elevator" + or n.name == "locked_travelnet:travelnet" ) then + + local oldmetadata = { fields = { + owner = m:get_string('owner'), + station_name = m:get_string('station_name'), + station_network = m:get_string('station_network') }}; + + -- the old box has to be removed from the network + travelnet.remove_box( {x=px, y=py, z=pz}, nil, oldmetadata, actor ); + if( pname == '' ) then + m:set_string( 'owner', original_owner ); + else + m:set_string( 'owner', pname ); + end + -- prepare the box with new input formspec etc. + minetest.registered_nodes[ n.name ].after_place_node({x=px, y=py, z=pz}, actor, nil); + itext = ''; + + + 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 + + if( not( oldmetadata) ) then + if( (owner == '' or original_owner==pname) + and (node.name ~= 'apartment:apartment_free')) then + minetest.swap_node( pos, {name='apartment:apartment_free', param2 = node.param2} ); + elseif( (node.name ~= 'apartment:apartment_occupied') + and (original_owner ~= pname)) then + minetest.swap_node( pos, {name='apartment:apartment_occupied', param2 = node.param2} ); + end + end + return true; +end + + + +apartment.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 + + +apartment.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 + + +apartment.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 + + +apartment.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, digger ); + apartment.apartments[ descr ] = nil; + apartment.save_data(); + minetest.chat_send_player( digger:get_player_name(), "Removed apartment "..descr.." successfully."); + end +end + + + + +minetest.register_node("apartment:apartment_free", { + drawtype = "nodebox", + description = "apartment management panel", +--- tiles = {"default_chest_top.png^door_steel.png"}, + tiles = {"default_steel_block.png","default_steel_block.png","default_steel_block.png","default_steel_block.png", + "default_steel_block.png","apartment_controls_vacant.png","default_steel_block.png"}, + paramtype = "light", + paramtype2 = "facedir", + light_source = 14, + groups = {cracky=2}, + node_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + + } + }, + selection_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + } + }, + + on_construct = function(pos) + return apartment.on_construct( pos ); + end, + + after_place_node = function(pos, placer) + return apartment.after_place_node( 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) + return apartment.can_dig( pos, player ); + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + return apartment.after_dig_node( pos, oldnode, oldmetadata, digger ); + end, + +}) + + +-- this one is not in the creative inventory +minetest.register_node("apartment:apartment_occupied", { + drawtype = "nodebox", + description = "apartment management panel", +--- tiles = {"default_chest_top.png^door_steel.png"}, + tiles = {"default_steel_block.png","default_steel_block.png","default_steel_block.png","default_steel_block.png", + "default_steel_block.png","apartment_controls_occupied.png","default_steel_block.png"}, + paramtype = "light", + paramtype2 = "facedir", + light_source = 14, + groups = {cracky=2, not_in_creative_inventory=1 }, + node_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + + } + }, + selection_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + } + }, + + on_construct = function(pos) + return apartment.on_construct( pos ); + end, + + after_place_node = function(pos, placer) + return apartment.after_place_node( 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) + return apartment.can_dig( pos, player ); + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + return apartment.after_dig_node( pos, oldnode, oldmetadata, digger ); + 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 category; + if (not param or param == "") then + category = 'apartment' + else + category = param + 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 and v.category == category) 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 a "..category.." first."); + end + }) +end + + + +-- old version of the node - will transform into _free or _occupied +minetest.register_node("apartment:apartment", { + drawtype = "nodebox", + description = "apartment management panel (transition state)", +--- tiles = {"default_chest_top.png^door_steel.png"}, + tiles = {"default_steel_block.png","default_steel_block.png","default_steel_block.png","default_steel_block.png", + "default_steel_block.png","apartment_controls_vacant.png","default_steel_block.png"}, + paramtype = "light", + paramtype2 = "facedir", + light_source = 14, + groups = {cracky=2}, + node_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + + } + }, + selection_box = { + type = "fixed", + fixed = { + { -0.5+(1/16), -0.5+(1/16), 0.5, 0.5-(1/16), 0.5-(1/16), 0.30}, + } + }, +}) + +minetest.register_abm({ + nodenames = {"apartment:apartment"}, + interval = 60, + chance = 1, + action = function(pos, node) + + local node = minetest.get_node( pos ); + local meta = minetest.get_meta( pos ); + local owner = meta:get_string( 'owner' ); + local original_owner = meta:get_string( 'original_owner' ); + + if( owner == '' or original_owner==owner ) then + minetest.swap_node( pos, {name='apartment:apartment_free', param2 = node.param2} ); + else + minetest.swap_node( pos, {name='apartment:apartment_occupied', param2 = node.param2} ); + end + end +}) + +minetest.register_abm({ + -- handle duplicates + nodenames= {"apartment:apartment_free" }, + interval = 1, + chance = 1, + action = function(pos,node) + local meta = minetest.get_meta( pos ); + local name = meta:get_string('descr'); +-- minetest.chat_send_all(name) + if apartment.apartments[name] and apartment.apartments[name].pos and ( apartment.apartments[name].pos.x ~= pos.x + or apartment.apartments[name].pos.y ~= pos.y or apartment.apartments[name].pos.z ~= pos.z ) then + -- duplicate name + old = apartment.apartments[name] + local number = name:match('%d+$') + if number then + n = name:sub(1,-tostring(number):len()-1)..tostring(number+1) + else + n = name..' 1' + end +-- minetest.chat_send_all(n) + meta:set_string('descr', n) + meta:set_string('formspec', apartment.get_formspec(pos, "")) + if not apartment.apartments[ n ] then + apartment.apartments[ n ] = { pos={x=pos.x, y=pos.y, z=pos.z}, original_owner = old.original_owner, owner='', category = old.category, + start_pos = old.start_pos, + end_pos = old.end_pos }; + end + end + end +}) + +-- give each player an apartment upon joining the server -- +local apartment_give_player = minetest.setting_getbool("apartment_give_newplayer") or true; +if apartment_give_player then + minetest.register_on_newplayer(function(player) + for k,v in pairs( apartment.apartments ) do + if (v.owner == '' and v.category == 'apartment') then + + local meta = minetest.get_meta( v.pos ); + local node = minetest.get_node( v.pos ); + if node.name == "ignore" then -- deal with unloaded nodes. + minetest.get_voxel_manip():read_from_map(v.pos, v.pos) + node = minetest.get_node(v.pos) + end + if (node.name == 'apartment:apartment_free' and apartment.rent( v.pos, player:get_player_name(), nil, player )) then + player:moveto( v.pos, false); + meta:set_string( 'formspec', apartment.get_formspec( v.pos, player )); + minetest.chat_send_player(player:get_player_name(),"Welcome to your new apartment. You can return here by saying '/aphome'") + break + elseif node.name == 'apartment:apartment_occupied' then -- Possible case of database corruption... + apartment.apartments[k] = nil + end + end + end + end) +end +-- upon server start, read the savefile +apartment.restore_data(); diff --git a/schems/apartment_4x10_0_270.mts b/schems/apartment_4x10_0_270.mts new file mode 100644 index 0000000..3009a77 Binary files /dev/null and b/schems/apartment_4x10_0_270.mts differ diff --git a/schems/apartment_4x11_0_180.mts b/schems/apartment_4x11_0_180.mts new file mode 100644 index 0000000..07a0779 Binary files /dev/null and b/schems/apartment_4x11_0_180.mts differ diff --git a/schems/apartment_4x6_0_90.mts b/schems/apartment_4x6_0_90.mts new file mode 100644 index 0000000..2b28664 Binary files /dev/null and b/schems/apartment_4x6_0_90.mts differ diff --git a/textures/apartment_controls_occupied.png b/textures/apartment_controls_occupied.png new file mode 100644 index 0000000..432d0cc Binary files /dev/null and b/textures/apartment_controls_occupied.png differ diff --git a/textures/apartment_controls_vacant.png b/textures/apartment_controls_vacant.png new file mode 100644 index 0000000..9f781fc Binary files /dev/null and b/textures/apartment_controls_vacant.png differ -- cgit v1.2.3