init.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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. -- Store the data. We use an indexed array.
  88. -- This allows the same mob to have multiple spawn registrations.
  89. local registered = mob_spawn.registered
  90. registered[#registered+1] = tb
  91. end
  92. function mob_spawn.reinit_player(pname)
  93. local players = mob_spawn.players
  94. -- This is an indexed array.
  95. local registered = mob_spawn.registered
  96. local random = math_random
  97. players[pname] = {}
  98. for k, v in pairs(registered) do
  99. players[pname][k] = {
  100. -- Initial interval. Wait this long before trying to spawn this mob again.
  101. interval = random(v.success_time_min, v.success_time_max)
  102. }
  103. end
  104. end
  105. -- Load mob spawning data.
  106. dofile(mob_spawn.modpath .. "/data.lua")
  107. -- Return 'true' if volume is only a single material.
  108. local function check_space(minp, maxp, material)
  109. local positions, counts = minetest.find_nodes_in_area(minp, maxp, material)
  110. -- Compute number of nodes that can fit in volume.
  111. local x = (maxp.x - minp.x) + 1
  112. local y = (maxp.y - minp.y) + 1
  113. local z = (maxp.z - minp.z) + 1
  114. local t = x * y * z
  115. if counts[material] >= t then
  116. return true
  117. end
  118. end
  119. -- May modify argument as output value.
  120. -- Search both upward and downward (starting from air) to find a non-air node.
  121. -- Depreciated -- too slow.
  122. local function try_locate_ground(pos)
  123. pos = {x=pos.x, y=pos.y, z=pos.z}
  124. local get_node = minetest.get_node
  125. local s = get_node(pos).name
  126. local c = 1
  127. if s == "air" then
  128. -- Search downward.
  129. while s == "air" do
  130. if c > 2 then
  131. return false
  132. end
  133. pos.y = pos.y - 1
  134. c = c + 1
  135. s = get_node(pos).name
  136. end
  137. -- Found non-air. Leave position pointing at non-air node.
  138. return true, pos
  139. elseif s ~= "air" then
  140. -- Search upward.
  141. while s ~= "air" do
  142. if c > 2 then
  143. return false
  144. end
  145. pos.y = pos.y + 1
  146. c = c + 1
  147. s = get_node(pos).name
  148. end
  149. -- Found air. Set position to point to non-air below.
  150. pos.y = pos.y - 1
  151. return true, pos
  152. end
  153. ::cancel::
  154. return false
  155. end
  156. -- Use for flying/swimming mobs.
  157. local function search_flyswim(pos, step, radius, jitter, nodes, offset, height)
  158. local random = math_random
  159. local floor = math.floor
  160. local get_node = minetest.get_node
  161. jitter = floor(jitter)
  162. radius = floor(radius)
  163. step = floor(step)
  164. offset = floor(offset)
  165. -- Height along the Y-axis is halved to reduce the amount of node checks.
  166. local minx = floor(pos.x - radius )
  167. local miny = floor(pos.y - (radius / 2))
  168. local minz = floor(pos.z - radius )
  169. local maxx = floor(pos.x + radius )
  170. local maxy = floor(pos.y + (radius / 2))
  171. local maxz = floor(pos.z + radius )
  172. local results = {}
  173. local gp = {x=0, y=0, z=0}
  174. local sp = {x=0, y=0, z=0}
  175. for x = minx, maxx, step do
  176. for y = miny, maxy, step do
  177. for z = minz, maxz, step do
  178. gp.x = x + random(-jitter, jitter)
  179. gp.y = y + random(-jitter, jitter)
  180. gp.z = z + random(-jitter, jitter)
  181. local bw = get_node(gp).name
  182. for i = 1, #nodes do
  183. if bw == nodes[i] then
  184. -- A volume of radius 1 should just check 1 node.
  185. local r = height - 1
  186. if check_space(vector.subtract(gp, r), vector.add(gp, r), bw) then
  187. results[#results+1] = table.copy(gp)
  188. break
  189. end
  190. end
  191. end
  192. end
  193. end
  194. end
  195. return results
  196. end
  197. -- Use for ground/surface mobs.
  198. local function search_terrain(pos, step, radius, jitter, nodes, offset, height)
  199. local random = math_random
  200. local floor = math.floor
  201. local get_node = minetest.get_node
  202. jitter = floor(jitter)
  203. radius = floor(radius)
  204. step = floor(step)
  205. offset = floor(offset)
  206. -- Height along the Y-axis is halved to reduce the amount of node checks.
  207. local minx = floor(pos.x - radius )
  208. local miny = floor(pos.y - (radius / 2))
  209. local minz = floor(pos.z - radius )
  210. local maxx = floor(pos.x + radius )
  211. local maxy = floor(pos.y + (radius / 2))
  212. local maxz = floor(pos.z + radius )
  213. local results = {}
  214. local gp = {x=0, y=0, z=0}
  215. local sp = {x=0, y=0, z=0}
  216. for x = minx, maxx, step do
  217. for y = miny, maxy, step do
  218. for z = minz, maxz, step do
  219. gp.x = x + random(-jitter, jitter)
  220. gp.y = y + random(-jitter, jitter)
  221. gp.z = z + random(-jitter, jitter)
  222. local bw = get_node(gp).name
  223. for i = 1, #nodes do
  224. if bw == nodes[i] then
  225. -- There must be nothing but enough air above.
  226. 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
  227. results[#results+1] = {x=gp.x, y=gp.y+offset, z=gp.z}
  228. break
  229. end
  230. end
  231. end
  232. end
  233. end
  234. end
  235. return results
  236. end
  237. local function execute_spawners()
  238. local players = mob_spawn.players
  239. -- This is an indexed array.
  240. local mobdefs = mob_spawn.registered
  241. local step = mob_spawn.server_step
  242. local random = math_random
  243. -- For each player online.
  244. for pname, k in pairs(players) do
  245. -- For each mobtype defined.
  246. for index, mdef in ipairs(mobdefs) do
  247. local pmdata = players[pname][index]
  248. if pmdata.interval == 0 then
  249. local mname = mdef.name
  250. local count, saturated = mob_spawn.spawn_mobs(pname, index)
  251. if count > 0 then
  252. -- Mobs were spawned. Spawn more mobs soon.
  253. -- Set the wait timer to expire in a bit.
  254. pmdata.interval = random(mdef.success_time_min, mdef.success_time_max)
  255. report(mname, "Spawned " .. count .. "!")
  256. else
  257. if saturated then
  258. -- No mobs spawned. Bad environment or area saturated, wait a while.
  259. -- Reset the wait timer.
  260. -- Use random duration to prevent thundering herd.
  261. pmdata.interval = random(mdef.saturation_time_min, mdef.saturation_time_max)
  262. report(mname, "Mob saturated!")
  263. else
  264. pmdata.interval = random(mdef.success_time_min, mdef.success_time_max)
  265. end
  266. end
  267. else
  268. -- Decrease time remaining until this mob can be spawned again.
  269. local int = pmdata.interval
  270. int = int - step
  271. if int < 0 then
  272. int = 0
  273. end
  274. pmdata.interval = int
  275. end
  276. end
  277. end
  278. end
  279. local time = 0
  280. local step = mob_spawn.server_step
  281. -- Called from the MT engine.
  282. function mob_spawn.on_globalstep(dtime)
  283. time = time + dtime
  284. if time < step then
  285. return
  286. end
  287. time = 0
  288. -- Profile function execution time.
  289. --local t1 = os.clock()
  290. execute_spawners()
  291. -- Calculate elapsed time.
  292. --local t2 = os.clock()
  293. --local totalms = math.ceil((t2 - t1) * 1000)
  294. --minetest.chat_send_player("MustTest", "Took " .. totalms .. " ms!")
  295. end
  296. -- Count how many mobs exist in a given area with a radius.
  297. -- Returns [mob count], [abs mob count]
  298. function mob_spawn.check_population(mname, spos, radius)
  299. -- Count mobs in mob range.
  300. -- We can do this check just for the center position, instead of for each point.
  301. local mob_count = 0
  302. local abs_count = 0
  303. local entities = minetest.get_objects_inside_radius(spos, radius)
  304. for j = 1, #entities do
  305. local entity = entities[j]
  306. if not entity:is_player() then
  307. local ref = entity:get_luaentity()
  308. if ref then
  309. if ref.name == mname then
  310. mob_count = mob_count + 1
  311. end
  312. if ref.mob then
  313. -- Absolute mob count.
  314. abs_count = abs_count + 1
  315. end
  316. end
  317. end
  318. end
  319. return mob_count, abs_count
  320. end
  321. -- API function. Spawns mobs around a player, if possible.
  322. -- Returns [num mobs spawned], [saturated|nil]
  323. function mob_spawn.spawn_mobs(pname, index)
  324. local player = minetest.get_player_by_name(pname)
  325. if not player then
  326. return 0
  327. end
  328. local mdef = mob_spawn.registered[index]
  329. if not mdef then
  330. return 0
  331. end
  332. -- Get mob's name.
  333. local mname = mdef.name
  334. local random = math_random
  335. -- Mobs have a 1 in X chance of spawning on this cycle.
  336. if random(1, mdef.spawn_chance) ~= 1 then
  337. report(mname, "Skipping mob due to low chance.")
  338. return 0
  339. end
  340. if mname == "iceman:iceman" then
  341. if not snow.should_spawn_icemen() then
  342. report(mname, "Not the season for icemen.")
  343. return 0
  344. end
  345. end
  346. -- Can mob spawn at this time of day?
  347. local daynight = mdef.day_toggle
  348. -- If toggle set to nil then ignore day/night check.
  349. if daynight ~= nil then
  350. local tod = (minetest.get_timeofday() or 0) * 24000
  351. if tod > 4500 and tod < 19500 then
  352. -- Daylight, but mob wants night.
  353. if daynight == false then
  354. report(mname, "Mob wants night time!")
  355. return 0
  356. end
  357. else
  358. -- Night time but mob wants day.
  359. if daynight == true then
  360. report(mname, "Mob wants day time!")
  361. return 0
  362. end
  363. end
  364. end
  365. local spos = vector_round(player:get_pos())
  366. -- Check if height levels are ok.
  367. -- We only bother checking the center point.
  368. local max_height = mdef.max_height
  369. local min_height = mdef.min_height
  370. if spos.y > max_height or spos.y < min_height then
  371. report(mname, "Bad elevation!")
  372. return 0
  373. end
  374. -- Mobs rarely spawn in the colonies. They keep killing the noobs!
  375. if vector_distance(spos, {x=0, y=0, z=0}) < 100 then
  376. if random(1, 10) < 10 then
  377. return 0
  378. end
  379. elseif vector_distance(spos, {x=0, y=-30790, z=0}) < 100 then
  380. if random(1, 10) < 10 then
  381. return 0
  382. end
  383. end
  384. -- If have perlin object, then check if mob can spawn in this location.
  385. if mdef.perlin and mdef.noise_threshold then
  386. local noise = mdef.perlin:get_3d(spos)
  387. if noise < mdef.noise_threshold then
  388. report(mname, "Mob needs more noise! " .. noise)
  389. return 0
  390. end
  391. end
  392. local get_node = minetest.get_node
  393. local pi = math.pi
  394. local vector_new = vector.new
  395. local vector_add = vector.add
  396. local add_entity = minetest.add_entity
  397. local attempts = mdef.max_spawns_per_run
  398. local max_light = mdef.max_light
  399. local min_light = mdef.min_light
  400. local mob_range = mdef.mob_range
  401. local mob_limit = mdef.mob_limit
  402. local abs_limit = mdef.absolute_mob_limit
  403. local player_min_range = mdef.player_min_range
  404. local player_max_range = mdef.player_max_range
  405. local min_count = mdef.min_count
  406. local max_count = mdef.max_count
  407. local clearance = mdef.clearance
  408. local players = minetest.get_connected_players()
  409. local step = mdef.node_skip
  410. local radius = mdef.spawn_radius
  411. local jitter = mdef.node_jitter
  412. local names = mdef.node_names
  413. local offset = mdef.air_offset
  414. -- Find potential spawn points around player location.
  415. local points
  416. if mdef.flyswim == "ground" then
  417. points = search_terrain(spos, step, radius, jitter, names, offset, clearance)
  418. elseif mdef.flyswim == "flyswim" then
  419. points = search_flyswim(spos, step, radius, jitter, names, offset, clearance)
  420. end
  421. report(mname, "Found " .. #points .. " spawn point(s) @ " .. minetest.pos_to_string(spos) .. "!")
  422. -- Prevent a crash when accessing the array later.
  423. if #points < 1 then
  424. -- No mobs spawned, environment no good (so we can call it saturated).
  425. return 0, true
  426. end
  427. -- Record number of mobs successfully spawned.
  428. local mobs_spawned = 0
  429. local mobs_saturated
  430. for i = 1, attempts do
  431. -- Pick a random point for each spawn attempt. Prevents bunching.
  432. local pos = points[random(1, #points)]
  433. report(mname, "Attempting to spawn mob @ " .. minetest.pos_to_string(pos) .. "!")
  434. -- Count mobs in mob range.
  435. -- Don't spawn mob if there are already too many mobs in area.
  436. local mob_count, abs_count = mob_spawn.check_population(mname, pos, mob_range)
  437. if mob_count >= mob_limit or abs_count >= abs_limit then
  438. mobs_saturated = true
  439. goto next_spawn
  440. end
  441. -- Check if light level is ok.
  442. -- We perform this check for each possible position.
  443. local light = minetest.get_node_light(pos)
  444. if not light or light > max_light or light < min_light then
  445. report(mname, "Bad light level!")
  446. goto next_spawn
  447. end
  448. -- Find nearest player.
  449. local nearest_dist = player_max_range + 1
  450. for j = 1, #players do
  451. local p = players[j]:get_pos()
  452. local d = vector_distance(pos, p)
  453. if d < nearest_dist then
  454. nearest_dist = d
  455. end
  456. end
  457. -- Don't spawn if too near player or too far.
  458. if nearest_dist < player_min_range or nearest_dist > player_max_range then
  459. report(mname, "Player too near or player too far!")
  460. goto next_spawn
  461. end
  462. -- Spawn a random number of mobs.
  463. local num_to_spawn = random(min_count, max_count)
  464. for i = 1, num_to_spawn, 1 do
  465. -- Slightly randomize horizontal positioning.
  466. local p2 = {x=random(-5, 5)/10, y=0.5, z=random(-5, 5)/10}
  467. if mdef.add_entity_func then
  468. mdef.add_entity_func(vector_add(pos, p2))
  469. else
  470. local mob = add_entity(vector_add(pos, p2), mname)
  471. if mob then
  472. local ent = mob:get_luaentity()
  473. if ent then
  474. -- Adjust the chance to use pathfinding on a per-entity basis.
  475. if ent.pathfinding and ent.pathfinding ~= 0 then
  476. local chance = ent.instance_pathfinding_chance or {100, 100}
  477. local res = math_random(1, chance[2])
  478. --minetest.chat_send_player("MustTest", "Chance: " .. res .. " of " .. chance[1] .. " in " .. chance[2])
  479. if res > chance[1] then
  480. --minetest.chat_send_player("MustTest", "Mob will not pathfind!")
  481. ent.pathfinding = 0
  482. end
  483. end
  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. mob_spawn.run_once = true
  518. end