Version 1.0.1
[minetest_findbiomes.git] / init.lua
blobce7fd97990778684707e927cb85dc28998c4aa03
1 local S = minetest.get_translator("findbiome")
3 local mod_biomeinfo = minetest.get_modpath("biomeinfo") ~= nil
4 local mg_name = minetest.get_mapgen_setting("mg_name")
5 local water_level = tonumber(minetest.get_mapgen_setting("water_level"))
7 -- Calculate the maximum playable limit
8 local mapgen_limit = tonumber(minetest.get_mapgen_setting("mapgen_limit"))
9 local chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))
10 local playable_limit = math.max(mapgen_limit - (chunksize + 1) * 16, 0)
12 -- Parameters
13 -------------
15 -- Resolution of search grid in nodes.
16 local res = 64
17 -- Number of points checked in the square search grid (edge * edge).
18 local checks = 128 * 128
20 -- End of parameters
21 --------------------
23 -- Direction table
25 local dirs = {
26 {x = 0, y = 0, z = 1},
27 {x = -1, y = 0, z = 0},
28 {x = 0, y = 0, z = -1},
29 {x = 1, y = 0, z = 0},
32 -- Returns true if pos is within the world boundaries
33 local function is_in_world(pos)
34 return not (math.abs(pos.x) > playable_limit or math.abs(pos.y) > playable_limit or math.abs(pos.z) > playable_limit)
35 end
37 -- Checks if pos is within the biome's boundaries. If it isn't, places pos inside the boundaries.
38 local function adjust_pos_to_biome_limits(pos, biome_id)
39 local bpos = table.copy(pos)
40 local biome_name = minetest.get_biome_name(biome_id)
41 local biome = minetest.registered_biomes[biome_name]
42 if not biome then
43 minetest.log("error", "[findbiome] adjust_pos_to_biome_limits non-existing biome!")
44 return bpos, true
45 end
46 local axes = {"y", "x", "z"}
47 local out_of_bounds = false
48 for a=1, #axes do
49 local ax = axes[a]
50 local min, max
51 if biome[ax.."_min"] then
52 min = biome[ax.."_min"]
53 else
54 min = -playable_limit
55 end
56 if biome[ax.."_max"] then
57 max = biome[ax.."_max"]
58 else
59 max = playable_limit
60 end
61 min = tonumber(min)
62 max = tonumber(max)
63 if bpos[ax] < min then
64 out_of_bounds = true
65 bpos[ax] = min
66 if max-min > 16 then
67 bpos[ax] = math.max(bpos[ax] + 8, -playable_limit)
68 end
69 end
70 if bpos[ax] > max then
71 out_of_bounds = true
72 bpos[ax] = max
73 if max-min > 16 then
74 bpos[ax] = math.min(bpos[ax] - 8, playable_limit)
75 end
76 end
77 end
78 return bpos, out_of_bounds
79 end
81 -- Find the special default biome
82 local function find_default_biome()
83 local all_biomes = minetest.registered_biomes
84 local biome_count = 0
85 for b, biome in pairs(all_biomes) do
86 biome_count = biome_count + 1
87 end
88 -- Trivial case: No biomes registered, default biome is everywhere.
89 if biome_count == 0 then
90 local y = minetest.get_spawn_level(0, 0)
91 if not y then
92 y = 0
93 end
94 return { x = 0, y = y, z = 0 }
95 end
96 local pos = {}
97 -- Just check a lot of random positions
98 -- It's a crappy algorithm but better than nothing.
99 for i=1, 100 do
100 pos.x = math.random(-playable_limit, playable_limit)
101 pos.y = math.random(-playable_limit, playable_limit)
102 pos.z = math.random(-playable_limit, playable_limit)
103 local biome_data = minetest.get_biome_data(pos)
104 if biome_data and minetest.get_biome_name(biome_data.biome) == "default" then
105 return pos
108 return nil
111 local function find_biome(pos, biomes)
112 pos = vector.round(pos)
113 -- Pos: Starting point for biome checks. This also sets the y co-ordinate for all
114 -- points checked, so the suitable biomes must be active at this y.
116 -- Initial variables
118 local edge_len = 1
119 local edge_dist = 0
120 local dir_step = 0
121 local dir_ind = 1
122 local success = false
123 local spawn_pos
124 local biome_ids
126 -- Get next position on square search spiral
127 local function next_pos()
128 if edge_dist == edge_len then
129 edge_dist = 0
130 dir_ind = dir_ind + 1
131 if dir_ind == 5 then
132 dir_ind = 1
134 dir_step = dir_step + 1
135 edge_len = math.floor(dir_step / 2) + 1
138 local dir = dirs[dir_ind]
139 local move = vector.multiply(dir, res)
141 edge_dist = edge_dist + 1
143 return vector.add(pos, move)
146 -- Position search
147 local function search()
148 local attempt = 1
149 while attempt < 3 do
150 for iter = 1, checks do
151 local biome_data = minetest.get_biome_data(pos)
152 -- Sometimes biome_data is nil
153 local biome = biome_data and biome_data.biome
154 for id_ind = 1, #biome_ids do
155 local biome_id = biome_ids[id_ind]
156 pos = adjust_pos_to_biome_limits(pos, biome_id)
157 local spos = table.copy(pos)
158 if biome == biome_id then
159 local good_spawn_height = pos.y <= water_level + 16 and pos.y >= water_level
160 local spawn_y = minetest.get_spawn_level(spos.x, spos.z)
161 if spawn_y then
162 spawn_pos = {x = spos.x, y = spawn_y, z = spos.z}
163 elseif not good_spawn_height then
164 spawn_pos = {x = spos.x, y = spos.y, z = spos.z}
165 elseif attempt >= 2 then
166 spawn_pos = {x = spos.x, y = spos.y, z = spos.z}
168 if spawn_pos then
169 local adjusted_pos, outside = adjust_pos_to_biome_limits(spawn_pos, biome_id)
170 if is_in_world(spawn_pos) and not outside then
171 return true
177 pos = next_pos()
179 attempt = attempt + 1
181 return false
183 local function search_v6()
184 if not mod_biomeinfo then return
185 false
187 for iter = 1, checks do
188 local found_biome = biomeinfo.get_v6_biome(pos)
189 for i = 1, #biomes do
190 local searched_biome = biomes[i]
191 if found_biome == searched_biome then
192 local spawn_y = minetest.get_spawn_level(pos.x, pos.z)
193 if spawn_y then
194 spawn_pos = {x = pos.x, y = spawn_y, z = pos.z}
195 if is_in_world(spawn_pos) then
196 return true
202 pos = next_pos()
205 return false
208 if mg_name == "v6" then
209 success = search_v6()
210 else
211 -- Table of suitable biomes
212 biome_ids = {}
213 for i=1, #biomes do
214 local id = minetest.get_biome_id(biomes[i])
215 if not id then
216 return nil, false
218 table.insert(biome_ids, id)
220 success = search()
222 return spawn_pos, success
226 local mods_loaded = false
227 minetest.register_on_mods_loaded(function()
228 mods_loaded = true
229 end)
231 -- Regiver chat commands
233 minetest.register_chatcommand("findbiome", {
234 description = S("Find and teleport to biome"),
235 params = S("<biome>"),
236 privs = { debug = true, teleport = true },
237 func = function(name, param)
238 if not mods_loaded then
239 return false
241 local player = minetest.get_player_by_name(name)
242 if not player then
243 return false, S("No player.")
245 local pos = player:get_pos()
246 local invalid_biome = true
247 if mg_name == "v6" then
248 if not mod_biomeinfo then
249 return false, S("Not supported. The “biomeinfo” mod is required for v6 mapgen support!")
251 local biomes = biomeinfo.get_active_v6_biomes()
252 for b=1, #biomes do
253 if param == biomes[b] then
254 invalid_biome = false
255 break
258 else
259 if param == "default" then
260 local biome_pos = find_default_biome()
261 if biome_pos then
262 player:set_pos(biome_pos)
263 return true, S("Biome found at @1.", minetest.pos_to_string(biome_pos))
264 else
265 return false, S("No biome found!")
268 local id = minetest.get_biome_id(param)
269 if id then
270 invalid_biome = false
273 if invalid_biome then
274 return false, S("Biome does not exist!")
276 local biome_pos, success = find_biome(pos, {param})
277 if success then
278 player:set_pos(biome_pos)
279 return true, S("Biome found at @1.", minetest.pos_to_string(biome_pos))
280 else
281 return false, S("No biome found!")
283 end,
286 minetest.register_chatcommand("listbiomes", {
287 description = S("List all biomes"),
288 params = "",
289 privs = { debug = true },
290 func = function(name, param)
291 if not mods_loaded then
292 return false
294 local biomes
295 local b = 0
296 if mg_name == "v6" then
297 if not mod_biomeinfo then
298 return false, S("Not supported. The “biomeinfo” mod is required for v6 mapgen support!")
300 biomes = biomeinfo.get_active_v6_biomes()
301 b = #biomes
302 else
303 biomes = {}
304 for k,v in pairs(minetest.registered_biomes) do
305 table.insert(biomes, k)
306 b = b + 1
309 if b == 0 then
310 return true, S("No biomes.")
311 else
312 table.sort(biomes)
313 for b=1, #biomes do
314 minetest.chat_send_player(name, biomes[b])
316 return true
318 end,