schematics.lua 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. lottmapgen = {}
  2. local areas_mod = minetest.get_modpath("areas")
  3. local protect_houses = minetest.setting_getbool("protect_structures") or false
  4. -- max number of nodes to try to fill below building
  5. local fill_below_count = 16
  6. -- list of building, with filename and bounding box
  7. local lottmapgen_list = {
  8. ["Angmar Fort"] = {build="angmarfort", area_owner = "Orc Guard", area_name = "Angmar Fort",
  9. center = {x=9, y=2, z=3} },
  10. ["Gondor Fort"] = {build="gondorfort", area_owner = "Gondor Guard", area_name = "Gondor Castle",
  11. center = {x=1, y=1, z=9} },
  12. ["Rohan Fort"] = {build="rohanfort", area_owner = "Rohan Guard", area_name = "Rohan Fort",
  13. center = {x=1, y=1, z=9} },
  14. ["Orc Fort"] = {build="orcfort", area_owner = "Orc Guard", area_name = "Orc Fort",
  15. center = {x=15, y=2, z=1} },
  16. ["Mallorn House"] = {build="mallornhouse", area_owner = "Elven Guard", area_name = "Elven House",
  17. center = {x=2, y=1, z=5} },
  18. ["Lorien House"] = {build="lorienhouse", area_owner = "Elven Guard", area_name = "Elven House",
  19. center = {x=2, y=1, z=5} },
  20. ["Mirkwood House"] = {build="mirkhouse", area_owner = "Elven Guard", area_name = "Elven House",
  21. center = {x=5, y=2, z=1} },
  22. ["Hobbit Hole"] = {build="hobbithole", area_owner = "Hobbit Family", area_name = "Hobbit Hole",
  23. center = {x=13, y=3, z=24} },
  24. ["Dwarf House"] = {build="dwarfhouse", area_owner = "Dwarf Smith", area_name = "Dwarf House",
  25. center = {x=16, y=1, z=3} },
  26. }
  27. -- load bounding box from files
  28. for i, v in pairs(lottmapgen_list) do
  29. local file = io.open(minetest.get_modpath("lottmapgen").."/schems/"..v.build..".we")
  30. local value = file:read("*a")
  31. file:close()
  32. local pos1, pos2 = worldedit.allocate({x=0, y=0, z=0}, value)
  33. lottmapgen_list[i].bbox = {
  34. xmin = pos1.x-v.center.x, ymin = pos1.y-v.center.y, zmin = pos1.z-v.center.z,
  35. xmax = pos2.x-v.center.x, ymax = pos2.y-v.center.y, zmax = pos2.z-v.center.z}
  36. end
  37. -- queue to store the buildings which are waiting to be built (queue structure from https://www.lua.org/pil/11.4.html)
  38. lottmapgen.queue = {first = 0, last = -1}
  39. local file = io.open(minetest.get_worldpath().."/"..SAVEDIR.."/building_queue", "r")
  40. if file then
  41. lottmapgen.queue = minetest.deserialize(file:read("*all"))
  42. file:close()
  43. end
  44. minetest.register_on_shutdown(function()
  45. local file = io.open(minetest.get_worldpath().."/"..SAVEDIR.."/building_queue", "w")
  46. if (file) then
  47. file:write(minetest.serialize(lottmapgen.queue))
  48. file:close()
  49. end
  50. end)
  51. function lottmapgen.enqueue_building(name, pos)
  52. local first = lottmapgen.queue.first - 1
  53. lottmapgen.queue.first = first
  54. lottmapgen.queue[first] = {name = name, pos = pos}
  55. end
  56. -- request to fill some node below buildings
  57. function lottmapgen.enqueue_fill(fill)
  58. local first = lottmapgen.queue.first - 1
  59. lottmapgen.queue.first = first
  60. lottmapgen.queue[first] = {fill=fill}
  61. end
  62. function lottmapgen.dequeue_building()
  63. local last = lottmapgen.queue.last
  64. if lottmapgen.queue.first > last then return nil end
  65. local value = lottmapgen.queue[last]
  66. lottmapgen.queue[last] = nil -- to allow garbage collection
  67. lottmapgen.queue.last = last - 1
  68. return value
  69. end
  70. -- check if all the blocks that intersect the building are genrated
  71. function lottmapgen.check_building(bbox, pos)
  72. --mapgen chuncks generate 80 blocks at a time, so we only need to checks the limits of the bounding box and
  73. -- each 80 inside nodes
  74. for z=bbox.zmin, bbox.zmax+80, 80 do
  75. local cur_z = pos.z + math.min(z, bbox.zmax)
  76. for y=bbox.ymin, bbox.ymax+80, 80 do
  77. local cur_y = pos.y + math.min(y, bbox.ymax)
  78. for x=bbox.xmin, bbox.xmax+80, 80 do
  79. local cur_x = pos.x + math.min(x, bbox.xmax)
  80. if minetest.get_node({x = cur_x, y = cur_y, z = cur_z}).name =="ignore" then
  81. return false
  82. end
  83. end
  84. end
  85. end
  86. return true
  87. end
  88. function lottmapgen.check_fill(fill)
  89. local bbox = {
  90. xmin = fill.xmin, ymin = fill.y-fill_below_count, zmin = fill.zmin,
  91. xmax = fill.xmax, ymax = fill.y, zmax = fill.zmax}
  92. return lottmapgen.check_building(bbox, {x=0, y=0, z=0})
  93. end
  94. -- place building using worldedit
  95. function lottmapgen.place_building(building, pos)
  96. --print(building.build.." placed at "..pos.x..' '..pos.y..' '..pos.z)
  97. local file = io.open(minetest.get_modpath("lottmapgen").."/schems/"..building.build..".we")
  98. local value = file:read("*a")
  99. file:close()
  100. local p = {x = pos.x-building.center.x, y = pos.y-building.center.y, z = pos.z-building.center.z}
  101. local pos1 = {x = pos.x + building.bbox.xmin, y = pos.y + building.bbox.ymin, z = pos.z + building.bbox.zmin}
  102. local pos2 = {x = pos.x + building.bbox.xmax, y = pos.y + building.bbox.ymax, z = pos.z + building.bbox.zmax}
  103. local count = worldedit.deserialize(p, value)
  104. if areas_mod ~= nil and protect_houses == true then
  105. areas:add(building.area_owner, building.area_name, pos1, pos2, nil)
  106. areas:save()
  107. end
  108. lottmapgen.enqueue_fill({xmin = pos1.x, zmin = pos1.z, xmax = pos2.x, zmax = pos2.z, y = pos1.y})
  109. end
  110. minetest.register_globalstep(function(dtime)
  111. -- parse the next 50 building in the queue (while not empty)
  112. for count= 1, 50 do
  113. local queued = lottmapgen.dequeue_building()
  114. if not queued then return end
  115. if queued.fill then
  116. if not lottmapgen.check_fill(queued.fill) then
  117. lottmapgen.enqueue_fill(queued.fill)
  118. else
  119. lottmapgen.fill_bellow(queued.fill)
  120. end
  121. else
  122. local building = lottmapgen_list[queued.name];
  123. -- not all the building will be placed, ask to replace it later
  124. if not lottmapgen.check_building(building.bbox, queued.pos) then
  125. lottmapgen.enqueue_building(queued.name, queued.pos)
  126. else
  127. -- place the building on generated nodes
  128. lottmapgen.place_building(building, queued.pos)
  129. end
  130. end
  131. end
  132. end)
  133. -- fill of the botom nodes to avoid empty space.
  134. lottmapgen.fill_bellow = function(fill)
  135. local pos1 = {x = fill.xmin, y = fill.y-fill_below_count, z = fill.zmin}
  136. local pos2 = {x = fill.xmax, y = fill.y, z = fill.zmax}
  137. local replace_node = {}
  138. replace_node[minetest.get_content_id("lottother:dirt")]=minetest.get_content_id("default:dirt")
  139. replace_node[minetest.get_content_id("lottother:snow")]=minetest.get_content_id("default:snowblock")
  140. replace_node[minetest.get_content_id("lottother:mordor_stone")]=minetest.get_content_id("lottmapgen:mordor_stone")
  141. local c_air = minetest.get_content_id("air")
  142. local c_ignore = minetest.get_content_id("ignore")
  143. local c_water = minetest.get_content_id("default:water_source")
  144. local c_river_water = minetest.get_content_id("default:river_water_source")
  145. local c_morwat = minetest.get_content_id("lottmapgen:blacksource")
  146. local c_morrivwat = minetest.get_content_id("lottmapgen:black_river_source")
  147. local water_level = minetest.get_mapgen_setting("water_level")
  148. local vm = minetest.get_voxel_manip()
  149. local emin, emax = vm:read_from_map(pos1, pos2)
  150. local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
  151. local data = vm:get_data()
  152. local bottom_reached = false
  153. for x = pos1.x, pos2.x do
  154. for z = pos1.z, pos2.z do
  155. local top_index = area:index(x, pos2.y, z)
  156. local top = data[top_index]
  157. local replace_to = replace_node[top]
  158. if replace_to then
  159. data[top_index] = replace_to
  160. if pos2.y > water_level-1000 then
  161. for y = pos2.y-1, pos1.y, -1 do
  162. local index = area:index(x, y, z)
  163. local cur = data[index]
  164. if cur == c_air or cur == c_water or cur == c_river_water
  165. or cur == c_morwat or cur == c_morrivwat then
  166. data[index] = replace_to
  167. else
  168. break
  169. end
  170. if y==pos1.y then
  171. data[index] = top
  172. bottom_reached = true
  173. end
  174. end
  175. end
  176. end
  177. end
  178. end
  179. vm:set_data(data)
  180. vm:update_map()
  181. vm:write_to_map()
  182. if pos2.y > water_level-1000 and bottom_reached then
  183. lottmapgen.enqueue_fill({xmin = fill.xmin, zmin = fill.zmin, xmax = fill.xmax, zmax = fill.zmax, y = fill.y-fill_below_count})
  184. end
  185. end
  186. -- folowing function let as a way to "hack" building placement, but shouldn't be needed
  187. for builddesc, v in ipairs(lottmapgen_list) do
  188. local build = v.build
  189. minetest.register_node("lottmapgen:"..build, {
  190. description = builddesc,
  191. drawtype = "glasslike",
  192. walkable = false,
  193. tiles = {"lottother_air.png"},
  194. pointable = false,
  195. sunlight_propagates = true,
  196. is_ground_content = true,
  197. groups = {not_in_creative_inventory = 1},
  198. on_place = function(itemstack, placer, pointed_thing)
  199. if pointed_thing.above then
  200. local file = io.open(minetest.get_modpath("lottmapgen").."/schems/"..build..".we")
  201. local value = file:read("*a")
  202. file:close()
  203. local p = pointed_thing.above
  204. p.x = p.x-v.center.x
  205. p.y = p.y-v.center.y
  206. p.z = p.z-v.center.z
  207. local count = worldedit.deserialize(p, value)
  208. itemstack:take_item()
  209. end
  210. return itemstack
  211. end,
  212. })
  213. end
  214. minetest.register_abm({
  215. nodenames = {"lottmapgen:gondorfort","lottmapgen:hobbithole","lottmapgen:orcfort","lottmapgen:rohanfort","lottmapgen:mallornhouse","lottmapgen:lorienhouse"},
  216. interval = 4,
  217. chance = 1,
  218. action = function(pos, node, active_object_count, active_object_count_wider)
  219. minetest.remove_node(pos)
  220. end,
  221. })
  222. minetest.register_abm({
  223. nodenames = {"lottmapgen:angmarfort","lottmapgen:mirkhouse"},
  224. interval = 8,
  225. chance = 1,
  226. action = function(pos, node, active_object_count, active_object_count_wider)
  227. minetest.remove_node(pos)
  228. end,
  229. })