739 lines
30 KiB
Lua
739 lines
30 KiB
Lua
|
|
-- citybuilder:constructor is used to construct the houses at the location
|
|
-- which the player selected
|
|
|
|
|
|
-- the citybuilder:constructor node does hold metadata information;
|
|
-- return a new stack with a configured constructor;
|
|
citybuilder.constructor_get_configured_itemstack = function( building_name, owner, city_center_pos, wood, player )
|
|
-- the building has to be known
|
|
local building_data = build_chest.building[ building_name ];
|
|
if( not( building_name ) or not( building_data )) then
|
|
return;
|
|
end
|
|
|
|
-- configure the new item stack holding a configured constructor
|
|
local item_stack = ItemStack("citybuilder:constructor 1");
|
|
local stack_meta = item_stack:get_meta();
|
|
local data = stack_meta:to_table().fields;
|
|
|
|
-- fallback if no playername is set
|
|
if( not( owner ) or owner == "" ) then
|
|
owner = player:get_player_name();
|
|
end
|
|
data.building_name = building_data.scm;
|
|
data.owner = owner;
|
|
data.city_center_pos = city_center_pos;
|
|
data.wood = wood;
|
|
if( building_data.title and building_data.provides and building_data.size) then
|
|
data.description = "\""..tostring(building_data.title)..
|
|
"\" (provides "..tostring( building_data.provides )..
|
|
") L "..tostring( building_data.size.x )..
|
|
" x W "..tostring(building_data.size.z)..
|
|
" x H "..tostring(building_data.size.y)..
|
|
" building constructor";
|
|
else
|
|
data.description = "Building constructor for "..tostring( building_data.scm );
|
|
end
|
|
item_stack:get_meta():from_table({ fields = data });
|
|
return item_stack;
|
|
end
|
|
|
|
|
|
-- when digging the player gets a citybuilder:constructor_blank first; but from the
|
|
-- oldmetadata we get in after_dig_node all the necessary information can be
|
|
-- obtained and the player will get a citybuilder:constructor that is set to the
|
|
-- correct blueprint and has a nice description for mouseover
|
|
citybuilder.constructor_digged = function(pos, oldnode, oldmetadata, player)
|
|
-- unregister the building from the city data table
|
|
if( oldmetadata and pos ) then
|
|
-- no matter which city did hold that building - it gets removed
|
|
citybuilder.city_delete_building( minetest.pos_to_string( pos ));
|
|
end
|
|
|
|
if( not(pos) or not(player)
|
|
or not( oldmetadata ) or oldmetadata=="nil" or not(oldmetadata.fields)) then
|
|
return;
|
|
end
|
|
|
|
-- the digged blank constructor will be replaced with a properly configured one if possible
|
|
local player_inv = player:get_inventory();
|
|
if( not( player_inv:contains_item("main", "citybuilder:constructor_blank"))
|
|
or not( player_inv:room_for_item( "main", "citybuilder:constructor" ))) then
|
|
return;
|
|
end
|
|
|
|
-- get a configured itemstack with metadata
|
|
local item_stack = citybuilder.constructor_get_configured_itemstack(
|
|
oldmetadata.fields.building_name, oldmetadata.fields.owner, oldmetadata.fields.city_center_pos, oldmetadata.fields.wood, player );
|
|
if( not( item_stack )) then
|
|
return;
|
|
end
|
|
|
|
-- remove the unconfigured constructor which was the result of the digging action
|
|
player_inv:remove_item("main", "citybuilder:constructor_blank 1");
|
|
player_inv:add_item("main", item_stack);
|
|
end
|
|
|
|
|
|
-- helper function for citybuilder.constructor_on_place(..)
|
|
citybuilder.constructor_clear_meta = function( meta )
|
|
meta:set_string( 'owner', nil);
|
|
meta:set_string( 'building_name', nil);
|
|
meta:set_string( 'city_center_pos', nil);
|
|
meta:set_string( 'wood', nil);
|
|
meta:set_string( 'start_pos', nil);
|
|
meta:set_string( 'end_pos', nil);
|
|
meta:set_string( 'rotate', nil);
|
|
meta:set_int( 'mirror', nil);
|
|
meta:set_string( 'replacements', nil);
|
|
end
|
|
|
|
|
|
-- the metadata might not fit the stored data; set it to the stored data
|
|
citybuilder.constructor_set_meta_to_stored_data = function( pos, stored_building )
|
|
local meta = minetest.get_meta( pos );
|
|
-- set metadata according to what we have stored
|
|
meta:set_string( 'owner', citybuilder.cities[ stored_building.city_id ].owner);
|
|
meta:set_string( 'building_name', citybuilder.full_filename[ stored_building.building_name ]);
|
|
meta:set_string( 'city_center_pos', stored_building.city_id );
|
|
meta:set_string( 'wood', stored_building.wood );
|
|
meta:set_string( 'mirror', stored_building.mirror );
|
|
meta:set_string( 'rotate', stored_building.rotate );
|
|
meta:set_string( 'start_pos', minetest.serialize(stored_building.start_pos ));
|
|
meta:set_string( 'end_pos', minetest.serialize(stored_building.end_pos ));
|
|
end
|
|
|
|
|
|
-- check if the constructor can be placed here; place it if possible
|
|
citybuilder.constructor_on_place = function( itemstack, placer, pointed_thing, mode )
|
|
|
|
if( placer == nil or pointed_thing == nil) then
|
|
return itemstack;
|
|
end
|
|
local pname = placer:get_player_name();
|
|
--minetest.chat_send_player( pname, "You USED this on "..minetest.serialize( pointed_thing )..".");
|
|
|
|
if( pointed_thing.type ~= "node" ) then
|
|
return itemstack; -- no node
|
|
end
|
|
|
|
local pos = minetest.get_pointed_thing_position( pointed_thing, mode );
|
|
local node = minetest.env:get_node_or_nil( pos );
|
|
--minetest.chat_send_player( pname, " Target node: "..minetest.serialize( node ).." at pos "..minetest.serialize( pos )..".");
|
|
if( node == nil or pos == nil) then
|
|
return itemstack; -- node not yet loaded
|
|
end
|
|
|
|
-- get itemstack metadata
|
|
local item_meta = itemstack:get_meta();
|
|
local data = item_meta:to_table().fields;
|
|
|
|
if( not( data ) or not( data.building_name ) or data.building_name=="") then
|
|
citybuilder.show_error_msg( placer,
|
|
"This building constructor has not been configured yet. "..
|
|
"Please configure it by using the desk of the city administrator.");
|
|
return itemstack;
|
|
end
|
|
local full_building_name = citybuilder.full_filename[ data.building_name ];
|
|
local building_data = citybuilder.city_get_building_data( data.building_name );
|
|
if( not( building_data)) then
|
|
citybuilder.show_error_msg( placer,
|
|
"The building this constructor has been configured for is no "..
|
|
"longer available. Old building: \""..tostring( data.building_name ).."\".");
|
|
return itemstack;
|
|
end
|
|
|
|
if( data.owner ~= pname) then
|
|
citybuilder.show_error_msg( placer,
|
|
"This building constructor belongs to "..tostring( data.owner )..". "..
|
|
"You can't use it. Craft your own one!");
|
|
return itemstack;
|
|
end
|
|
|
|
if( not( data.city_center_pos ) or not( citybuilder.cities[ data.city_center_pos ])) then
|
|
citybuilder.show_error_msg( placer,
|
|
"This constructor is configured for a (no longer?) existing city at "..
|
|
tostring( data.city_center_pos )..". It cannot be used anymore.");
|
|
return itemstack;
|
|
end
|
|
|
|
-- the data structure might believe that there is a configured constructor here, but it might
|
|
-- have been removed by other means (WorldEdit etc.) in the meantime
|
|
local stored_building = citybuilder.city_get_building_at( pos );
|
|
if( stored_building) then
|
|
-- place the node with correct param2
|
|
minetest.set_node( pos, {name="citybuilder:constructor", param2=stored_building.param2});
|
|
-- set metadata accordingly
|
|
citybuilder.constructor_set_meta_to_stored_data( pos, stored_building );
|
|
-- tell the player
|
|
citybuilder.show_error_msg( placer, "The constructor here had gone missing. It has been replaced.");
|
|
-- show the formspec
|
|
local formspec = citybuilder.constructor_update( pos, placer, meta, nil, nil, nil );
|
|
minetest.show_formspec( pname, "citybuilder:constructor", formspec );
|
|
-- do not consume this constructor as we are just replacing a missing one
|
|
return itemstack;
|
|
end
|
|
|
|
|
|
local city_data = citybuilder.cities[ data.city_center_pos ];
|
|
|
|
-- buildings have to be withhin a reasonable distance of the city administration desk
|
|
local city_center_pos = citybuilder.cities[ data.city_center_pos ].pos;
|
|
if( not( city_data.start_pos )
|
|
or not( city_data.end_pos )
|
|
or not( citybuilder.pos_is_inside( pos, city_data.start_pos, city_data.end_pos ))) then
|
|
citybuilder.show_error_msg( placer,
|
|
"This location is too far away form the city center at "..
|
|
data.city_center_pos..
|
|
". Please place this constructor closer to your city administration desk.");
|
|
return itemstack;
|
|
end
|
|
|
|
|
|
-- is6d is false (4 values are sufficient)
|
|
local param2 = core.dir_to_facedir(placer:get_look_dir(), false);
|
|
-- place the node
|
|
minetest.set_node( pos, {name="citybuilder:constructor", param2=param2});
|
|
|
|
-- NOTE: We use and set metadata here even though we have not placed the node itshelf yet!
|
|
local meta = minetest.get_meta( pos );
|
|
meta:set_string( 'owner', pname);
|
|
meta:set_string( 'building_name', full_building_name );
|
|
meta:set_string( 'city_center_pos', data.city_center_pos );
|
|
meta:set_string( 'wood', data.wood );
|
|
|
|
-- this takes param2 of the node at the position pos into account (=rotation
|
|
-- of the chest/plot marker/...) and sets metadata accordingly: start_pos,
|
|
-- end_pos, rotate, mirror and replacements
|
|
local start_pos = build_chest.get_start_pos( pos, full_building_name, param2 );
|
|
if( not( start_pos ) or not( start_pos.x )) then
|
|
-- clean up the metadata since we will not place a node there
|
|
citybuilder.constructor_clear_meta( meta );
|
|
-- remove the node as well
|
|
minetest.set_node( pos, {name="air"});
|
|
citybuilder.show_error_msg( placer,
|
|
"Error: "..tostring( start_pos ));
|
|
return itemstack;
|
|
end
|
|
|
|
|
|
-- make sure there is no overlap with other buildings
|
|
local end_pos = minetest.deserialize( meta:get_string('end_pos'));
|
|
for k,v in pairs( city_data.buildings) do
|
|
if( v and v.start_pos and v.end_pos
|
|
-- no end corner of the new building can be inside that of the old one here
|
|
and( citybuilder.pos_is_inside( {x=start_pos.x, y=start_pos.y, z=start_pos.z}, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=start_pos.x, y=start_pos.y, z=end_pos.z }, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=start_pos.x, y=end_pos.y, z=start_pos.z}, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=start_pos.x, y=end_pos.y, z=end_pos.z }, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=end_pos.x, y=start_pos.y, z=start_pos.z}, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=end_pos.x, y=start_pos.y, z=end_pos.z }, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=end_pos.x, y=end_pos.y, z=start_pos.z}, v.start_pos, v.end_pos )
|
|
or citybuilder.pos_is_inside( {x=end_pos.x, y=end_pos.y, z=end_pos.z }, v.start_pos, v.end_pos )
|
|
-- no end corner of the old building can be inside the volume of the new building
|
|
or citybuilder.pos_is_inside( {x=v.start_pos.x, y=v.start_pos.y, z=v.start_pos.z}, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.start_pos.x, y=v.start_pos.y, z=v.end_pos.z }, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.start_pos.x, y=v.end_pos.y, z=v.start_pos.z}, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.start_pos.x, y=v.end_pos.y, z=v.end_pos.z }, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.end_pos.x, y=v.start_pos.y, z=v.start_pos.z}, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.end_pos.x, y=v.start_pos.y, z=v.end_pos.z }, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.end_pos.x, y=v.end_pos.y, z=v.start_pos.z}, start_pos, end_pos )
|
|
or citybuilder.pos_is_inside( {x=v.end_pos.x, y=v.end_pos.y, z=v.end_pos.z }, start_pos, end_pos ))) then
|
|
|
|
citybuilder.constructor_clear_meta( meta );
|
|
minetest.set_node( pos, {name="air"});
|
|
citybuilder.show_error_msg( placer,
|
|
"Error: Overlapping with building project at "..minetest.pos_to_string( v.pos ));
|
|
return itemstack;
|
|
end
|
|
end
|
|
|
|
-- register the building in the citybuilder.cities data structure
|
|
citybuilder.city_add_building( data.city_center_pos,
|
|
{ pos = pos, start_pos = start_pos, end_pos = end_pos, building_name = building_data.scm,
|
|
rotate = meta:get_string("rotate"), mirror = meta:get_string("mirror"), wood = data.wood, param2 = param2 });
|
|
|
|
-- prepare inventory space
|
|
local inv = meta:get_inventory();
|
|
inv:set_size("needed", 8*5);
|
|
-- consume the placed constructor
|
|
itemstack:take_item(1);
|
|
|
|
local formspec = citybuilder.constructor_update( pos, placer, meta, nil, nil, nil );
|
|
minetest.show_formspec( pname, "citybuilder:constructor", formspec );
|
|
|
|
return itemstack;
|
|
end
|
|
|
|
|
|
|
|
-- helper function
|
|
citybuilder.get_wood_replacements = function( wood, replacements )
|
|
if( not( wood ) or wood == "" or not( replacements_group['wood'].data[ wood ]) or wood == "default:wood") then
|
|
return replacements;
|
|
end
|
|
for i,v in ipairs( replacements_group['wood'].data[ "default:wood"] ) do
|
|
local new_node = replacements_group['wood'].data[ wood ][ i ];
|
|
if( v and new_node and minetest.registered_nodes[ v ] and minetest.registered_nodes[ new_node ]) then
|
|
table.insert( replacements, {v, new_node});
|
|
end
|
|
end
|
|
return replacements;
|
|
end
|
|
|
|
|
|
-- returns the new formspec
|
|
citybuilder.constructor_update = function( pos, player, meta, do_upgrade, do_downgrade, no_update )
|
|
if( not( meta ) or not( pos ) or not( player )) then
|
|
return;
|
|
end
|
|
-- refuse update of constructors in ready-to-dig-mode
|
|
local level = meta:get_int( "citybuilder_level" );
|
|
if( level < -1 ) then
|
|
return;
|
|
end
|
|
|
|
-- compare metadata with stored data - they ought to be consistent
|
|
|
|
-- get the data from the citybuilder.cities data structure
|
|
local stored_building = citybuilder.city_get_building_at( pos );
|
|
local city_center_pos = meta:get_string( 'city_center_pos');
|
|
-- the data stored in the cities datastructure is considered the valid one because
|
|
-- that data determines which upgrades are possible
|
|
if( stored_building ) then
|
|
-- make sure the metadata is up to date
|
|
citybuilder.constructor_set_meta_to_stored_data( pos, stored_building );
|
|
|
|
-- the building was lost somehow, but the city still exists; read metadata and restore city building data from it
|
|
elseif( city_center_pos and citybuilder.cities[ city_center_pos ]) then
|
|
local node = minetest.get_node( pos );
|
|
citybuilder.city_add_building( city_center_pos, -- this is the city_id
|
|
{ pos = pos,
|
|
building_name = meta:get_string("building_name"),
|
|
start_pos = minetest.deserialize( meta:get_string('start_pos')),
|
|
end_pos = minetest.deserialize( meta:get_string('end_pos')),
|
|
rotate = meta:get_string("rotate"),mirror = meta:get_string("mirror"),
|
|
wood = meta:get_string("wood"), param2 = param2 });
|
|
citybuilder.show_error_msg( player, "Adding this building back to the city. It was missing due to an error.");
|
|
stored_building = citybuilder.city_get_building_at( pos );
|
|
-- if the city is gone, show error message and abort
|
|
else
|
|
citybuilder.show_error_msg( player, "Error: The city this building belongs to does not exist.");
|
|
-- make it diggable again
|
|
meta:set_int( "citybuilder_level", -1 );
|
|
return;
|
|
end
|
|
|
|
|
|
-- update status (set dig_here indicators, place scaffolding, check how many nodes need to be digged etc.)
|
|
local building_name = citybuilder.full_filename[ stored_building.building_name ];
|
|
local building_data = citybuilder.city_get_building_data( stored_building.building_name );
|
|
local error_msg = nil;
|
|
if( not( building_name ) or building_name == "" or not( building_data )) then
|
|
error_msg = "Unkown building \""..tostring( building_name ).."\".";
|
|
elseif( no_update ) then
|
|
-- do nothing here
|
|
else
|
|
-- apply wood replacements
|
|
local replacements = citybuilder.get_wood_replacements( stored_building.wood, {} );
|
|
|
|
-- the place_building_from_file function will set these values
|
|
meta:set_int( "nodes_to_dig", -1 );
|
|
meta:set_int( "nodes_to_place", -1 );
|
|
-- prepare the inventory
|
|
local inv = meta:get_inventory();
|
|
inv:set_size("needed", 8*5);
|
|
-- actually place dig_here-indicators and special scaffolding
|
|
error_msg = handle_schematics.place_building_from_file(
|
|
stored_building.start_pos, --minetest.deserialize(meta:get_string( "start_pos")),
|
|
stored_building.end_pos, --minetest.deserialize(meta:get_string( "end_pos")),
|
|
building_name,
|
|
replacements,
|
|
stored_building.rotate, -- meta:get_string("rotate"),
|
|
building_data.axis,
|
|
nil, -- no mirror; meta:get_int("mirror"),
|
|
-- no_plotmarker, keep_ground, scaffolding_only, plotmarker_pos
|
|
1, false, true, pos );
|
|
end
|
|
|
|
-- show error message if any occoured
|
|
if( error_msg ) then
|
|
return "size[9,2]"..
|
|
"label[0,0.1;Error: "..tostring( error_msg ).."]"..
|
|
"button_exit[4.0,1.2;1,0.5;OK;OK]";
|
|
end
|
|
|
|
|
|
|
|
|
|
local city_data = citybuilder.cities[ stored_building.city_id ];
|
|
-- show main menu; provide some information about this building project
|
|
local formspec = "size[9,9]"..
|
|
"field[20,20;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string( pos ).."]"..
|
|
|
|
"button_exit[6.0,8.0;3,0.5;OK;Exit]"..
|
|
|
|
"label[1.0,1.5;This is a \""..minetest.formspec_escape( building_data.title or building_data.scm ).."\"]"..
|
|
"label[1.5,2.0;Description: "..minetest.formspec_escape( building_data.descr or " - no description available - ").."]"..
|
|
"label[1.5,2.5;Provides "..( building_data.provides or " - nothing - ")..
|
|
minetest.formspec_escape(" [Level "..tostring( building_data.level ).."]").."]"..
|
|
|
|
"button_exit[6.0,1.5;3,0.5;update;Update status]"..
|
|
|
|
"label[1.0,6.0;Belongs to settlement:]"..
|
|
"label[1.5,6.5;"..minetest.formspec_escape( city_data.city_name or "-?-").."]"..
|
|
"label[1.5,7.0;located at "..tostring( stored_building.city_id or "-?-").."]"..
|
|
"label[1.5,7.5;founded by: "..minetest.formspec_escape( city_data.owner or "-?-").."]";
|
|
|
|
local upgrade_possible_to = building_data.upgrade_to;
|
|
if( upgrade_possible_to ) then
|
|
local upgrade_data = citybuilder.city_get_building_data( upgrade_possible_to );
|
|
formspec = formspec..
|
|
"label[1.0,4.5;Next upgrade (to level "..tostring( upgrade_data.level ).."):]"..
|
|
"label[1.5,5.0;\""..minetest.formspec_escape( upgrade_data.title or building_data.scm or "-?-").."\"]"..
|
|
"label[1.5,5.5;Description: "..minetest.formspec_escape( upgrade_data.descr or " - no description available - ").."]"..
|
|
"button_exit[6.0,4.95;3.0,0.5;show_requirements;Show requirements]";
|
|
else
|
|
formspec = formspec..
|
|
"label[1.0,4.5;No upgrades available.]";
|
|
end
|
|
|
|
if( citybuilder.city_can_downgrade_building( pos )) then
|
|
if( building_data.level > 0 ) then
|
|
local downgrade_building = citybuilder.city_get_downgrade_data( pos );
|
|
if( downgrade_building ) then
|
|
formspec = formspec..
|
|
"button_exit[6.0,6.55;3.0,0.5;downgrade;Downgrade]";
|
|
end
|
|
-- else
|
|
-- formspec = formspec..
|
|
-- "button_exit[6.0,6.55;3.0,0.5;downgrade;Abandon building]";
|
|
end
|
|
end
|
|
|
|
-- store these values in the city data structure
|
|
stored_building.nodes_to_dig = meta:get_int( "nodes_to_dig" );
|
|
stored_building.nodes_to_place = meta:get_int( "nodes_to_place" );
|
|
|
|
-- TODO: show inh(abitants), worker, children, needs_worker, job (those are potential inhabitants)
|
|
-- TODO: show real inhabitants if there are any
|
|
|
|
-- set the level of the building (+1, so that we do not start from 0)
|
|
meta:set_int( "citybuilder_level", building_data.level+1 );
|
|
|
|
-- handle downgrade option
|
|
if( do_downgrade ) then
|
|
local downgrade_building = citybuilder.city_get_downgrade_data( pos );
|
|
|
|
if( not( citybuilder.city_can_downgrade_building( pos ))) then
|
|
formspec = formspec..
|
|
"label[0,0.1;Downgrade not possible. This building at this level is needed by other buildings in the city.]";
|
|
|
|
-- only the owner/founder can do downgrades
|
|
elseif( not( citybuilder.can_access_inventory( pos, player))) then
|
|
formspec = formspec..
|
|
"label[0,0.1;Only the founder of this city may downgrade buildings.]";
|
|
|
|
-- level 0 buildings do not have a downgrade; they get abandoned when downgraded
|
|
elseif( building_data.level>0 and not( downgrade_building )) then
|
|
formspec = formspec..
|
|
"label[0,0.1;Error: Building for downgrade not found.]";
|
|
|
|
elseif( building_data.level>0 ) then
|
|
meta:set_string( 'building_name', citybuilder.full_filename[ downgrade_building.scm ]);
|
|
-- store it in the city data structure
|
|
stored_building.building_name = downgrade_building.scm;
|
|
stored_building.level = downgrade_building.level;
|
|
stored_building.complete = 0;
|
|
-- store which level has been reached
|
|
if( not( stored_building.max_level_reached ) or stored_building.max_level_reached < stored_building.level ) then
|
|
stored_building.max_level_reached = stored_building.level;
|
|
end
|
|
citybuilder.save_data();
|
|
-- call the function recursively once in order to update
|
|
return citybuilder.constructor_update( pos, player, meta, nil, nil, nil );
|
|
|
|
else
|
|
formspec = formspec..
|
|
"label[0,0.1;Error: This building cannot be downgraded further. It is already level 0.]";
|
|
end
|
|
end
|
|
|
|
|
|
-- if the building is not yet finished
|
|
if( stored_building.nodes_to_dig ~= 0 or stored_building.nodes_to_place ~= 0 or not( building_data.citybuilder)) then
|
|
-- store the current status
|
|
stored_building.complete = 0;
|
|
citybuilder.save_data();
|
|
formspec = formspec..
|
|
"label[1.0,3.0;Status: Number of blocks that need to be..]"..
|
|
"label[1.5,3.5;..digged: "..stored_building.nodes_to_dig.."]"..
|
|
"label[1.5,4.0;..placed: "..stored_building.nodes_to_place.."]"..
|
|
"button_exit[6.0,2.3;3.0,0.5;preview;Preview]"..
|
|
"button_exit[6.0,3.35;3.0,0.5;remove_indicators;Remove scaffolding]"..
|
|
"button_exit[6.0,4.15;3.0,0.5;show_needed;Show materials needed]";
|
|
|
|
-- if this building has never reached a higher upgrade state: return
|
|
if( not(stored_building.max_level_reached) or stored_building.max_level_reached < stored_building.level ) then
|
|
return formspec;
|
|
end
|
|
else
|
|
-- the building has been completed
|
|
meta:set_int( "complete", 1 );
|
|
|
|
-- the current building is complete
|
|
formspec = formspec..
|
|
"label[1.0,3.0;Status:]"..
|
|
"label[1.5,3.5;Complete]";
|
|
stored_building.complete = 1;
|
|
citybuilder.save_data();
|
|
end
|
|
|
|
|
|
-- handle upgrade
|
|
if( upgrade_possible_to ) then
|
|
|
|
local upgrade_data = citybuilder.city_get_building_data( upgrade_possible_to );
|
|
local descr = upgrade_possible_to;
|
|
if( upgrade_possible_to and upgrade_data and upgrade_data.descr ) then
|
|
descr = upgrade_data.descr;
|
|
end
|
|
|
|
-- check if upgrade is allowed
|
|
if( not( citybuilder.city_can_upgrade_building( pos ))) then
|
|
return formspec;
|
|
end
|
|
|
|
|
|
if( not( do_upgrade )) then
|
|
return formspec..
|
|
"button_exit[6.0,5.75;3.0,0.5;upgrade;Upgrade now]";
|
|
end
|
|
|
|
-- only the owner/founder can do upgrades
|
|
if( not( citybuilder.can_access_inventory( pos, player))) then
|
|
return formspec..
|
|
"label[0,0.1;Only the founder of this city may upgrade buildings.]";
|
|
end
|
|
|
|
meta:set_string( 'building_name', citybuilder.full_filename[ upgrade_possible_to ]);
|
|
-- store it in the city data structure
|
|
stored_building.building_name = upgrade_possible_to;
|
|
stored_building.level = building_data.level+1;
|
|
stored_building.complete = 1;
|
|
citybuilder.save_data();
|
|
-- call the function recursively once in order to update
|
|
return citybuilder.constructor_update( pos, player, meta, nil, nil, nil );
|
|
|
|
end
|
|
return formspec..
|
|
"label[0,0.1;Congratulations! The highest upgrade has been reached.]";
|
|
end
|
|
|
|
|
|
|
|
-- list requirements of an upgrade
|
|
citybuilder.constructor_show_requirements = function( pos )
|
|
local formspec = "size[9,7]"..
|
|
"button[7,0;2,0.5;end_preview;Back]"..
|
|
"field[20,20;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string( pos ).."]"..
|
|
"label[0,0.0;In order to upgrade to the next level, you will need at]"..
|
|
"label[0,0.4;least one building at the required level. Requirements:]"..
|
|
"tablecolumns["..
|
|
"text,align=center;".. -- OK or MISSING
|
|
"text,align=left;".. -- Level X requires
|
|
"text,align=center]".. -- Current Level: Y
|
|
'table[0.2,1.0;9.0,8.0;form_does_not_exist;';
|
|
|
|
-- collect the necessary data
|
|
local stored_building = citybuilder.city_get_building_at( pos );
|
|
|
|
if( not( stored_building )
|
|
or not( stored_building.city_id )
|
|
or not( citybuilder.cities[ stored_building.city_id ])) then
|
|
return formspec.."]";
|
|
end
|
|
|
|
-- find out what is required
|
|
local building_data = citybuilder.city_get_building_data( stored_building.building_name );
|
|
if( not( building_data ) or not( building_data.upgrade_to )) then
|
|
return formspec.."]";
|
|
end
|
|
local upgrade_data = citybuilder.city_get_building_data( building_data.upgrade_to );
|
|
|
|
-- check what the city provides against the requirements
|
|
local city_data = citybuilder.cities[ stored_building.city_id ];
|
|
local req_met = citybuilder.city_requirements_met( city_data, "" );
|
|
for k,v in pairs( upgrade_data.requires ) do
|
|
if( not( req_met[ k ])) then
|
|
formspec = formspec.."MISSING";
|
|
elseif(req_met[ k ]<v ) then
|
|
formspec = formspec.."NEEDS UPGRADE";
|
|
else
|
|
formspec = formspec.."OK";
|
|
end
|
|
formspec = formspec..",Level "..tostring(v).." "..tostring(k)..",";
|
|
if( not( req_met[ k ])) then
|
|
formspec = formspec.."- not built yet -,";
|
|
else
|
|
formspec = formspec.."Current Level: "..tostring( req_met[k] )..",";
|
|
end
|
|
end
|
|
return formspec.."]";
|
|
end
|
|
|
|
|
|
citybuilder.constructor_on_receive_fields = function(pos, formname, fields, player)
|
|
if( not( pos ) or fields.OK) then
|
|
return;
|
|
end
|
|
local meta = minetest.get_meta( pos );
|
|
|
|
-- remove "dig here" indicators and configured scaffolding nodes
|
|
if( fields.remove_indicators ) then
|
|
handle_schematics.abort_project_remove_indicators( meta );
|
|
return;
|
|
end
|
|
|
|
-- most functions are accessible to all players; only upgrades are limited to the owner
|
|
local formspec = "";
|
|
|
|
-- show preview (2d image) of how the finished building will look like
|
|
if( fields.preview and not( fields.end_preview )) then
|
|
if( fields.preview == "Preview" ) then
|
|
fields.preview = "front";
|
|
end
|
|
local building_name = meta:get_string( 'building_name' );
|
|
local replacements = citybuilder.get_wood_replacements( meta:get_string( "wood"), {} );
|
|
formspec = "size[10,10]"..
|
|
"label[3,1;Preview]"..
|
|
"button[7,1;2.5,0.5;end_preview;Back to main menu]"..
|
|
"field[20,20;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string( pos ).."]"..
|
|
build_chest.preview_image_formspec( building_name, replacements, fields.preview);
|
|
|
|
-- list which materials are needed
|
|
elseif( fields.show_needed ) then
|
|
formspec = "size[9,7]"..
|
|
"label[0,1.5;Materials needed to complete this project (click on \"Update status\" to update):]"..
|
|
"list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";needed;0,2;8,5;]"..
|
|
"field[20,20;0.1,0.1;pos2str;Pos;"..minetest.pos_to_string( pos ).."]"..
|
|
"button_exit[0.5,0.7;2,0.5;update;Update status]"..
|
|
"button[7.5,0.7;1,0.5;back;Back]";
|
|
|
|
elseif( fields.show_requirements ) then
|
|
formspec = citybuilder.constructor_show_requirements( pos );
|
|
else
|
|
formspec = citybuilder.constructor_update( pos, player, meta, fields.upgrade, fields.downgrade, not(fields.update) );
|
|
end
|
|
minetest.show_formspec( player:get_player_name(), "citybuilder:constructor", formspec );
|
|
end
|
|
|
|
|
|
minetest.register_node("citybuilder:constructor", {
|
|
description = "constructor for a house",
|
|
tiles = {"default_chest_side.png", "default_chest_top.png", "default_chest_side.png", -- TODO: a universal texture would be better
|
|
"default_chest_side.png", "default_chest_side.png", "default_chest_front.png^beds_bed.png"},
|
|
|
|
drawtype = "nodebox",
|
|
node_box = {
|
|
type = "fixed",
|
|
fixed = {
|
|
{-0.5, -0.5+3/16, 0.5-3/16, 0.5, 0.5, 0.5-1/16},
|
|
{-0.5+1/16, -0.5, 0.5-1/16, -0.5+3/16, 0.5, 0.5 },
|
|
{ 0.5-3/16, -0.5, 0.5-1/16, 0.5-1/16, 0.5, 0.5 },
|
|
},
|
|
},
|
|
|
|
paramtype2 = "facedir",
|
|
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,not_in_creative_inventory=1},
|
|
legacy_facedir_simple = true,
|
|
-- constructors are configured; stacking would not be a good idea
|
|
stack_max = 1,
|
|
-- when digging, return unconfigured constructor; but: in after_dig_node it is exchanged for a configured one
|
|
drop = "citybuilder:constructor_blank",
|
|
|
|
on_receive_fields = function( pos, formname, fields, player )
|
|
return citybuilder.constructor_on_receive_fields(pos, formname, fields, player);
|
|
end,
|
|
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
|
if from_list=="needed" or to_list=="needed" then return 0 end
|
|
if( not( citybuilder.can_access_inventory( pos, player))) then
|
|
return false;
|
|
end
|
|
return count
|
|
end,
|
|
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
|
if listname=="needed" then return 0 end
|
|
if( not( citybuilder.can_access_inventory( pos, player))) then
|
|
return false;
|
|
end
|
|
return stack:get_count()
|
|
end,
|
|
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
|
|
if listname=="needed" then return 0 end
|
|
if( not( citybuilder.can_access_inventory( pos, player))) then
|
|
return false;
|
|
end
|
|
return stack:get_count()
|
|
end,
|
|
|
|
on_place = function(itemstack, placer, pointed_thing)
|
|
return citybuilder.constructor_on_place( itemstack, placer, pointed_thing, "above" );
|
|
end,
|
|
|
|
can_dig = function(pos,player)
|
|
local meta = minetest.get_meta( pos );
|
|
local inv = meta:get_inventory();
|
|
local owner_name = meta:get_string( 'owner' );
|
|
local building_name = meta:get_string( 'building_name' );
|
|
local name = player:get_player_name();
|
|
|
|
if( not( meta ) or not( owner_name )) then
|
|
return true;
|
|
end
|
|
if( owner_name ~= name and owner_name ~= "") then
|
|
minetest.chat_send_player(name, "This building constructor belongs to "..tostring( owner_name )..". You can't take it.");
|
|
return false;
|
|
end
|
|
|
|
-- only allow aborting a project if level 0 has not been completed yet
|
|
local stored_building = citybuilder.city_get_building_at( pos );
|
|
if( stored_building
|
|
and ((stored_building.level and stored_building.level>0 )
|
|
or (stored_building.complete and stored_building.complete>0))) then
|
|
minetest.chat_send_player(name, "This building constructor has spawned a building and cannot be digged.");
|
|
return false;
|
|
end
|
|
handle_schematics.abort_project_remove_indicators( meta );
|
|
return true;
|
|
end,
|
|
|
|
-- handle formspec manually - not via meta:set_string("formspec") as it is very dynamic
|
|
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
|
citybuilder.constructor_on_receive_fields(pos, "citybuilder:constructor", {}, clicker );
|
|
return itemstack;
|
|
end,
|
|
|
|
after_dig_node = function(pos, oldnode, oldmetadata, digger)
|
|
return citybuilder.constructor_digged(pos, oldnode, oldmetadata, digger);
|
|
end,
|
|
})
|
|
|
|
|
|
-- helper item; blank constructors cannot be placed
|
|
minetest.register_craftitem("citybuilder:constructor_blank", {
|
|
description = "Blank constructor",
|
|
inventory_image = "default_paper.png^[transformFX",
|
|
groups = {},
|
|
})
|
|
|
|
minetest.register_craft({
|
|
output = "citybuilder:constructor_blank",
|
|
recipe = {
|
|
{"default:paper", "default:paper", "default:paper"},
|
|
{"default:paper", "default:paper", "default:paper"},
|
|
{"", "default:chest", "default:sign_wall"}
|
|
}
|
|
})
|