mapgen.lua 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. minetest.set_gen_notify({dungeon = true, temple = true})
  2. local function noise3d_integer(noise, pos)
  3. return math.abs(math.floor(noise:get_3d(pos) * 0x7fffffff))
  4. end
  5. local function random_sample(rand, list, count)
  6. local ret = {}
  7. for n = 1, count do
  8. local idx = rand:next(1, #list)
  9. table.insert(ret, list[idx])
  10. table.remove(list, idx)
  11. end
  12. return ret
  13. end
  14. local function find_walls(cpos)
  15. local is_wall = function(node)
  16. return node.name ~= "air" and node.name ~= "ignore"
  17. end
  18. local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}}
  19. local get_node = minetest.get_node
  20. local ret = {}
  21. local mindist = {x=0, z=0}
  22. local min = function(a, b) return a ~= 0 and math.min(a, b) or b end
  23. for _, dir in ipairs(dirs) do
  24. for i = 1, 9 do -- 9 = max room size / 2
  25. local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i})
  26. -- continue in that direction until we find a wall-like node
  27. local node = get_node(pos)
  28. if is_wall(node) then
  29. local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z})
  30. local above = vector.add(pos, {x=0, y=1, z=0})
  31. -- check that it:
  32. --- is at least 2 nodes high (not a staircase)
  33. --- has a floor
  34. if is_wall(get_node(front_below)) and is_wall(get_node(above)) then
  35. table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}})
  36. if dir.z == 0 then
  37. mindist.x = min(mindist.x, i-1)
  38. else
  39. mindist.z = min(mindist.z, i-1)
  40. end
  41. end
  42. -- abort even if it wasn't a wall cause something is in the way
  43. break
  44. end
  45. end
  46. end
  47. local biome = minetest.get_biome_data(cpos)
  48. local biome = biome and minetest.get_biome_name(biome.biome) or ""
  49. local type = "normal"
  50. if biome:find("desert") == 1 then
  51. type = "desert"
  52. elseif biome:find("sandstone_desert") == 1 then
  53. type = "sandstone"
  54. elseif biome:find("icesheet") == 1 then
  55. type = "ice"
  56. end
  57. return {
  58. walls = ret,
  59. size = {x=mindist.x*2, z=mindist.z*2},
  60. type = type,
  61. }
  62. end
  63. local function populate_chest(pos, rand, dungeontype)
  64. --minetest.chat_send_all("chest placed at " .. minetest.pos_to_string(pos) .. " [" .. dungeontype .. "]")
  65. --minetest.add_node(vector.add(pos, {x=0, y=1, z=0}), {name="default:torch", param2=1})
  66. local item_list = dungeon_loot._internal_get_loot(pos.y, dungeontype)
  67. -- take random (partial) sample of all possible items
  68. assert(#item_list >= dungeon_loot.STACKS_PER_CHEST_MAX)
  69. item_list = random_sample(rand, item_list, dungeon_loot.STACKS_PER_CHEST_MAX)
  70. -- apply chances / randomized amounts and collect resulting items
  71. local items = {}
  72. for _, loot in ipairs(item_list) do
  73. if rand:next(0, 1000) / 1000 <= loot.chance then
  74. local itemdef = minetest.registered_items[loot.name]
  75. local amount = 1
  76. if loot.count ~= nil then
  77. amount = rand:next(loot.count[1], loot.count[2])
  78. end
  79. if itemdef then
  80. if itemdef.tool_capabilities then
  81. for n = 1, amount do
  82. local wear = rand:next(0.20 * 65535, 0.75 * 65535) -- 20% to 75% wear
  83. table.insert(items, ItemStack({name = loot.name, wear = wear}))
  84. end
  85. elseif itemdef.stack_max == 1 then
  86. -- not stackable, add separately
  87. for n = 1, amount do
  88. table.insert(items, loot.name)
  89. end
  90. else
  91. table.insert(items, ItemStack({name = loot.name, count = amount}))
  92. end
  93. end
  94. end
  95. end
  96. -- place items at random places in chest
  97. local inv = minetest.get_meta(pos):get_inventory()
  98. local listsz = inv:get_size("main")
  99. assert(listsz >= #items)
  100. for _, item in ipairs(items) do
  101. local index = rand:next(1, listsz)
  102. if inv:get_stack("main", index):is_empty() then
  103. inv:set_stack("main", index, item)
  104. else
  105. inv:add_item("main", item) -- space occupied, just put it anywhere
  106. end
  107. end
  108. end
  109. minetest.register_on_generated(function(minp, maxp, blockseed)
  110. local gennotify = minetest.get_mapgen_object("gennotify")
  111. local poslist = gennotify["dungeon"] or {}
  112. for _, entry in ipairs(gennotify["temple"] or {}) do
  113. table.insert(poslist, entry)
  114. end
  115. if #poslist == 0 then return end
  116. local noise = minetest.get_perlin(10115, 4, 0.5, 1)
  117. local rand = PcgRandom(noise3d_integer(noise, poslist[1]))
  118. local candidates = {}
  119. -- process at most 8 rooms to keep runtime of this predictable
  120. local num_process = math.min(#poslist, 8)
  121. for i = 1, num_process do
  122. local room = find_walls(poslist[i])
  123. -- skip small rooms and everything that doesn't at least have 3 walls
  124. if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then
  125. table.insert(candidates, room)
  126. end
  127. end
  128. local num_chests = rand:next(dungeon_loot.CHESTS_MIN, dungeon_loot.CHESTS_MAX)
  129. num_chests = math.min(#candidates, num_chests)
  130. local rooms = random_sample(rand, candidates, num_chests)
  131. for _, room in ipairs(rooms) do
  132. -- choose place somewhere in front of any of the walls
  133. local wall = room.walls[rand:next(1, #room.walls)]
  134. local v, vi -- vector / axis that runs alongside the wall
  135. if wall.facing.x ~= 0 then
  136. v, vi = {x=0, y=0, z=1}, "z"
  137. else
  138. v, vi = {x=1, y=0, z=0}, "x"
  139. end
  140. local chestpos = vector.add(wall.pos, wall.facing)
  141. local off = rand:next(-room.size[vi]/2 + 1, room.size[vi]/2 - 1)
  142. chestpos = vector.add(chestpos, vector.multiply(v, off))
  143. if minetest.get_node(chestpos).name == "air" then
  144. -- make it face inwards to the room
  145. local facedir = minetest.dir_to_facedir(vector.multiply(wall.facing, -1))
  146. minetest.add_node(chestpos, {name = "default:chest", param2 = facedir})
  147. populate_chest(chestpos, PcgRandom(noise3d_integer(noise, chestpos)), room.type)
  148. end
  149. end
  150. end)