From 18e0082cdbf7d5d2f6974a4ac48592f5b145f839 Mon Sep 17 00:00:00 2001 From: francisco athens Date: Mon, 20 Jul 2020 18:46:34 -0700 Subject: [PATCH] Witches have little houses! --- basic_houses.lua | 923 +++++++++++++++++++++++++++++++++++++++++++++++ depends.txt | 2 +- init.lua | 28 +- mod.conf | 2 +- nodes.lua | 68 ++++ utilities.lua | 179 ++++++++- 6 files changed, 1190 insertions(+), 12 deletions(-) create mode 100644 basic_houses.lua create mode 100644 nodes.lua diff --git a/basic_houses.lua b/basic_houses.lua new file mode 100644 index 0000000..150cc76 --- /dev/null +++ b/basic_houses.lua @@ -0,0 +1,923 @@ +--Adapted from Sokomine's basic_houses mod under GPLv3 requires +--Sokomine's handle_schematics mod (also GPLv3) + +-- Features: +-- * each house has a door and one mese lamp per floor +-- * houses can have multiple floors +-- * each house comes with a ladder for access to all floors +-- * normal saddle roofs and flat roofs supported +-- * trees, plants and snow inside the house are not cleared +-- -> the houses look abandoned (ready for players to move in) +-- * houses look acceptable but leave a lot of room for improvement +-- through their future inhabitants +-- (no windows in gable, no decoration, no cellar, no furniture, +-- no mini-house for elevator/ladder on top of skyscrapers, ...) +-- * if the saddle roof does not fit into the height volume that is +-- reserved for the house, the top of the roof is made flat +-- * some random houses receive a chest with further building material +-- for the house the chest spawned in +-- * houses made out of plasterwork nodes may receive a machine from +-- plasterwork instead of a chest +-- * can be used with the RealTest game as well +-- Technical stuff: +-- * used function from handle_schematics to mark parts of the heightmap as used +-- * glass panes, glass and obisidan glass are more common than bars +-- * windows are no longer "filled" (param2 now set to 0) +-- * doors are sourrounded by wall node and not glass panes or bars +-- (would look strange and leave gaps otherwise) +-- Known issues: +-- * cavegen may eat holes into the ground below the house +-- * houses may very seldom overlap + +witches.bh = {}; + +-- generate at max this many houses per mapchunk; +-- Note: This amount will likely only spawn if your mapgen is very flat. +-- Else you will see far less houses. +witches.bh.max_per_mapchunk = 2; + +-- how many houses shall be generated on average per mapchunk? +witches.bh.houses_wanted_per_mapchunk = 0.1; + +-- even if there would not be any house here due to amount of houses +-- generated beeing equal or larger than the amount of houses expected, +-- there is still this additional chance (in percent) that the mapchunk +-- will receive a house anyway (more randomness is good!) +witches.bh.additional_chance = 1; + +-- how many mapchunks have been generated since the server was started? +witches.bh.mapchunks_processed = 0; +-- how many houses have been generated in these mapchunks? +witches.bh.houses_generated = 0; + + +-- materials the houses can be made out of +-- allows to reach upper floors +witches.bh.ladder = "default:ladder_wood"; +-- gets placed over the door +witches.bh.lamp = "default:torch"; +-- floor at the entrance level of the house +witches.bh.floor = "default:cobble"; +-- placed randomly in some houses +witches.bh.chest = "default:chest"; +-- glass can be glass panes, iron bars or solid glass +witches.bh.glass = {"default:fence_junglewood","default:fence_wood"}; +-- some walls are tree logs, some wooden planks, some colored plasterwork (if installed) +-- - and some nodes are made out of these materials here +witches.bh.walls = {"default:tree","default:jungletree","default:acacia_tree","default:cobble","default:mossycobble"}; +-- doors +witches.bh.door_bottom = "doors:door_wood_witch_a"; +witches.bh.door_top = "doors:hidden"; +-- make sure the place in front of the door will not get griefed by mapgen +witches.bh.around_house = {"default:mossycobble","default:tree","default:jungletree","default:acacia_tree","default:cobble"}; + + +-- if the realtest game is choosen: adjust materials +if( minetest.get_modpath("core") and minetest.get_modpath("trees")) then + witches.bh.ladder = "trees:pine_ladder"; + witches.bh.lamp = "light:streetlight"; + witches.bh.glass = {"xpanes:pane_5","xpanes:pane_5","xpanes:pane_5", + "default:glass","default:glass"}; + witches.bh.walls = {"default:clay", "default:stone", "default:stone_bricks", "default:stone_flat", + "default:stone_macadam", "default:desert_stone", "default:desert_stone_bricks", + "default:desert_stone_flat", "default:desert_stone_macadam", "decorations:malachite_block", + "decorations:cinnabar_block", "decorations:gypsum_block", "decorations:jet_block", + "decorations:lazurite_block", "decorations:olivine_block", "decorations:petrified_wood_block", + "decorations:satinspar_block", "decorations:selenite_block", "decorations:serpentine_block"}; + witches.bh.door_bottom = "doors:door_pine_b_1"; + witches.bh.door_top = "doors:door_pine_t_1"; + witches.bh.around_house = witches.bh.walls; +-- if the MineClone2 game is choosen: adjust materials +elseif( minetest.get_modpath("mcl_core")) then + local colors = {"red", "green", "blue", "light_blue", "black", "white", + "yellow", "brown", "orange"; "pink", "grey", "lime", "silver", + "magenta", "purple", "cyan"}; + witches.bh.ladder = "mcl_core:ladder"; + witches.bh.lamp = "mcl_ocean:sea_lantern"; + witches.bh.floor = "mcl_core:brick_block"; + witches.bh.chest = "mcl_chests:chest"; + + witches.bh.glass = {"mcl_core:glass", "mcl_core:glass", "mcl_core:glass", + "xpanes:bar_flat"}; + for i,k in ipairs( colors ) do + table.insert( witches.bh.glass, "mcl_core:glass_"..k ); + table.insert( witches.bh.glass, "xpanes:pane_"..k.."_flat" ); + end + + witches.bh.walls = {"mcl_core:brick_block", + "mcl_core:stonebrick", "mcl_core:stonebrickcarved", "mcl_core:stonebrickcracked", + "mcl_core:stonebrickmossy","mcl_core:sandstonecarved", "mcl_core:sandstonesmooth2", + "mcl_core:redsandstonecarved"}; + for i,k in ipairs( colors ) do + table.insert( witches.bh.walls, "mcl_colorblocks:glazed_terracotta_"..k ); + table.insert( witches.bh.walls, "mcl_colorblocks:hardened_clay_"..k ); + end + witches.bh.around_house = { "mcl_core:stone_smooth", "mcl_core:granite_smooth", + "mcl_core:andesite_smooth", "mcl_core:diorite_smooth", "mcl_core:sandstonesmooth", + "mcl_core:sandstonecarved", "mcl_core:sandstonesmooth2", + "mcl_core:redsandstonesmooth", "mcl_core:redsandstonesmooth2"}; + witches.bh.door_bottom = "mcl_doors:wooden_door_b_1"; + witches.bh.door_top = "mcl_doors:wooden_door_t_1"; +end + +-- build either the two walls of the box that forms the house in x or z direction; +-- windows are added randomly +-- parameters: +-- p starting point of these walls +-- sizex length of the entire building in x direction +-- sizez same for z direction +-- in_x_direction do we have to build the two walls in x direction or the two in z direction? +-- materials needs to contain at least the fields +-- walls node name of wall material +-- glass node name of glass material +-- color optional; param2-color-value for wall node +-- rotation_1 param2 for materials.wall nodes for the first wall +-- rotation_2 param2 for materials.wall nodes for the second wall +-- vm voxel manipulator +witches.bh.build_two_walls = function( p, sizex, sizez, in_x_direction, materials, vm, pr) + + local v = 0; + if( not( in_x_direction )) then + v = 2; + end + -- param2 (orientation or color) for the first two walls; + -- tree logs need to be orientated correctly, colored nodes have to keep their color; + local node_wall_1 = {name=materials.walls, param2 = (materials.color or materials.wall_orients[1+v])}; + local node_wall_2 = {name=materials.walls, param2 = (materials.color or materials.wall_orients[2+v])}; + -- glass panes and metal bars need the correct rotation and no color value + local node_glass_1 = {name=materials.glass, param2 = materials.glass_orients[1+v]}; + local node_glass_2 = {name=materials.glass, param2 = materials.glass_orients[2+v]}; + -- solid glass needs a rotation of 0 (else it would be interpreted as level) + if( minetest.registered_nodes[ materials.glass ] + and minetest.registered_nodes[ materials.glass ].paramtype2 == "glasslikeliquidlevel") then + node_glass_1.param2 = 0; + node_glass_2.param2 = 0; + end + + local w1_x; + local w2_x; + local w1_z; + local w2_z; + local size; + if( in_x_direction ) then + w1_x = p.x; + w2_x = p.x; + w1_z = p.z; + w2_z = p.z+sizez; + size = sizex+1; + else + w1_x = p.x; + w2_x = p.x+sizex; + w1_z = p.z; + w2_z = p.z; + size = sizez+1; + end + + -- place windows at even or odd rows? -> create some variety + local window_at_odd_row = false; + if( pr:next(1,2)==1 ) then + window_at_odd_row = true; + end + + -- place where a door or ladder might be added (no window there); + -- we need to avid adding ladders directly in front of windows or + -- placing doors right next to glass panes because that would look ugly + local special_wall_1 = pr:next(3,math.max(2,size-2)); + local special_wall_2 = pr:next(3,math.max(2,size-2)); + if( special_wall_2 == special_wall_1 ) then + special_wall_2 = special_wall_2 - 1; + if( special_wall_2 < 3 ) then + special_wall_2 = 3; + end + end + + local wall_height = #materials.window_at_height; + for lauf = 1, size do + local wall_1_has_window = false; + local wall_2_has_window = false; + -- the corners never get glass + if( lauf>1 and lauf1) then + i = i-1; + else + i = i+1; + end + end + -- ladders need to be placed on the right side so that people can climb up + if( use_this_one and places[use_this_one]) then + i = use_this_one; + end + local at_odd_row = (places[i]%2==1); + if( (i==1 or i==2) and (places[5]==at_odd_row)) then + return {x=p.x+places[i], y=p.y, z=p.z+1+offset, p2=5, used=i}; + elseif( (i==1 or i==2) and (places[5]~=at_odd_row)) then + return {x=p.x+places[i], y=p.y, z=p.z-1-offset+sizez, p2=4, used=i}; + elseif( (i==3 or i==4) and (places[6]==at_odd_row)) then + return {x=p.x+1+offset, y=p.y, z=p.z+places[i], p2=3, used=i}; + elseif( (i==3 or i==4) and (places[6]~=at_odd_row)) then + return {x=p.x-1-offset+sizex, y=p.y, z=p.z+places[i], p2=2, used=i}; + else + return {x=p.x, y=p.y, z=p.z, used=0}; + end +end + + +-- add a ladder from bottom to top (staircases would be nicer but are too difficult to do well) +-- if flat_roof is false, the ladder needs to be placed on the smaller side so that people can +-- actually climb it; +-- ladder_places are the special places witches.bh.build_two_walls(..) has reserved +witches.bh.place_ladder = function( p, sizex, sizez, ladder_places, ladder_height, flat_roof, vm, pr ) + -- place the ladder at the galbe side in houses with a real roof (else + -- climbing the ladder up to the roof would fail due to lack of room) + local use_place = nil; + if( not( flat_roof) and (sizex < sizez )) then + use_place = pr:next(1,2); + elseif( not( flat_roof) and (sizex >= sizez )) then + use_place = pr:next(3,4); + end + -- select one of the four reserved places + local res = witches.bh.get_random_place( p, sizex, sizez, ladder_places, use_place, -1, 1, pr ); + local ladder_node = {name=witches.bh.ladder, param2 = res.p2}; + -- actually place the ladders + for height=p.y+1, p.y + ladder_height do + vm:set_node_at( {x=res.x, y=height, z=res.z}, ladder_node ); + end + return res.used; +end + +-- place the door into one of the reserved places +witches.bh.place_door = function( p, sizex, sizez, door_places, wall_with_ladder, floor_height, vm, pr ) + + local res = witches.bh.get_random_place( p, sizex, sizez, door_places, -1, wall_with_ladder, 0, pr ); + vm:set_node_at( {x=res.x, y=p.y+1, z=res.z}, {name=witches.bh.door_bottom, param2 = 0 }); + vm:set_node_at( {x=res.x, y=p.y+2, z=res.z}, {name=witches.bh.door_top, param2 = 0}); + -- light so that the door can be found + --vm:set_node_at( {x=res.x, y=p.y+3, z=res.z}, {name=witches.bh.lamp}); + + -- add some light to the upper floors as well + for i,height in ipairs( floor_height ) do + if( i>2) then + vm:set_node_at( {x=res.x,y=height-1,z=res.z},{name=witches.bh.lamp}); + end + end + return res.used; +end + +-- the chest is placed on one of the upper floors; it contains +-- additional building material +witches.bh.place_chest = function( p, sizex, sizez, chest_places, wall_with_ladder, floor_height, vm, materials, pr ) + -- not each building needs a chest + --[[yes it does! + if( pr:next(1,2)>1 ) then + return; + end +--]] + local res = witches.bh.get_random_place( p, sizex, sizez, chest_places, -1, wall_with_ladder, 1, pr ); + local height = floor_height[ pr:next(2,math.max(2,#floor_height))]; + -- translate wallmounted (for ladder) to facedir for chest + res.p2 = res.p2; + if( res.p2 == 5 ) then + res.p2n = 2; + elseif( res.p2 == 4 ) then + res.p2n = 0; + elseif( res.p2 == 3 ) then + res.p2n = 3; + elseif( res.p2 == 2 ) then + res.p2n = 1; + end + -- determine target position + local pos = {x=res.x, y=height+1, z=res.z}; + -- if plasterwork is installed: place a machine + if( materials.color and minetest.global_exists("plasterwork")) then -- and pr:next(1,10)==1) then + vm:set_node_at( pos, {name=materials.walls, param2 = materials.color}); + local pos2 = {x=res.x, y=height+2, z=res.z}; + vm:set_node_at( pos2, {name="plasterwork:machine", param2 = res.p2n}); + -- if we are operating inside handle_schematics, pos2 will not relate to the real world; + -- it will just be a data structure. Therefore, we can't change the world at those coordinates. + if( not( vm.is_fake_vm )) then + minetest.registered_nodes[ "plasterwork:machine" ].after_place_node(pos2, nil, nil); + local meta = minetest.get_meta( pos2); + meta:set_string( "target_node", materials.walls ); + meta:set_int( "target_color", materials.color ); + end + return; + end + -- place the chest + vm:set_node_at( pos, {name=witches.bh.chest, param2 = res.p2n}); + -- if we are operating inside handle_schematics, positions do not directly correspond + -- to real map positions; we can't change the map directly at this time. Therefore, + -- we're finished for now. + if( vm.is_fake_vm ) then + return; + end + -- fill chest with building material + minetest.registered_nodes[ witches.bh.chest ].on_construct( pos ); + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory(); + local c = pr:next(1,4); + for i=1,c do + local stack_name = materials.walls.." "..pr:next(1,99); + if( materials.color ) then + stack_name = minetest.itemstring_with_palette( stack_name, materials.color ); + end + inv:add_item( "main", stack_name ); + end + inv:add_item( "main", materials.first_floor.." "..pr:next(1,49) ); + c = pr:next(1,2); + for i=1,c do + inv:add_item( "main", materials.ceiling.." "..pr:next(1,99) ); + end + inv:add_item( "main", materials.glass.." "..pr:next(1,20) ); + if( not( materials.roof_flat )) then + inv:add_item( "main", materials.roof.." "..pr:next(1,99) ); + inv:add_item( "main", materials.roof_middle.." "..pr:next(1,49) ); + end +end + + +-- locate a place for the "hut" +witches.bh.simple_hut_find_place = function( heightmap, minp, maxp, sizex, sizez, minheight, maxheight ) + + local res = handle_schematics.find_flat_land_get_candidates_fast( heightmap, minp, maxp, + sizex, sizez, minheight, maxheight ); + +-- print( "Places found of size "..tostring( sizex ).."x"..tostring(sizez)..": "..tostring( #res.places_x ).. +-- " and "..tostring( sizez ).."x"..tostring(sizex)..": "..tostring( #res.places_z ).. +-- "."); + + if( (#res.places_x + #res.places_z )< 1 ) then +-- print( " Aborting. No place found."); + return nil; + end + + -- select a random place - either sizex x sizez or sizez x sizex + local c = math.random( 1, #res.places_x + #res.places_z ); + local i = 1; + if( c > #res.places_x ) then + i = res.places_z[ c-#res.places_x ]; + -- swap x and z due to rotation of 90 or 270 degree + local tmp = sizex; + sizex = sizez; + sizez = tmp; + tmp = nil; + else + i = res.places_x[ c ]; + end + + local chunksize = maxp.x - minp.x + 1; + -- translate index back into coordinates + local p = {x=minp.x+(i%chunksize)-1, y=heightmap[ i ], z=minp.z+math.floor(i/chunksize), i=i}; + return {p1={x=p.x - sizex, y=p.y, z=p.z - sizez }, p2=p, sizex=sizex, sizez=sizez}; +end + + +-- chooses random materials, amount of floors etc.; +-- sets data.materials and data.p2.ymax +witches.bh.simple_hut_get_materials = function( data, amount_in_this_mapchunk, chunk_ends_at_height, pr ) + -- select some random materials, height etc. + -- wood is always useful + local wood_types = replacements_group['wood'].found; + local wood = wood_types[ pr:next(1,math.max(1,#wood_types))]; + local wood_roof = wood_types[ pr:next(1,math.max(1,#wood_types))]; + -- choose random materials + local materials = { + walls = nil, + color = nil, + gable = nil, + glass = witches.bh.glass[ pr:next( 1,math.max(1,#witches.bh.glass ))], + roof = replacements_group['wood'].data[ wood_roof ][7], -- stair + roof_middle = replacements_group['wood'].data[ wood_roof ][8], -- slab + first_floor = witches.bh.floor, + ceiling = wood_types[ pr:next(1,math.max(1,#wood_types))], + wall_orients = {0,1,2,3}, + glass_orients = {12,18,9,7}, + }; + + -- windows 3 nodes high, 2 high, or just 1? + local r = pr:next(1,6); + if( r==1 or r==2) then + materials.window_at_height = {0,1,1,1,0}; + elseif( r==3 or r==4 or r==5) then + materials.window_at_height = {0,0,1,1,0}; + else + materials.window_at_height = {0,0,1,0,0}; + end + + -- how many floors will the house have? + local max_floors_possible = math.floor((chunk_ends_at_height-1-data.p2.y)/#materials.window_at_height); + if( pr:next(1,5)==1) then + materials.floors = pr:next(1,math.min(2,math.max(1,max_floors_possible-1))); + else + materials.floors = pr:next(1,math.min(2,math.max(1,max_floors_possible-1))); + end + + + -- some houses may have a flat roof instead of a saddle roof + materials.flat_roof = false; + --[[ yeah,nah... + if( pr:next(1,2)==1) then + materials.flat_roof = true; + end + --]] + -- path around the house so that the door is accessible + materials.around_house = witches.bh.around_house[ pr:next(1, #witches.bh.around_house )]; + -- which wall material shall be used? + if( minetest.global_exists("plasterwork") and pr:next(1,2)==1 ) then + -- colored plasterwork + materials.walls = plasterwork.node_list[ pr:next(1, #plasterwork.node_list)]; + materials.color = pr:next(0,255); + else + local r = pr:next(1,3); + -- wooden house + if( r==1 ) then + materials.walls = wood; + -- wooden houses with more than 3 floors would be strange + materials.floors = pr:next(1, math.min( 2, math.max(2,max_floors_possible-1 ))); + -- flat roofs do not look good on them either + materials.flat_roof = false; + -- vertical wood is also pretty decorative + if( pr:next(1,2)==1 ) then + materials.wall_orients = {12,18,9,7}; + end + -- tree logs + elseif( r==2 ) then + materials.walls = replacements_group['wood'].data[ wood ][4]; -- tree trunk + -- log cabins with more than 2 floors are unlikely + materials.floors = pr:next(1, math.min( 2, math.max(2,max_floors_possible-1 ))); + -- log cabins do not have a flat roof either + materials.flat_roof = false; + materials.wall_orients = {12,18,9,7}; + else + materials.walls = witches.bh.walls[ pr:next(1,#witches.bh.walls)]; + end + end + -- if there are less than three houses in a mapchunk: do not place skyscrapers + if( amount_in_this_mapchunk < 100 ) then + -- use saddle roof instead of flat one + materials.roof_flat = false; + -- at max two floors + materials.floors = math.min( 2, materials.floors ); + end + + materials.gable = materials.walls; + if( pr:next(1,3)==1 ) then + materials.gable = wood_types[ pr:next(1,#wood_types)]; + end + + local height = materials.floors * #materials.window_at_height +1; + if( materials.flat_roof ) then + data.p2.ymax = math.min( chunk_ends_at_height, data.p2.y + height + math.ceil( math.min( data.sizex, data.sizez )/2 )); + else + data.p2.ymax = math.min( chunk_ends_at_height, data.p2.y + height + 4); + end + data.p2.ymax = math.min( chunk_ends_at_height, data.p2.ymax ); + data.materials = materials; + + + -- place windows at even or odd rows? -> create some variety + local window_at_odd_row = false; + if( pr:next(1,2)==1 ) then + window_at_odd_row = true; + end + + -- place where a door or ladder might be added (no window there); + -- we need to avid adding ladders directly in front of windows or + -- placing doors right next to glass panes because that would look ugly + local special_wall_1 = pr:next(3,math.max(3,math.min(data.sizex,data.sizez)-3)); + local special_wall_2 = pr:next(3,math.max(3,math.min(data.sizex,data.sizez)-3)); + if( special_wall_2 == special_wall_1 ) then + special_wall_2 = special_wall_2 - 1; + if( special_wall_2 < 3 ) then + special_wall_2 = 4; + end + end + +--[[ + local wall_height = #materials.window_at_height; +-- TODO: size (1x x, 1x z) + for lauf = 1, size do + local wall_1_has_window = false; + local wall_2_has_window = false; + -- the corners never get glass + if( lauf>1 and lauf64 or sizez>64) then + return nil; + end + + -- replaicate the pattern of windows for the other floors + local first_floor_height = #materials.window_at_height; + local floor_height = {p.y}; + local floor_materials = {{name=materials.first_floor}}; + for i=1,materials.floors-1 do + for k=2,first_floor_height do + table.insert( materials.window_at_height, materials.window_at_height[k]); + end + table.insert( floor_height, floor_height[ #floor_height] + first_floor_height-1); + table.insert( floor_materials, {name=materials.ceiling}); + end + table.insert( floor_height, floor_height[ #floor_height] + first_floor_height-1); + if( materials.flat_roof ) then + -- the upper floor will form the roof of the house and is made out of + -- its wall material + table.insert( floor_materials, {name=materials.walls, param2 = (materials.color or 12)}); + table.insert( materials.window_at_height, 0 ); + else + -- the house uses a saddle roof; the ceiling will use wood + table.insert( floor_materials, {name=materials.ceiling, param2 = (materials.color or 12)}); + end + + local p_start = {x=p.x-sizex+1, y=p.y-1, z=p.z-sizez+1}; + -- build the two walls in x direction + local s1 = witches.bh.build_two_walls(p_start, sizex-2, sizez-2, true, materials, vm, pr ); --12, 18, vm ); + -- build the two walls in z direction + local s2 = witches.bh.build_two_walls(p_start, sizex-2, sizez-2, false, materials, vm, pr ); -- 9, 7, vm ); + + -- each floor is 4 blocks heigh + local roof_starts_at = p.y + (3*materials.floors); + p_start = {x=p.x-sizex, y=roof_starts_at, z=p.z-sizez, ymax = p.ymax}; + -- make the roof one higher - so that players/mobs can stay upright on + -- each roof floor node - this makes it easier to build staircases + p_start.y = p_start.y+2; + -- build the roof + if( materials.flat_roof ) then + -- build a flat roof + p_start.y = p_start.y-1; -- no need to make that higher + elseif( sizex < sizez ) then + witches.bh.build_roof_and_gable(p_start, sizex, sizez, true, materials, 1, 3, vm ); + else + witches.bh.build_roof_and_gable(p_start, sizex, sizez, false, materials, 0, 2, vm ); + end + + local do_ceiling = ( math.min( sizex, sizez )>4 ); + -- floor and ceiling + for dx = p.x-sizex+2, p.x-2 do + for dz = p.z-sizez+2, p.z-2 do + for i,height in ipairs( floor_height ) do + vm:set_node_at( {x=dx,y=height,z=dz},floor_materials[i]); + end + end + end + + local around_house_node = {name=materials.around_house, param2=0}; + local air_node = {name="air"}; + for dx = p.x-sizex, p.x do + -- path around the house + vm:set_node_at( {x=dx, y=p.y, z=p.z-sizez}, around_house_node ); + vm:set_node_at( {x=dx, y=p.y, z=p.z }, around_house_node ); + -- make sure there is no snow blocking entrance + vm:set_node_at( {x=dx, y=p.y+1, z=p.z-sizez}, air_node ); + vm:set_node_at( {x=dx, y=p.y+1, z=p.z }, air_node ); + end + local max_trees = 2 + for dz = p.z-sizez+1, p.z-1 do + -- path around the house + if math.random(1,100) and max_trees >= 1 then + + local tree_pos = {x=p.x-(sizex+20), y=p.y, z=dz} + --default.grow_new_apple_tree + if math.random() < .5 then + minetest.spawn_tree(tree_pos,witches.acacia_tree) + else + minetest.spawn_tree(tree_pos,witches.apple_tree) + end + print("growing tree at "..minetest.pos_to_string(vector.round(tree_pos))) + max_trees = max_trees - 1 + end + vm:set_node_at( {x=p.x-sizex, y=p.y, z=dz}, around_house_node ); + vm:set_node_at( {x=p.x, y=p.y, z=dz}, around_house_node ); + -- make sure there is no snow blocking entrance + vm:set_node_at( {x=p.x-sizex, y=p.y+1, z=dz}, air_node ); + vm:set_node_at( {x=p.x, y=p.y+1, z=dz}, air_node ); + end + + + -- index 1 and 2 are offsets in any of the walls; index 3 indicates if the + -- windows start at odd indices or not + local reserved_places = {s1[1], s1[2], s2[1], s2[2], s1[3], s2[3]}; + p_start = {x=p.x-sizex, y=p.y, z=p.z-sizez}; + local wall_with_ladder = witches.bh.place_ladder( p_start, sizex, sizez, + reserved_places, #materials.window_at_height-1, materials.flat_roof, vm, pr ); + + witches.bh.place_door( p_start, sizex, sizez, reserved_places, wall_with_ladder, floor_height, vm, pr ); + witches.bh.place_chest( p_start, sizex, sizez, reserved_places, wall_with_ladder, floor_height, vm, materials, pr ); + + -- return where the hut has been placed + return {p1={x=p.x - sizex, y=p.y, z=p.z - sizez }, p2=p}; +end + + +-- get the voxelmanip object and place the house in there +witches.bh.simple_hut_place_hut = function( data, materials, pr ) + local p = data.p2; + local sizex = data.sizex-1; + local sizez = data.sizez-1; + -- house too small or too large + if( sizex < 3 or sizez < 3 or sizex>64 or sizez>64) then + return nil; + end + print( " Placing house at "..minetest.pos_to_string( p )); + + local vm = minetest.get_voxel_manip(); + vm:read_from_map( + {x=p.x - sizex, y=p.y-1, z=p.z - sizez }, + {x=p.x, y=p.ymax, z=p.z}); + witches.bh.simple_hut_place_hut_using_vm( data, materials, vm, pr ) + vm:write_to_map(true); +end + + +witches.bh.simple_hut_get_size_and_place = function( heightmap, minp, maxp) + if( minp.y < -64 or minp.y > 500 or not(heightmap)) then + return; + end + -- halfway reasonable house sizes + local maxsize = 9; + --[[ + if( math.random(1,5)==1) then + maxsize = 17; + end + --]] +-- TODO: if more than 2-3 houses are placed, get voxelmanip for entire area instead of for each house +-- TODO: avoid overlapping with mg_villages if that one is installed + local sizex = math.random(8,maxsize); + local sizez = math.max( 8, math.min( maxsize, math.random( math.floor(sizex/4), sizex*2 ))); + -- chooses random materials and a random place without destroying the landscape + -- minheight 2: one above water level; avoid below water level and places on ice + return witches.bh.simple_hut_find_place( heightmap, minp, maxp, sizex, sizez, 2, 1000 ); +end + + +-- mg_villages takes precedence; however, both mods can work together; it's just that mg_villages +-- has to take care of all the things at mapgen time +if(not(minetest.get_modpath("mg_villages"))) then + minetest.register_on_generated(function(minp, maxp, seed) + if( minp.y < -64 or minp.y > 500) then + return; + end + witches.bh.mapchunks_processed = witches.bh.mapchunks_processed + 1; + -- with each map chunk generated, there's more room where houses could be + local missing = math.floor(witches.bh.mapchunks_processed * witches.bh.houses_wanted_per_mapchunk) + - witches.bh.houses_generated; + -- some randomness to make it more intresting + -- also place a house in the first mapchunk possible in order to "greet" the player + -- with it and assure the player that the mod is installed + if( (witches.bh.houses_generated>1) + and missing < witches.bh.max_per_mapchunk and math.random(1,100)>witches.bh.additional_chance) then + return; + end + local heightmap = minetest.get_mapgen_object('heightmap'); + local houses_placed = 0; + local house_data = {}; + local anz_houses = math.random( math.min( missing, math.floor(witches.bh.max_per_mapchunk/2 )), + witches.bh.max_per_mapchunk ); + for i=1,anz_houses do + local res = witches.bh.simple_hut_get_size_and_place( heightmap, minp, maxp); + if( res and res.p1 and res.p2 + and res.p2.x>=minp.x and res.p2.z>=minp.z + and res.p2.x<=maxp.x and res.p2.z<=maxp.z) then + handle_schematics.mark_flat_land_as_used(heightmap, minp, maxp, + res.p2.i, + (res.p2.x-res.p1.x), + (res.p2.z-res.p1.z)); + table.insert( house_data, res ); + houses_placed = houses_placed + 1; + end + end + -- use the same material around the houses in the entire mapchunk + local around_house_material = nil; + for i,data in ipairs( house_data ) do + -- initialize pseudorandom number generator + local pr = PseudoRandom( data.p2.x + data.p2.z ); + local res = witches.bh.simple_hut_get_materials( data, #house_data, maxp.y+16, pr ); + if( not( around_house_material )) then + around_house_material = res.materials.around_house; + else + res.materials.around_house = around_house_material; + end + witches.bh.simple_hut_place_hut( data, res.materials, pr ); + end + + if( houses_placed > 0 ) then + witches.bh.houses_generated = witches.bh.houses_generated + houses_placed; +-- print("Count: "..tostring( witches.bh.mapchunks_processed ).. +-- " Houses: "..tostring( witches.bh.houses_generated )); + end + end); +end + + +-- interface for handle_schematics for manual generation of houses +witches.bh.get_parameter = function( pos, sizex, sizez, sizey, pr ) + local data = { p2={x=pos.x+sizex, y=pos.y, z=pos.z+sizez}, sizex=sizex, sizez=sizez, sizey=sizey}; + -- it needs at least 3 houses in this mapchunk in order to generate a flat roof + local amount_in_this_mapchunk = 100; + -- how heigh can the building become at max? + local chunk_ends_at_height = data.p2.y+1+sizey; + -- suggest random materials and other values + local res = witches.bh.simple_hut_get_materials( data, amount_in_this_mapchunk, chunk_ends_at_height, pr ) + -- these parameters are needed as well + res.p2 = data.p2; + res.sizex = data.sizex; + res.sizez = data.sizez; + res.sizey = data.sizey; + return res; +end + + +-- for manual placement with handle_schematics and/or mg_villages; +-- vm may be a fake VoxelManip data structure +-- returns a value != nil (actually the start and end position) if successful +witches.bh.generate_random_hut_at_pos = function( pos, sizex, sizez, sizey, seed, vm ) + -- prepare the data structure containing position and size + local data = { p2 = {x=pos.x+sizex, y=pos.y, z=pos.z+sizez}, sizex = sizex, sizez = sizez }; + -- initialize pseudorandom number generator for reproducability + local pr = PseudoRandom( seed ); + -- if the second parameter is greater than 3, houses with a flat roof can be generated + local res = witches.bh.simple_hut_get_materials( data, 4, pos.y+sizey+1, pr ); + -- no need to assure a walkable path to the entrance if we are dealing with mods + -- that ensure that by diffrent means (mg_villages = flat land; build chest from + -- handle_schematics = player places manually); dirt with grass is a general + -- placeholder for the biome surface + res.materials.around_house = "default:dirt_with_grass"; + -- place the house into the vm data structure + local res = witches.bh.simple_hut_place_hut_using_vm( data, data.materials, vm, pr ) + -- the structure is burried one node deep (=floor) + vm.yoff = 0; + -- the fake voxelmanip data structure contains all the data we need + return vm; +end + + +build_chest.add_entry( {'generate building','basic_houses', 'witches.bh.generator'}); +build_chest.add_building( 'witches.bh.generator', + { generator=witches.bh.generate_random_hut_at_pos, + } ); diff --git a/depends.txt b/depends.txt index 89352b0..aacc70a 100644 --- a/depends.txt +++ b/depends.txt @@ -1,3 +1,3 @@ default mobs - +handle_schematics? diff --git a/init.lua b/init.lua index c1e81e7..0052b8e 100644 --- a/init.lua +++ b/init.lua @@ -5,7 +5,7 @@ local path = minetest.get_modpath("witches") witches = {} -witches.version = "20200719" +witches.version = "20200720" print("this is Witches "..witches.version) -- Strips any kind of escape codes (translation, colors) from a string @@ -59,8 +59,21 @@ end dofile(path .. "/utilities.lua") dofile(path .. "/ui.lua") dofile(path .. "/items.lua") +dofile(path .. "/nodes.lua") + +if not handle_schematics then + return +else + + dofile(path .. "/basic_houses.lua") + print("handle_schematics found! Witch houses enabled!") +end print("enter the witches! version: "..witches.version) +print(minetest.colorize("red","sometext")) + +print("crgbtest: " ..colorsRGB.RGB("red")) + local variance = witches.variance local rnd_color = witches.rnd_color local rnd_colors = witches.rnd_colors @@ -68,17 +81,20 @@ local hair_colors = witches.hair_colors local spawning = { generic = { - nodes = {"group:stone"}, - neighbors = "air", - min_light = 0, + nodes = {"group:wood","default:mossycobble","default:cobble"}, + neighbors = {"air","default:chest","doors:wood_witch_a"}, + min_light = 5, max_light = 15, interval = 30, - chance = 1000, + chance = 10, active_object_count = 2, min_height = 0, max_height = 200, day_toggle = nil, - on_spawn = nil, + on_spawn = function(self) + local pos = self.object:get_pos() + print(self.secret_name.." spawned at ".. minetest.pos_to_string(vector.round(pos))) + end, }, } diff --git a/mod.conf b/mod.conf index 5165ee5..5d2609a 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name = witches descriptions = adds witches depends = default, mobs -optional_depends = none +optional_depends = handle_schematics diff --git a/nodes.lua b/nodes.lua new file mode 100644 index 0000000..a49a7a1 --- /dev/null +++ b/nodes.lua @@ -0,0 +1,68 @@ +local S = minetest.get_translator("witches") +if not doors then + print("doors mod not found!") + return +else + print("doors active") + doors.register("door_wood_witch", { + tiles = {{ name = "doors_door_wood.png", backface_culling = true }}, + description = S("Wooden Door"), + inventory_image = "doors_item_wood.png", + groups = {node = 1, choppy = 2, oddly_breakable_by_hand = 2, flammable = 2}, + --[[ + recipe = { + {"group:wood", "group:wood"}, + {"group:wood", "group:wood"}, + {"group:wood", "group:wood"}, + } + --]] + }) +end + +witches.acacia_tree={ + axiom="FFFFFFccccA", + rules_a = "[B]//[B]//[B]//[B]", + rules_b = "&TTTT&TT^^G&&----GGGGGG++GGG++" -- line up with the "canvas" edge + .."fffffRfGG++G++" -- first layer, drawn in a zig-zag raster pattern + .."Gffffffff--G--" + .."ffffRfffG++G++" + .."fffffffff--G--" + .."fffffffff++G++" + .."ffRffffff--G--" + .."ffffffffG++G++" + .."GffffRfff--G--" + .."fffffffGG" + .."^^G&&----GGGGGGG++GGGGGG++" -- re-align to second layer canvas edge + .."ffffGGG++G++" -- second layer + .."GGfffff--G--" + .."ffRfffG++G++" + .."fffffff--G--" + .."ffffRfG++G++" + .."GGfffff--G--" + .."ffRfGGG", + rules_c = "/", + trunk="default:tree", + leaves="default:leaves", + angle=45, + iterations=3, + random_level=0, + trunk_type="single", + thin_branches=true, + fruit_chance=5, + fruit="default:apple" +} + +witches.apple_tree={ + axiom="FFFFFAFFBF", + rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]", + rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]", + trunk="default:tree", + leaves="default:leaves", + angle=30, + iterations=2, + random_level=1, + trunk_type="single", + thin_branches=true, + fruit_chance=5, + fruit="default:apple" +} diff --git a/utilities.lua b/utilities.lua index a026184..83c6165 100644 --- a/utilities.lua +++ b/utilities.lua @@ -393,9 +393,180 @@ end ---- Our mobs, territories, etc can have randomly generated names. --- @name_parts is the name parts table: {list_a = "foo bar baz"} --- @rules are the list table key names in order of how they will be chosen --- "-" and "\'" are rules that can be used to add a hyphen or apostrophe respectively +-- modified from https://snipplr.com/view/40782/color-to-rgb-value-table-in-lua +-- so that it won't crash on undefined name (just returns the name in that case) +-- FreeLikeGNU + +colorsRGB = { + aliceblue = {240, 248, 255}, + antiquewhite = {250, 235, 215}, + aqua = { 0, 255, 255}, + aquamarine = {127, 255, 212}, + azure = {240, 255, 255}, + beige = {245, 245, 220}, + bisque = {255, 228, 196}, + black = { 0, 0, 0}, + blanchedalmond = {255, 235, 205}, + blue = { 0, 0, 255}, + blueviolet = {138, 43, 226}, + brown = {165, 42, 42}, + burlywood = {222, 184, 135}, + cadetblue = { 95, 158, 160}, + chartreuse = {127, 255, 0}, + chocolate = {210, 105, 30}, + coral = {255, 127, 80}, + cornflowerblue = {100, 149, 237}, + cornsilk = {255, 248, 220}, + crimson = {220, 20, 60}, + cyan = { 0, 255, 255}, + darkblue = { 0, 0, 139}, + darkcyan = { 0, 139, 139}, + darkgoldenrod = {184, 134, 11}, + darkgray = {169, 169, 169}, + darkgreen = { 0, 100, 0}, + darkgrey = {169, 169, 169}, + darkkhaki = {189, 183, 107}, + darkmagenta = {139, 0, 139}, + darkolivegreen = { 85, 107, 47}, + darkorange = {255, 140, 0}, + darkorchid = {153, 50, 204}, + darkred = {139, 0, 0}, + darksalmon = {233, 150, 122}, + darkseagreen = {143, 188, 143}, + darkslateblue = { 72, 61, 139}, + darkslategray = { 47, 79, 79}, + darkslategrey = { 47, 79, 79}, + darkturquoise = { 0, 206, 209}, + darkviolet = {148, 0, 211}, + deeppink = {255, 20, 147}, + deepskyblue = { 0, 191, 255}, + dimgray = {105, 105, 105}, + dimgrey = {105, 105, 105}, + dodgerblue = { 30, 144, 255}, + firebrick = {178, 34, 34}, + floralwhite = {255, 250, 240}, + forestgreen = { 34, 139, 34}, + fuchsia = {255, 0, 255}, + gainsboro = {220, 220, 220}, + ghostwhite = {248, 248, 255}, + gold = {255, 215, 0}, + goldenrod = {218, 165, 32}, + gray = {128, 128, 128}, + grey = {128, 128, 128}, + green = { 0, 128, 0}, + greenyellow = {173, 255, 47}, + honeydew = {240, 255, 240}, + hotpink = {255, 105, 180}, + indianred = {205, 92, 92}, + indigo = { 75, 0, 130}, + ivory = {255, 255, 240}, + khaki = {240, 230, 140}, + lavender = {230, 230, 250}, + lavenderblush = {255, 240, 245}, + lawngreen = {124, 252, 0}, + lemonchiffon = {255, 250, 205}, + lightblue = {173, 216, 230}, + lightcoral = {240, 128, 128}, + lightcyan = {224, 255, 255}, + lightgoldenrodyellow = {250, 250, 210}, + lightgray = {211, 211, 211}, + lightgreen = {144, 238, 144}, + lightgrey = {211, 211, 211}, + lightpink = {255, 182, 193}, + lightsalmon = {255, 160, 122}, + lightseagreen = { 32, 178, 170}, + lightskyblue = {135, 206, 250}, + lightslategray = {119, 136, 153}, + lightslategrey = {119, 136, 153}, + lightsteelblue = {176, 196, 222}, + lightyellow = {255, 255, 224}, + lime = { 0, 255, 0}, + limegreen = { 50, 205, 50}, + linen = {250, 240, 230}, + magenta = {255, 0, 255}, + maroon = {128, 0, 0}, + mediumaquamarine = {102, 205, 170}, + mediumblue = { 0, 0, 205}, + mediumorchid = {186, 85, 211}, + mediumpurple = {147, 112, 219}, + mediumseagreen = { 60, 179, 113}, + mediumslateblue = {123, 104, 238}, + mediumspringgreen = { 0, 250, 154}, + mediumturquoise = { 72, 209, 204}, + mediumvioletred = {199, 21, 133}, + midnightblue = { 25, 25, 112}, + mintcream = {245, 255, 250}, + mistyrose = {255, 228, 225}, + moccasin = {255, 228, 181}, + navajowhite = {255, 222, 173}, + navy = { 0, 0, 128}, + oldlace = {253, 245, 230}, + olive = {128, 128, 0}, + olivedrab = {107, 142, 35}, + orange = {255, 165, 0}, + orangered = {255, 69, 0}, + orchid = {218, 112, 214}, + palegoldenrod = {238, 232, 170}, + palegreen = {152, 251, 152}, + paleturquoise = {175, 238, 238}, + palevioletred = {219, 112, 147}, + papayawhip = {255, 239, 213}, + peachpuff = {255, 218, 185}, + peru = {205, 133, 63}, + pink = {255, 192, 203}, + plum = {221, 160, 221}, + powderblue = {176, 224, 230}, + purple = {128, 0, 128}, + red = {255, 0, 0}, + rosybrown = {188, 143, 143}, + royalblue = { 65, 105, 225}, + saddlebrown = {139, 69, 19}, + salmon = {250, 128, 114}, + sandybrown = {244, 164, 96}, + seagreen = { 46, 139, 87}, + seashell = {255, 245, 238}, + sienna = {160, 82, 45}, + silver = {192, 192, 192}, + skyblue = {135, 206, 235}, + slateblue = {106, 90, 205}, + slategray = {112, 128, 144}, + slategrey = {112, 128, 144}, + snow = {255, 250, 250}, + springgreen = { 0, 255, 127}, + steelblue = { 70, 130, 180}, + tan = {210, 180, 140}, + teal = { 0, 128, 128}, + thistle = {216, 191, 216}, + tomato = {255, 99, 71}, + turquoise = { 64, 224, 208}, + violet = {238, 130, 238}, + wheat = {245, 222, 179}, + white = {255, 255, 255}, + whitesmoke = {245, 245, 245}, + yellow = {255, 255, 0}, + yellowgreen = {154, 205, 50} + } + + colorsRGB.R = function (name) + return colorsRGB[name][1] + end + + colorsRGB.G = function (name) + return colorsRGB[name][2] + end + + colorsRGB.B = function (name) + return colorsRGB[name][3] + end + + colorsRGB.RGB = function (name) + if colorsRGB[name] then + local values = colorsRGB[name][1]..colorsRGB[name][2]..colorsRGB[name][3] + return values + else + return name + end + + end \ No newline at end of file