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