· 4 years ago · Apr 05, 2021, 01:58 PM
1-- Biome library mod by VanessaE
2--
3-- I got the temperature map idea from "hmmmm", values used for it came from
4-- Splizard's snow mod.
5--
6
7-- Various settings - most of these probably won't need to be changed
8
9biome_lib = {}
10biome_lib.air = {name = "air"}
11
12biome_lib.blocklist_aircheck = {}
13biome_lib.blocklist_no_aircheck = {}
14
15biome_lib.surface_nodes_aircheck = {}
16biome_lib.surface_nodes_no_aircheck = {}
17
18biome_lib.surfaceslist_aircheck = {}
19biome_lib.surfaceslist_no_aircheck = {}
20
21biome_lib.actioncount_aircheck = {}
22biome_lib.actioncount_no_aircheck = {}
23
24biome_lib.actionslist_aircheck = {}
25biome_lib.actionslist_no_aircheck = {}
26
27biome_lib.modpath = minetest.get_modpath("biome_lib")
28
29biome_lib.total_no_aircheck_calls = 0
30
31biome_lib.queue_run_ratio = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or 100
32
33local function tableize(s)
34 return string.split(string.trim(string.gsub(s, " ", "")))
35end
36
37local c1 minetest.settings:get("biome_lib_default_grow_through_nodes")
38biome_lib.default_grow_through_nodes = {["air"] = true}
39if c1 then
40 for _, i in ipairs(tableize(c1)) do
41 biome_lib.default_grow_through_nodes[i] = true
42 end
43else
44 biome_lib.default_grow_through_nodes["default:snow"] = true
45end
46
47local c2 minetest.settings:get("biome_lib_default_water_nodes")
48biome_lib.default_water_nodes = {}
49if c2 then
50 for _, i in ipairs(tableize(c2)) do
51 biome_lib.default_water_nodes[i] = true
52 end
53else
54 biome_lib.default_water_nodes["default:water_source"] = true
55 biome_lib.default_water_nodes["default:water_flowing"] = true
56 biome_lib.default_water_nodes["default:river_water_source"] = true
57 biome_lib.default_water_nodes["default:river_water_flowing"] = true
58end
59
60local c3 = minetest.settings:get("biome_lib_default_wet_surfaces")
61local c4 = minetest.settings:get("biome_lib_default_ground_nodes")
62local c5 = minetest.settings:get("biome_lib_default_grow_nodes")
63
64biome_lib.default_wet_surfaces = c3 and tableize(c3) or {"default:dirt", "default:dirt_with_grass", "default:sand"}
65biome_lib.default_ground_nodes = c4 and tableize(c4) or {"default:dirt_with_grass"}
66biome_lib.default_grow_nodes = c5 and tableize(c5) or {"default:dirt_with_grass"}
67
68-- Boilerplate to support localized strings if intllib mod is installed.
69local S
70if minetest.global_exists("intllib") then
71 if intllib.make_gettext_pair then
72 S = intllib.make_gettext_pair()
73 else
74 S = intllib.Getter()
75 end
76else
77 S = function(s) return s end
78end
79biome_lib.intllib = S
80
81local DEBUG = minetest.settings:get_bool("biome_lib_debug", false)
82
83function biome_lib:dbg(msg)
84 if DEBUG then
85 print("[Biome Lib] "..msg)
86 minetest.log("verbose", "[Biome Lib] "..msg)
87 end
88end
89
90biome_lib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it
91
92local perlin_octaves = 3
93local perlin_persistence = 0.6
94local perlin_scale = 100
95
96local temperature_seeddiff = 112
97local temperature_octaves = 3
98local temperature_persistence = 0.5
99local temperature_scale = 150
100
101local humidity_seeddiff = 9130
102local humidity_octaves = 3
103local humidity_persistence = 0.5
104local humidity_scale = 250
105
106local time_scale = 1
107local time_speed = tonumber(minetest.settings:get("time_speed"))
108
109if time_speed and time_speed > 0 then
110 time_scale = 72 / time_speed
111end
112
113--PerlinNoise(seed, octaves, persistence, scale)
114
115biome_lib.perlin_temperature = PerlinNoise(temperature_seeddiff, temperature_octaves, temperature_persistence, temperature_scale)
116biome_lib.perlin_humidity = PerlinNoise(humidity_seeddiff, humidity_octaves, humidity_persistence, humidity_scale)
117
118-- Local functions
119
120local function get_biome_data(pos, perlin_fertile)
121 local fertility = perlin_fertile:get_2d({x=pos.x, y=pos.z})
122
123 if type(minetest.get_biome_data) == "function" then
124 local data = minetest.get_biome_data(pos)
125 if data then
126 return fertility, data.heat / 100, data.humidity / 100
127 end
128 end
129
130 local temperature = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z})
131 local humidity = biome_lib.perlin_humidity:get2d({x=pos.x+150, y=pos.z+50})
132
133 return fertility, temperature, humidity
134end
135
136function biome_lib:is_node_loaded(node_pos)
137 local n = minetest.get_node_or_nil(node_pos)
138 if (not n) or (n.name == "ignore") then
139 return false
140 end
141 return true
142end
143
144function biome_lib:set_defaults(biome)
145 biome.seed_diff = biome.seed_diff or 0
146 biome.min_elevation = biome.min_elevation or -31000
147 biome.max_elevation = biome.max_elevation or 31000
148 biome.temp_min = biome.temp_min or 1
149 biome.temp_max = biome.temp_max or -1
150 biome.humidity_min = biome.humidity_min or 1
151 biome.humidity_max = biome.humidity_max or -1
152 biome.plantlife_limit = biome.plantlife_limit or 0.1
153 biome.near_nodes_vertical = biome.near_nodes_vertical or 1
154
155-- specific to on-generate
156
157 biome.neighbors = biome.neighbors or biome.surface
158 biome.near_nodes_size = biome.near_nodes_size or 0
159 biome.near_nodes_count = biome.near_nodes_count or 1
160 biome.rarity = biome.rarity or 50
161 biome.max_count = biome.max_count or 125
162 if biome.check_air ~= false then biome.check_air = true end
163
164-- specific to abm spawner
165 biome.seed_diff = biome.seed_diff or 0
166 biome.light_min = biome.light_min or 0
167 biome.light_max = biome.light_max or 15
168 biome.depth_max = biome.depth_max or 1
169 biome.facedir = biome.facedir or 0
170end
171
172local function search_table(t, s)
173 for i = 1, #t do
174 if t[i] == s then return true end
175 end
176 return false
177end
178
179-- register the list of surfaces to spawn stuff on, filtering out all duplicates.
180-- separate the items by air-checking or non-air-checking map eval methods
181
182function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
183
184 -- if calling code passes an undefined node for a surface or
185 -- as a node to be spawned, don't register an action for it.
186
187 if type(nodes_or_function_or_model) == "string"
188 and string.find(nodes_or_function_or_model, ":")
189 and not minetest.registered_nodes[nodes_or_function_or_model] then
190 biome_lib:dbg("Warning: Ignored registration for undefined spawn node: "..dump(nodes_or_function_or_model))
191 return
192 end
193
194 if type(nodes_or_function_or_model) == "string"
195 and not string.find(nodes_or_function_or_model, ":") then
196 biome_lib:dbg("Warning: Registered function call using deprecated string method: "..dump(nodes_or_function_or_model))
197 end
198
199 if biomedef.check_air == false then
200 biome_lib:dbg("Register no-air-check mapgen hook: "..dump(nodes_or_function_or_model))
201 biome_lib.actionslist_no_aircheck[#biome_lib.actionslist_no_aircheck + 1] = { biomedef, nodes_or_function_or_model }
202 local s = biomedef.surface
203 if type(s) == "string" then
204 if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
205 if not search_table(biome_lib.surfaceslist_no_aircheck, s) then
206 biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
207 end
208 else
209 biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s))
210 end
211 else
212 for i = 1, #biomedef.surface do
213 local s = biomedef.surface[i]
214 if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
215 if not search_table(biome_lib.surfaceslist_no_aircheck, s) then
216 biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
217 end
218 else
219 biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s))
220 end
221 end
222 end
223 else
224 biome_lib:dbg("Register with-air-checking mapgen hook: "..dump(nodes_or_function_or_model))
225 biome_lib.actionslist_aircheck[#biome_lib.actionslist_aircheck + 1] = { biomedef, nodes_or_function_or_model }
226 local s = biomedef.surface
227 if type(s) == "string" then
228 if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
229 if not search_table(biome_lib.surfaceslist_aircheck, s) then
230 biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
231 end
232 else
233 biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s))
234 end
235 else
236 for i = 1, #biomedef.surface do
237 local s = biomedef.surface[i]
238 if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
239 if not search_table(biome_lib.surfaceslist_aircheck, s) then
240 biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
241 end
242 else
243 biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s))
244 end
245 end
246 end
247 end
248end
249
250-- Function to check whether a position matches the given biome definition
251-- Returns true when the surface can be populated
252
253local function populate_single_surface(biome, pos, perlin_fertile_area, checkair)
254 local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
255
256 if math.random(1, 100) <= biome.rarity then
257 return
258 end
259
260 local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
261
262 local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
263 and fertility > biome.plantlife_limit
264 and temperature <= biome.temp_min and temperature >= biome.temp_max
265 and humidity <= biome.humidity_min and humidity >= biome.humidity_max
266
267 if not pos_biome_ok then
268 return -- Y position mismatch, outside of biome
269 end
270
271 local biome_surfaces_string = dump(biome.surface)
272 local surface_ok = false
273
274 if not biome.depth then
275 local dest_node = minetest.get_node(pos)
276 if string.find(biome_surfaces_string, dest_node.name) then
277 surface_ok = true
278 else
279 if string.find(biome_surfaces_string, "group:") then
280 for j = 1, #biome.surface do
281 if string.find(biome.surface[j], "^group:")
282 and minetest.get_item_group(dest_node.name, biome.surface[j]) then
283 surface_ok = true
284 break
285 end
286 end
287 end
288 end
289 elseif not string.find(biome_surfaces_string,
290 minetest.get_node({ x = pos.x, y = pos.y-biome.depth-1, z = pos.z }).name) then
291 surface_ok = true
292 end
293
294 if not surface_ok then
295 return -- Surface does not match the given node group/name
296 end
297
298 if checkair and minetest.get_node(p_top).name ~= "air" then
299 return
300 end
301
302 if biome.below_nodes and
303 not string.find(dump(biome.below_nodes),
304 minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name
305 ) then
306 return -- Node below does not match
307 end
308
309 if biome.ncount and
310 #minetest.find_nodes_in_area(
311 {x=pos.x-1, y=pos.y, z=pos.z-1},
312 {x=pos.x+1, y=pos.y, z=pos.z+1},
313 biome.neighbors
314 ) <= biome.ncount then
315 return -- Not enough similar biome nodes around
316 end
317
318 if biome.near_nodes and
319 #minetest.find_nodes_in_area(
320 {x=pos.x-biome.near_nodes_size, y=pos.y-biome.near_nodes_vertical, z=pos.z-biome.near_nodes_size},
321 {x=pos.x+biome.near_nodes_size, y=pos.y+biome.near_nodes_vertical, z=pos.z+biome.near_nodes_size},
322 biome.near_nodes
323 ) < biome.near_nodes_count then
324 return -- Long distance neighbours do not match
325 end
326
327 -- Position fits into given biome
328 return true
329end
330
331function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair)
332 local items_added = 0
333
334 biome_lib:set_defaults(biome)
335
336 -- filter stage 1 - find nodes from the supplied surfaces that are within the current biome.
337
338 local in_biome_nodes = {}
339 local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale)
340
341 for i = 1, #snodes do
342 local pos = vector.new(snodes[i])
343 if populate_single_surface(biome, pos, perlin_fertile_area, checkair) then
344 in_biome_nodes[#in_biome_nodes + 1] = pos
345 end
346 end
347
348 -- filter stage 2 - find places within that biome area to place the plants.
349
350 local num_in_biome_nodes = #in_biome_nodes
351
352 if num_in_biome_nodes == 0 then
353 return 0
354 end
355
356 for i = 1, math.min(math.ceil(biome.max_count/25), num_in_biome_nodes) do
357 local tries = 0
358 local spawned = false
359 while tries < 2 and not spawned do
360 local pos = in_biome_nodes[math.random(1, num_in_biome_nodes)]
361 if biome.spawn_replace_node then
362 pos.y = pos.y-1
363 end
364 local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
365
366 if not (biome.avoid_nodes and biome.avoid_radius
367 and minetest.find_node_near(p_top, biome.avoid_radius
368 + math.random(-1.5,2), biome.avoid_nodes)) then
369 if biome.delete_above then
370 minetest.swap_node(p_top, biome_lib.air)
371 minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z}, biome_lib.air)
372 end
373
374 if biome.delete_above_surround then
375 minetest.swap_node({x=p_top.x-1, y=p_top.y, z=p_top.z}, biome_lib.air)
376 minetest.swap_node({x=p_top.x+1, y=p_top.y, z=p_top.z}, biome_lib.air)
377 minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z-1}, biome_lib.air)
378 minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z+1}, biome_lib.air)
379
380 minetest.swap_node({x=p_top.x-1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
381 minetest.swap_node({x=p_top.x+1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
382 minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z-1}, biome_lib.air)
383 minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z+1}, biome_lib.air)
384 end
385
386 if biome.spawn_replace_node then
387 minetest.swap_node(pos, biome_lib.air)
388 end
389
390 local objtype = type(nodes_or_function_or_model)
391
392 if objtype == "table" then
393 if nodes_or_function_or_model.axiom then
394 biome_lib:generate_tree(p_top, nodes_or_function_or_model)
395 spawned = true
396 else
397 local fdir = nil
398 if biome.random_facedir then
399 fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
400 end
401 minetest.swap_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir })
402 spawned = true
403 end
404 elseif objtype == "string" and
405 minetest.registered_nodes[nodes_or_function_or_model] then
406 local fdir = nil
407 if biome.random_facedir then
408 fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
409 end
410 minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir })
411 spawned = true
412 elseif objtype == "function" then
413 nodes_or_function_or_model(pos)
414 spawned = true
415 elseif objtype == "string" and pcall(loadstring(("return %s(...)"):
416 format(nodes_or_function_or_model)),pos) then
417 spawned = true
418 else
419 biome_lib:dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}")
420 end
421 else
422 tries = tries + 1
423 end
424 end
425 if spawned then items_added = items_added + 1 end
426 end
427 return items_added
428end
429
430-- Primary mapgen spawner, for mods that can work with air checking enabled on
431-- a surface during the initial map read stage.
432
433local function confirm_block_surroundings(pos)
434 return minetest.get_node_or_nil(pos)
435 and minetest.get_node_or_nil({ x=pos.x-1, y=pos.y, z=pos.z })
436 and minetest.get_node_or_nil({ x=pos.x+1, y=pos.y, z=pos.z })
437 and minetest.get_node_or_nil({ x=pos.x, y=pos.y-1, z=pos.z })
438 and minetest.get_node_or_nil({ x=pos.x, y=pos.y+1, z=pos.z })
439 and minetest.get_node_or_nil({ x=pos.x, y=pos.y, z=pos.z-1 })
440 and minetest.get_node_or_nil({ x=pos.x, y=pos.y, z=pos.z+1 })
441end
442
443function biome_lib:generate_block_with_air_checking(shutdown)
444 if not biome_lib.blocklist_aircheck[1] then
445 return
446 end
447
448 local minp = biome_lib.blocklist_aircheck[1][1]
449 local maxp = biome_lib.blocklist_aircheck[1][2]
450
451 -- use the block hash as a unique key into the surface nodes
452 -- tables, so that we can write the tables thread-safely.
453
454 local blockhash = minetest.hash_node_position(minp)
455
456 if not biome_lib.surface_nodes_aircheck.blockhash then -- read it into the block cache
457 biome_lib.surface_nodes_aircheck.blockhash =
458 minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
459 biome_lib.actioncount_aircheck.blockhash = 1
460 if #biome_lib.surface_nodes_aircheck.blockhash > 0 then
461 biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." added, with "..#biome_lib.surface_nodes_aircheck.blockhash.." surface nodes detected.")
462 end
463 elseif not shutdown and not confirm_block_surroundings(minp) then
464 biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck+1] = biome_lib.blocklist_aircheck[1]
465 table.remove(biome_lib.blocklist_aircheck, 1)
466 else
467 if biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash] then
468 -- [1] is biome, [2] is node/function/model
469 local added = biome_lib:populate_surfaces(
470 biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][1],
471 biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][2],
472 biome_lib.surface_nodes_aircheck.blockhash, true)
473 if added > 0 then
474 biome_lib:dbg("Ran biome_lib:populate_surfaces for block at "..minetest.pos_to_string(minp)..
475 ". Entry #"..biome_lib.actioncount_aircheck.blockhash.." added "..added.." items.")
476 end
477 biome_lib.actioncount_aircheck.blockhash = biome_lib.actioncount_aircheck.blockhash + 1
478 else
479 table.remove(biome_lib.blocklist_aircheck, 1)
480 biome_lib.surface_nodes_aircheck.blockhash = nil
481 biome_lib.actioncount_aircheck.blockhash = nil
482 end
483 end
484end
485
486-- Secondary mapgen spawner, for mods that require disabling of
487-- checking for air during the initial map read stage.
488
489function biome_lib:generate_block_no_aircheck(shutdown)
490 if not biome_lib.blocklist_no_aircheck[1] then
491 return
492 end
493
494 local minp = biome_lib.blocklist_no_aircheck[1][1]
495 local maxp = biome_lib.blocklist_no_aircheck[1][2]
496
497 local blockhash = minetest.hash_node_position(minp)
498
499 if not biome_lib.surface_nodes_no_aircheck.blockhash then
500 biome_lib.surface_nodes_no_aircheck.blockhash =
501 minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
502 biome_lib.actioncount_no_aircheck.blockhash = 1
503 elseif not shutdown and not confirm_block_surroundings(minp) then
504 biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck+1] = biome_lib.blocklist_no_aircheck[1]
505 table.remove(biome_lib.blocklist_no_aircheck, 1)
506 else
507 if biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash] then
508 biome_lib:populate_surfaces(
509 biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][1],
510 biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][2],
511 biome_lib.surface_nodes_no_aircheck.blockhash, false)
512 biome_lib.actioncount_no_aircheck.blockhash = biome_lib.actioncount_no_aircheck.blockhash + 1
513 else
514 table.remove(biome_lib.blocklist_no_aircheck, 1)
515 biome_lib.surface_nodes_no_aircheck.blockhash = nil
516 biome_lib.actioncount_no_aircheck.blockhash = nil
517 end
518 end
519end
520
521-- "Play" them back, populating them with new stuff in the process
522
523local step_duration = tonumber(minetest.settings:get("dedicated_server_step"))
524minetest.register_globalstep(function(dtime)
525 if dtime >= step_duration + 0.1 -- don't attempt to populate if lag is already too high
526 or math.random(100) > biome_lib.queue_run_ratio
527 or (#biome_lib.blocklist_aircheck == 0 and #biome_lib.blocklist_no_aircheck == 0) then
528 return
529 end
530
531 biome_lib.globalstep_start_time = minetest.get_us_time()
532 biome_lib.globalstep_runtime = 0
533 while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0)
534 and biome_lib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS.
535 if #biome_lib.blocklist_aircheck > 0 then
536 biome_lib:generate_block_with_air_checking(false)
537 end
538 if #biome_lib.blocklist_no_aircheck > 0 then
539 biome_lib:generate_block_no_aircheck(false)
540 end
541 biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time
542 end
543end)
544
545-- Play out the entire log all at once on shutdown
546-- to prevent unpopulated map areas
547
548minetest.register_on_shutdown(function()
549 if #biome_lib.blocklist_aircheck == 0 then
550 return
551 end
552
553 print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log")
554 print("(there are "..#biome_lib.blocklist_aircheck.." entries)...")
555 while #biome_lib.blocklist_aircheck > 0 do
556 biome_lib:generate_block_with_air_checking(true)
557 end
558end)
559
560minetest.register_on_shutdown(function()
561 if #biome_lib.blocklist_aircheck == 0 then
562 return
563 end
564
565 print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log")
566 print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...")
567 while #biome_lib.blocklist_no_aircheck > 0 do
568 biome_lib:generate_block_no_aircheck(true)
569 end
570end)
571
572-- The spawning ABM
573
574function biome_lib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa)
575
576 local biome = {}
577
578 if type(sd) ~= "table" then
579 biome.spawn_delay = sd -- old api expects ABM interval param here.
580 biome.spawn_plants = {sp}
581 biome.avoid_radius = sr
582 biome.spawn_chance = sc
583 biome.spawn_surfaces = {ss}
584 biome.avoid_nodes = sa
585 else
586 biome = sd
587 end
588
589 if biome.spawn_delay*time_scale >= 1 then
590 biome.interval = biome.spawn_delay*time_scale
591 else
592 biome.interval = 1
593 end
594
595 biome_lib:set_defaults(biome)
596 biome.spawn_plants_count = #(biome.spawn_plants)
597
598 local n
599 if type(biome.spawn_plants) == "table" then
600 n = "random: "..biome.spawn_plants[1]..", ..."
601 else
602 n = biome.spawn_plants
603 end
604 biome.label = biome.label or "biome_lib spawn_on_surfaces(): "..n
605
606 minetest.register_abm({
607 nodenames = biome.spawn_surfaces,
608 interval = biome.interval,
609 chance = biome.spawn_chance,
610 neighbors = biome.neighbors,
611 label = biome.label,
612 action = function(pos, node, active_object_count, active_object_count_wider)
613 local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
614 local n_top = minetest.get_node(p_top)
615 local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale)
616
617 local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
618
619 local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
620 and fertility > biome.plantlife_limit
621 and temperature <= biome.temp_min and temperature >= biome.temp_max
622 and humidity <= biome.humidity_min and humidity >= biome.humidity_max
623 and biome_lib:is_node_loaded(p_top)
624
625 if not pos_biome_ok then
626 return -- Outside of biome
627 end
628
629 local n_light = minetest.get_node_light(p_top, nil)
630 if n_light < biome.light_min or n_light > biome.light_max then
631 return -- Too dark or too bright
632 end
633
634 if biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near(
635 p_top, biome.avoid_radius + math.random(-1.5,2), biome.avoid_nodes) then
636 return -- Nodes to avoid are nearby
637 end
638
639 if biome.neighbors and biome.ncount and
640 #minetest.find_nodes_in_area(
641 {x=pos.x-1, y=pos.y, z=pos.z-1},
642 {x=pos.x+1, y=pos.y, z=pos.z+1},
643 biome.neighbors
644 ) <= biome.ncount then
645 return -- Near neighbour nodes are not present
646 end
647
648 local NEAR_DST = biome.near_nodes_size
649 if biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size and
650 #minetest.find_nodes_in_area(
651 {x=pos.x-NEAR_DST, y=pos.y-biome.near_nodes_vertical, z=pos.z-NEAR_DST},
652 {x=pos.x+NEAR_DST, y=pos.y+biome.near_nodes_vertical, z=pos.z+NEAR_DST},
653 biome.near_nodes
654 ) < biome.near_nodes_count then
655 return -- Far neighbour nodes are not present
656 end
657
658 if (biome.air_count and biome.air_size) and
659 #minetest.find_nodes_in_area(
660 {x=p_top.x-biome.air_size, y=p_top.y, z=p_top.z-biome.air_size},
661 {x=p_top.x+biome.air_size, y=p_top.y, z=p_top.z+biome.air_size},
662 "air"
663 ) < biome.air_count then
664 return -- Not enough air
665 end
666
667 local walldir = biome_lib:find_adjacent_wall(p_top, biome.verticals_list, biome.choose_random_wall)
668 if biome.alt_wallnode and walldir then
669 if n_top.name == "air" then
670 minetest.swap_node(p_top, { name = biome.alt_wallnode, param2 = walldir })
671 end
672 return
673 end
674
675 local currentsurface = minetest.get_node(pos).name
676
677 if biome_lib.default_water_nodes[currentsurface] and
678 #minetest.find_nodes_in_area(
679 {x=pos.x, y=pos.y-biome.depth_max-1, z=pos.z},
680 vector.new(pos),
681 biome_lib.default_wet_surfaces
682 ) == 0 then
683 return -- On water but no ground nearby
684 end
685
686 local rnd = math.random(1, biome.spawn_plants_count)
687 local plant_to_spawn = biome.spawn_plants[rnd]
688 local fdir = biome.facedir
689 if biome.random_facedir then
690 fdir = math.random(biome.random_facedir[1],biome.random_facedir[2])
691 end
692 if type(biome.spawn_plants) == "string" then
693 assert(loadstring(biome.spawn_plants.."(...)"))(pos)
694 elseif not biome.spawn_on_side and not biome.spawn_on_bottom and not biome.spawn_replace_node then
695 if n_top.name == "air" then
696 minetest.swap_node(p_top, { name = plant_to_spawn, param2 = fdir })
697 end
698 elseif biome.spawn_replace_node then
699 minetest.swap_node(pos, { name = plant_to_spawn, param2 = fdir })
700
701 elseif biome.spawn_on_side then
702 local onside = biome_lib:find_open_side(pos)
703 if onside then
704 minetest.swap_node(onside.newpos, { name = plant_to_spawn, param2 = onside.facedir })
705 end
706 elseif biome.spawn_on_bottom then
707 if minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
708 minetest.swap_node({x=pos.x, y=pos.y-1, z=pos.z}, { name = plant_to_spawn, param2 = fdir} )
709 end
710 end
711 end
712 })
713end
714
715-- Function to decide how to replace a plant - either grow it, replace it with
716-- a tree, run a function, or die with an error.
717
718function biome_lib:replace_object(pos, replacement, grow_function, walldir, seeddiff)
719 local growtype = type(grow_function)
720 if growtype == "table" then
721 minetest.swap_node(pos, biome_lib.air)
722 biome_lib:grow_tree(pos, grow_function)
723 return
724 elseif growtype == "function" then
725 local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale)
726 local fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
727 grow_function(pos, fertility, temperature, walldir)
728 return
729 elseif growtype == "string" then
730 local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale)
731 local fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
732 assert(loadstring(grow_function.."(...)"))(pos, fertility, temperature, walldir)
733 return
734 elseif growtype == "nil" then
735 minetest.swap_node(pos, { name = replacement, param2 = walldir})
736 return
737 elseif growtype ~= "nil" and growtype ~= "string" and growtype ~= "table" then
738 error("Invalid grow function "..dump(grow_function).." used on object at ("..dump(pos)..")")
739 end
740end
741
742dofile(biome_lib.modpath .. "/search_functions.lua")
743assert(loadfile(biome_lib.modpath .. "/growth.lua"))(time_scale)
744
745-- Check for infinite stacks
746
747if minetest.get_modpath("unified_inventory") or not minetest.settings:get_bool("creative_mode") then
748 biome_lib.expect_infinite_stacks = false
749else
750 biome_lib.expect_infinite_stacks = true
751end
752
753-- read a field from a node's definition
754
755function biome_lib:get_nodedef_field(nodename, fieldname)
756 if not minetest.registered_nodes[nodename] then
757 return nil
758 end
759 return minetest.registered_nodes[nodename][fieldname]
760end
761
762if DEBUG then
763 biome_lib.last_count_air = 0
764 biome_lib.last_count_no_air = 0
765
766 function biome_lib.show_pending_block_counts()
767 if biome_lib.last_count_air ~= #biome_lib.blocklist_aircheck
768 or biome_lib.last_count_no_air ~= #biome_lib.blocklist_no_aircheck then
769 biome_lib:dbg(string.format("Pending block counts, air: %-7i no-air: %i",
770 #biome_lib.blocklist_aircheck, #biome_lib.blocklist_no_aircheck))
771
772 biome_lib.last_count_air = #biome_lib.blocklist_aircheck
773 biome_lib.last_count_no_air = #biome_lib.blocklist_no_aircheck
774 end
775 minetest.after(1, biome_lib.show_pending_block_counts)
776 end
777
778 biome_lib.show_pending_block_counts()
779
780 minetest.after(0, function()
781 print("Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread")
782 print("across "..#biome_lib.actionslist_aircheck.." actions with air-checking and "..#biome_lib.actionslist_no_aircheck.." actions without.")
783 end)
784
785end
786
787print("[Biome Lib] Loaded")
788