init.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. mob_spawn = mob_spawn or {}
  2. mob_spawn.modpath = minetest.get_modpath("mob_spawn")
  3. mob_spawn.report_name = "MustTest"
  4. mob_spawn.report_mob = ""
  5. -- Ensure we don't get 'attempt to compare number with nil' at runtime.
  6. assert(type(default.LIGHT_MAX) == "number")
  7. mob_spawn.registered = mob_spawn.registered or {}
  8. mob_spawn.players = mob_spawn.players or {}
  9. -- May be adjusted @ runtime.
  10. mob_spawn.server_step = 10
  11. mob_spawn.enable_reports = mob_spawn.enable_reports or false
  12. -- Localize for performance.
  13. local vector_distance = vector.distance
  14. local vector_round = vector.round
  15. local math_random = math.random
  16. local function report(mob, msg)
  17. if mob_spawn.enable_reports then
  18. if mob == mob_spawn.report_mob or mob_spawn.report_mob == "" then
  19. minetest.chat_send_player(mob_spawn.report_name, "[" .. mob .. "]: " .. msg)
  20. end
  21. end
  22. end
  23. -- API function, for registering a mob's spawning data.
  24. function mob_spawn.register_spawn(data)
  25. local tb = {}
  26. -- Name of the mob.
  27. tb.name = data.name or ""
  28. -- Terrain scanning parameters.
  29. tb.node_skip = data.node_skip or 10
  30. tb.node_jitter = data.node_jitter or 10
  31. tb.node_names = data.nodes or {"default:stone"}
  32. tb.spawn_radius = data.spawn_radius or 50
  33. tb.air_offset = data.air_offset or 1
  34. tb.flyswim = data.flyswim or "ground"
  35. -- Min and max duration before mob can be spawned again, after a spawn failure.
  36. -- Smaller values attempt to respawn mobs more frequently, but with more load.
  37. -- Note: saturation time only applies if there are already too many mobs.
  38. tb.saturation_time_min = data.saturation_time_min or 60*1
  39. tb.saturation_time_max = data.saturation_time_max or 60*10
  40. -- Min and max delay before next mob spawn, after a successfull spawn.
  41. tb.success_time_min = data.success_time_min or 1
  42. tb.success_time_max = data.success_time_max or 60
  43. -- Must be 1 or greater.
  44. tb.spawn_chance = data.spawn_chance or 10
  45. -- How many attempts allowed to spawn this mob per iteration?
  46. tb.max_spawns_per_run = data.max_spawns_per_run or 10
  47. -- Does mob want day, night, or doesn't care?
  48. -- If true, mob only spawns in daytime. False means only spawn at night.
  49. -- If nil, mob may spawn at any time.
  50. tb.day_toggle = data.day_toggle or nil
  51. -- How many mobs to spawn at once?
  52. -- This determines how many mobs spawn, when all other checks pass.
  53. tb.min_count = data.min_count or 1
  54. tb.max_count = data.max_count or 3
  55. -- How many mobs of the same kind may exist in the local area?
  56. -- Max number of mobs of the *same type* which may spawn in the same local area.
  57. tb.mob_limit = data.mob_limit or 3
  58. -- Max number of mobs (of any kind) in local area for this mob?
  59. -- Mob will not spawn if there are too many mobs of *any* type in local area.
  60. tb.absolute_mob_limit = data.absolute_mob_limit or 10
  61. -- What is the radius of the mob's local area?
  62. -- What radius to use, when checking for other mobs or players.
  63. tb.mob_range = data.mob_range or 50
  64. -- Mob's light requirements?
  65. -- Mob will not spawn if light is too bright or too dark.
  66. tb.min_light = data.min_light or 0
  67. tb.max_light = data.max_light or default.LIGHT_MAX
  68. -- Mob's desired elevation?
  69. -- Registered mob will not spawn above or below these bounds.
  70. tb.min_height = data.min_height or -31000
  71. tb.max_height = data.max_height or 31000
  72. -- Amount of vertical airspace needed for spawning?
  73. -- Need at least this many vertical air nodes.
  74. tb.clearance = data.clearance or 2
  75. -- Min and max ranges from player before spawning is possible?
  76. -- Mobs will not spawn if player too far or too close to spawn point.
  77. tb.player_min_range = data.player_min_range or 10
  78. tb.player_max_range = data.player_max_range or 50
  79. -- Copy the noiseparams table, if there is one.
  80. -- Mobs can be spawned only if noise is ABOVE the noise threshold.
  81. tb.noise_params = data.noise_params or nil
  82. tb.noise_threshold = data.noise_threshold or 0
  83. -- Create perlin noise object if wanted.
  84. if tb.noise_params then
  85. tb.perlin = PerlinNoise(tb.noise_params)
  86. end
  87. -- If specified, this causes the spawn logic to do an extra check to make sure
  88. -- the final spawn coordinates are within the boundaries of ANY realm. Most
  89. -- mobs won't need this, as their min/max Y-coords preclude spawning in void.
  90. tb.realm_restriction = data.realm_restriction or nil
  91. -- Store the data. We use an indexed array.
  92. -- This allows the same mob to have multiple spawn registrations.
  93. local registered = mob_spawn.registered
  94. registered[#registered+1] = tb
  95. end
  96. function mob_spawn.reinit_player(pname)
  97. local players = mob_spawn.players
  98. -- This is an indexed array.
  99. local registered = mob_spawn.registered
  100. local random = math_random
  101. players[pname] = {}
  102. for k, v in pairs(registered) do
  103. players[pname][k] = {
  104. -- Initial interval. Wait this long before trying to spawn this mob again.
  105. interval = random(v.success_time_min, v.success_time_max)
  106. }
  107. end
  108. end
  109. -- Load mob spawning data.
  110. dofile(mob_spawn.modpath .. "/data.lua")
  111. -- Return 'true' if volume is only a single material.
  112. local function check_space(minp, maxp, material)
  113. local positions, counts = minetest.find_nodes_in_area(minp, maxp, material)
  114. -- Compute number of nodes that can fit in volume.
  115. local x = (maxp.x - minp.x) + 1
  116. local y = (maxp.y - minp.y) + 1
  117. local z = (maxp.z - minp.z) + 1
  118. local t = x * y * z
  119. if counts[material] >= t then
  120. return true
  121. end
  122. end
  123. -- May modify argument as output value.
  124. -- Search both upward and downward (starting from air) to find a non-air node.
  125. -- Depreciated -- too slow.
  126. local function try_locate_ground(pos)
  127. pos = {x=pos.x, y=pos.y, z=pos.z}
  128. local get_node = minetest.get_node
  129. local s = get_node(pos).name
  130. local c = 1
  131. if s == "air" then
  132. -- Search downward.
  133. while s == "air" do
  134. if c > 2 then
  135. return false
  136. end
  137. pos.y = pos.y - 1
  138. c = c + 1
  139. s = get_node(pos).name
  140. end
  141. -- Found non-air. Leave position pointing at non-air node.
  142. return true, pos
  143. elseif s ~= "air" then
  144. -- Search upward.
  145. while s ~= "air" do
  146. if c > 2 then
  147. return false
  148. end
  149. pos.y = pos.y + 1
  150. c = c + 1
  151. s = get_node(pos).name
  152. end
  153. -- Found air. Set position to point to non-air below.
  154. pos.y = pos.y - 1
  155. return true, pos
  156. end
  157. ::cancel::
  158. return false
  159. end
  160. -- Use for flying/swimming mobs.
  161. local function search_flyswim(pos, step, radius, jitter, nodes, offset, height)
  162. local random = math_random
  163. local floor = math.floor
  164. local get_node = minetest.get_node
  165. jitter = floor(jitter)
  166. radius = floor(radius)
  167. step = floor(step)
  168. offset = floor(offset)
  169. -- Height along the Y-axis is halved to reduce the amount of node checks.
  170. local minx = floor(pos.x - radius )
  171. local miny = floor(pos.y - (radius / 2))
  172. local minz = floor(pos.z - radius )
  173. local maxx = floor(pos.x + radius )
  174. local maxy = floor(pos.y + (radius / 2))
  175. local maxz = floor(pos.z + radius )
  176. local results = {}
  177. local gp = {x=0, y=0, z=0}
  178. local sp = {x=0, y=0, z=0}
  179. for x = minx, maxx, step do
  180. for y = miny, maxy, step do
  181. for z = minz, maxz, step do
  182. gp.x = x + random(-jitter, jitter)
  183. gp.y = y + random(-jitter, jitter)
  184. gp.z = z + random(-jitter, jitter)
  185. local bw = get_node(gp).name
  186. for i = 1, #nodes do
  187. if bw == nodes[i] then
  188. -- A volume of radius 1 should just check 1 node.
  189. local r = height - 1
  190. if check_space(vector.subtract(gp, r), vector.add(gp, r), bw) then
  191. results[#results+1] = table.copy(gp)
  192. break
  193. end
  194. end
  195. end
  196. end
  197. end
  198. end
  199. return results
  200. end
  201. -- Use for ground/surface mobs.
  202. local function search_terrain(pos, step, radius, jitter, nodes, offset, height)
  203. local random = math_random
  204. local floor = math.floor
  205. local get_node = minetest.get_node
  206. jitter = floor(jitter)
  207. radius = floor(radius)
  208. step = floor(step)
  209. offset = floor(offset)
  210. -- Height along the Y-axis is halved to reduce the amount of node checks.
  211. local minx = floor(pos.x - radius )
  212. local miny = floor(pos.y - (radius / 2))
  213. local minz = floor(pos.z - radius )
  214. local maxx = floor(pos.x + radius )
  215. local maxy = floor(pos.y + (radius / 2))
  216. local maxz = floor(pos.z + radius )
  217. local results = {}
  218. local gp = {x=0, y=0, z=0}
  219. local sp = {x=0, y=0, z=0}
  220. for x = minx, maxx, step do
  221. for y = miny, maxy, step do
  222. for z = minz, maxz, step do
  223. gp.x = x + random(-jitter, jitter)
  224. gp.y = y + random(-jitter, jitter)
  225. gp.z = z + random(-jitter, jitter)
  226. local bw = get_node(gp).name
  227. for i = 1, #nodes do
  228. if bw == nodes[i] then
  229. -- There must be nothing but enough air above.
  230. if check_space(vector.add(gp, {x=0, y=offset, z=0}), vector.add(gp, {x=0, y=offset+(height-1), z=0}), "air") then
  231. results[#results+1] = {x=gp.x, y=gp.y+offset, z=gp.z}
  232. break
  233. end
  234. end
  235. end
  236. end
  237. end
  238. end
  239. return results
  240. end
  241. local function execute_spawners()
  242. local players = mob_spawn.players
  243. -- This is an indexed array.
  244. local mobdefs = mob_spawn.registered
  245. local step = mob_spawn.server_step
  246. local random = math_random
  247. -- For each player online.
  248. for pname, k in pairs(players) do
  249. -- For each mobtype defined.
  250. for index, mdef in ipairs(mobdefs) do
  251. local pmdata = players[pname][index]
  252. if pmdata.interval == 0 then
  253. local mname = mdef.name
  254. local count, saturated = mob_spawn.spawn_mobs(pname, index)
  255. if count > 0 then
  256. -- Mobs were spawned. Spawn more mobs soon.
  257. -- Set the wait timer to expire in a bit.
  258. pmdata.interval = random(mdef.success_time_min, mdef.success_time_max)
  259. report(mname, "Spawned " .. count .. "!")
  260. else
  261. if saturated then
  262. -- No mobs spawned. Bad environment or area saturated, wait a while.
  263. -- Reset the wait timer.
  264. -- Use random duration to prevent thundering herd.
  265. pmdata.interval = random(mdef.saturation_time_min, mdef.saturation_time_max)
  266. report(mname, "Mob saturated!")
  267. else
  268. pmdata.interval = random(mdef.success_time_min, mdef.success_time_max)
  269. end
  270. end
  271. else
  272. -- Decrease time remaining until this mob can be spawned again.
  273. local int = pmdata.interval
  274. int = int - step
  275. if int < 0 then
  276. int = 0
  277. end
  278. pmdata.interval = int
  279. end
  280. end
  281. end
  282. end
  283. local time = 0
  284. local step = mob_spawn.server_step
  285. -- Called from the MT engine.
  286. function mob_spawn.on_globalstep(dtime)
  287. time = time + dtime
  288. if time < step then
  289. return
  290. end
  291. time = 0
  292. -- Profile function execution time.
  293. --local t1 = os.clock()
  294. execute_spawners()
  295. -- Calculate elapsed time.
  296. --local t2 = os.clock()
  297. --local totalms = math.ceil((t2 - t1) * 1000)
  298. --minetest.chat_send_player("MustTest", "Took " .. totalms .. " ms!")
  299. end
  300. -- Count how many mobs exist in a given area with a radius.
  301. -- Returns [mob count], [abs mob count]
  302. function mob_spawn.check_population(mname, spos, radius)
  303. -- Count mobs in mob range.
  304. -- We can do this check just for the center position, instead of for each point.
  305. local mob_count = 0
  306. local abs_count = 0
  307. local entities = minetest.get_objects_inside_radius(spos, radius)
  308. for j = 1, #entities do
  309. local entity = entities[j]
  310. if not entity:is_player() then
  311. local ref = entity:get_luaentity()
  312. if ref then
  313. if ref.name == mname then
  314. mob_count = mob_count + 1
  315. end
  316. if ref.mob then
  317. -- Absolute mob count.
  318. abs_count = abs_count + 1
  319. end
  320. end
  321. end
  322. end
  323. return mob_count, abs_count
  324. end
  325. -- API function. Spawns mobs around a player, if possible.
  326. -- Returns [num mobs spawned], [saturated|nil]
  327. function mob_spawn.spawn_mobs(pname, index)
  328. local player = minetest.get_player_by_name(pname)
  329. if not player then
  330. return 0
  331. end
  332. local mdef = mob_spawn.registered[index]
  333. if not mdef then
  334. return 0
  335. end
  336. -- Get mob's name.
  337. local mname = mdef.name
  338. local random = math_random
  339. -- Mobs have a 1 in X chance of spawning on this cycle.
  340. if random(1, mdef.spawn_chance) ~= 1 then
  341. report(mname, "Skipping mob due to low chance.")
  342. return 0
  343. end
  344. if mname == "iceman:iceman" then
  345. if not snow.should_spawn_icemen() then
  346. report(mname, "Not the season for icemen.")
  347. return 0
  348. end
  349. end
  350. -- Can mob spawn at this time of day?
  351. local daynight = mdef.day_toggle
  352. -- If toggle set to nil then ignore day/night check.
  353. if daynight ~= nil then
  354. local tod = (minetest.get_timeofday() or 0) * 24000
  355. if tod > 4500 and tod < 19500 then
  356. -- Daylight, but mob wants night.
  357. if daynight == false then
  358. report(mname, "Mob wants night time!")
  359. return 0
  360. end
  361. else
  362. -- Night time but mob wants day.
  363. if daynight == true then
  364. report(mname, "Mob wants day time!")
  365. return 0
  366. end
  367. end
  368. end
  369. local spos = vector_round(player:get_pos())
  370. -- Check if height levels are ok.
  371. -- We only bother checking the center point.
  372. local max_height = mdef.max_height
  373. local min_height = mdef.min_height
  374. if spos.y > max_height or spos.y < min_height then
  375. report(mname, "Bad elevation!")
  376. return 0
  377. end
  378. -- Mobs rarely spawn in the colonies. They keep killing the noobs!
  379. if vector_distance(spos, {x=0, y=0, z=0}) < 100 then
  380. if random(1, 10) < 10 then
  381. return 0
  382. end
  383. elseif vector_distance(spos, {x=0, y=-30790, z=0}) < 100 then
  384. if random(1, 10) < 10 then
  385. return 0
  386. end
  387. end
  388. -- If have perlin object, then check if mob can spawn in this location.
  389. if mdef.perlin and mdef.noise_threshold then
  390. local noise = mdef.perlin:get_3d(spos)
  391. if noise < mdef.noise_threshold then
  392. report(mname, "Mob needs more noise! " .. noise)
  393. return 0
  394. end
  395. end
  396. local get_node = minetest.get_node
  397. local pi = math.pi
  398. local vector_new = vector.new
  399. local vector_add = vector.add
  400. local add_entity = minetest.add_entity
  401. local attempts = mdef.max_spawns_per_run
  402. local max_light = mdef.max_light
  403. local min_light = mdef.min_light
  404. local mob_range = mdef.mob_range
  405. local mob_limit = mdef.mob_limit
  406. local abs_limit = mdef.absolute_mob_limit
  407. local player_min_range = mdef.player_min_range
  408. local player_max_range = mdef.player_max_range
  409. local min_count = mdef.min_count
  410. local max_count = mdef.max_count
  411. local clearance = mdef.clearance
  412. local players = minetest.get_connected_players()
  413. local step = mdef.node_skip
  414. local radius = mdef.spawn_radius
  415. local jitter = mdef.node_jitter
  416. local names = mdef.node_names
  417. local offset = mdef.air_offset
  418. -- Find potential spawn points around player location.
  419. local points
  420. if mdef.flyswim == "ground" then
  421. points = search_terrain(spos, step, radius, jitter, names, offset, clearance)
  422. elseif mdef.flyswim == "flyswim" then
  423. points = search_flyswim(spos, step, radius, jitter, names, offset, clearance)
  424. end
  425. report(mname, "Found " .. #points .. " spawn point(s) @ " .. minetest.pos_to_string(spos) .. "!")
  426. -- Prevent a crash when accessing the array later.
  427. if #points < 1 then
  428. -- No mobs spawned, environment no good (so we can call it saturated).
  429. return 0, true
  430. end
  431. -- Record number of mobs successfully spawned.
  432. local mobs_spawned = 0
  433. local mobs_saturated
  434. for i = 1, attempts do
  435. -- Pick a random point for each spawn attempt. Prevents bunching.
  436. local pos = points[random(1, #points)]
  437. report(mname, "Attempting to spawn mob @ " .. minetest.pos_to_string(pos) .. "!")
  438. -- For spawns that require it, don't spawn outside of the world.
  439. if mdef.realm_restriction then
  440. if not rc.is_valid_realm_pos(pos) then
  441. goto next_spawn
  442. end
  443. end
  444. -- Count mobs in mob range.
  445. -- Don't spawn mob if there are already too many mobs in area.
  446. local mob_count, abs_count = mob_spawn.check_population(mname, pos, mob_range)
  447. if mob_count >= mob_limit or abs_count >= abs_limit then
  448. mobs_saturated = true
  449. goto next_spawn
  450. end
  451. -- Check if light level is ok.
  452. -- We perform this check for each possible position.
  453. local light = minetest.get_node_light(pos)
  454. if not light or light > max_light or light < min_light then
  455. report(mname, "Bad light level!")
  456. goto next_spawn
  457. end
  458. -- Find nearest player.
  459. local nearest_dist = player_max_range + 1
  460. for j = 1, #players do
  461. local p = players[j]:get_pos()
  462. local d = vector_distance(pos, p)
  463. if d < nearest_dist then
  464. nearest_dist = d
  465. end
  466. end
  467. -- Don't spawn if too near player or too far.
  468. if nearest_dist < player_min_range or nearest_dist > player_max_range then
  469. report(mname, "Player too near or player too far!")
  470. goto next_spawn
  471. end
  472. -- Spawn a random number of mobs.
  473. local num_to_spawn = random(min_count, max_count)
  474. for i = 1, num_to_spawn, 1 do
  475. -- Slightly randomize horizontal positioning.
  476. local p2 = {x=random(-5, 5)/10, y=0.5, z=random(-5, 5)/10}
  477. if mdef.add_entity_func then
  478. mdef.add_entity_func(vector_add(pos, p2))
  479. else
  480. local mob = add_entity(vector_add(pos, p2), mname)
  481. if mob then
  482. local ent = mob:get_luaentity()
  483. if ent then
  484. mob:setyaw((random(0, 360) - 180) / 180 * pi)
  485. mobs_spawned = mobs_spawned + 1
  486. report(mname, "Successfully spawned a mob!")
  487. else
  488. mob:remove()
  489. end
  490. end
  491. end
  492. end
  493. ::next_spawn::
  494. end
  495. return mobs_spawned, mobs_saturated
  496. end
  497. function mob_spawn.on_joinplayer(player)
  498. local pname = player:get_player_name()
  499. mob_spawn.reinit_player(pname)
  500. end
  501. function mob_spawn.on_leaveplayer(player)
  502. mob_spawn.players[player:get_player_name()] = nil
  503. end
  504. if not mob_spawn.run_once then
  505. minetest.register_globalstep(function(...)
  506. mob_spawn.on_globalstep(...)
  507. end)
  508. minetest.register_on_joinplayer(function(...)
  509. return mob_spawn.on_joinplayer(...)
  510. end)
  511. minetest.register_on_leaveplayer(function(...)
  512. return mob_spawn.on_leaveplayer(...)
  513. end)
  514. local c = "mob_spawn:core"
  515. local f = mob_spawn.modpath .. "/init.lua"
  516. reload.register_file(c, f, false)
  517. -- Load spawn secrets if present. This should be the last file loaded, in
  518. -- order to allow it to override stuff. File must be excluded from source
  519. -- control! Otherwise it will not be secret. D'oh.
  520. reload.register_optional("mob_spawn:secrets",
  521. mob_spawn.modpath .. "/secrets.sec")
  522. mob_spawn.run_once = true
  523. end