init.lua 17 KB

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