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