init.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. if not minetest.global_exists("fortress") then fortress = {} end
  2. fortress.modpath = minetest.get_modpath("fortress")
  3. fortress.worldpath = minetest.get_worldpath()
  4. fortress.schempath = fortress.modpath .. "/schems"
  5. if fortress.debug_layout == nil then
  6. fortress.debug_layout = false
  7. end
  8. -- Localize for performance.
  9. local vector_round = vector.round
  10. local math_floor = math.floor
  11. local math_random = math.random
  12. -- Default fortress definition.
  13. dofile(fortress.modpath .. "/default.lua")
  14. dofile(fortress.modpath .. "/loot.lua")
  15. local keydirs = {
  16. ["+x"] = {x=1, y=0, z=0},
  17. ["-x"] = {x=-1, y=0, z=0},
  18. ["+y"] = {x=0, y=1, z=0},
  19. ["-y"] = {x=0, y=-1, z=0},
  20. ["+z"] = {x=0, y=0, z=1},
  21. ["-z"] = {x=0, y=0, z=-1},
  22. }
  23. function fortress.initialize(pos, data, start, traversal, build, internal)
  24. -- Build traversal table if not provided. The traversal table allows us to
  25. -- know if a section of fortress was already generated at a cell location. The
  26. -- table contains the hashes of locations where fortress was generated.
  27. if not traversal then traversal = {} end
  28. -- Initialize build table to an empty array. This array describes all schems
  29. -- which must be placed, and their parameters, once the fortress generation
  30. -- algorithm is complete.
  31. if not build then
  32. build = {
  33. schems = {},
  34. chests = {},
  35. }
  36. end
  37. if not internal then
  38. internal = {
  39. -- Recursion depth.
  40. depth = 0,
  41. -- Algorithm start time.
  42. time = os.time(),
  43. -- Storage for limits information.
  44. limit = {},
  45. -- Initial starting position.
  46. spawn_pos = vector.round({x=pos.x, y=pos.y, z=pos.z}),
  47. -- Reference to the fortress data sheet.
  48. data = data,
  49. -- Step size.
  50. step = table.copy(data.step),
  51. -- Max/soft extents.
  52. max_extent = table.copy(data.max_extent),
  53. soft_extent = table.copy(data.soft_extent),
  54. }
  55. minetest.log("action", "Computing fortress pattern @ " .. minetest.pos_to_string(vector_round(pos)) .. "!")
  56. end
  57. -- Ensure the start position is rounded. Floating positions can screw us up!
  58. pos = vector_round(pos)
  59. -- In debug layout mode, make SMALLER fortresses.
  60. if fortress.debug_layout then
  61. internal.step = {x=1, y=1, z=1}
  62. internal.max_extent = vector.round(vector.divide(data.max_extent, data.step))
  63. internal.soft_extent = vector.round(vector.divide(data.soft_extent, data.step))
  64. end
  65. -- Use `initial` if not specified.
  66. -- Multiple initial start-points may be specified, pick a random one.
  67. if not start then
  68. start = data.initial[math_random(1, #data.initial)]
  69. end
  70. return pos, data, start, traversal, build, internal
  71. end
  72. function fortress.space_free(pos, info, internal, traversal)
  73. -- Calculate all positions this chunk will potentially occupy.
  74. -- This adds a position hash for each possible location from 'offset' to
  75. -- 'size'. The position hashes are sparse, so this is more efficient than it
  76. -- looks.
  77. local hashes = {}
  78. local size = info.size or {x=1, y=1, z=1}
  79. for x = 0, size.x-1, 1 do
  80. for y = 0, size.y-1, 1 do
  81. for z = 0, size.z-1, 1 do
  82. local p3 = vector_round(vector.add(pos, vector.multiply({x=x, y=y, z=z}, internal.step)))
  83. local hash = minetest.hash_node_position(p3)
  84. hashes[#hashes+1] = hash
  85. end
  86. end
  87. end
  88. -- Do nothing if this chunk already occupied.
  89. for k, v in ipairs(hashes) do
  90. if traversal[v] then
  91. return false
  92. end
  93. end
  94. return true
  95. end
  96. function fortress.claim_space(pos, start, info, internal, traversal)
  97. -- Calculate all positions this chunk will potentially occupy.
  98. -- This adds a position hash for each possible location from 'offset' to
  99. -- 'size'. The position hashes are sparse, so this is more efficient than it
  100. -- looks.
  101. local hashes = {}
  102. local size = info.size or {x=1, y=1, z=1}
  103. for x = 0, size.x-1, 1 do
  104. for y = 0, size.y-1, 1 do
  105. for z = 0, size.z-1, 1 do
  106. local p3 = vector_round(vector.add(pos, vector.multiply({x=x, y=y, z=z}, internal.step)))
  107. local hash = minetest.hash_node_position(p3)
  108. hashes[#hashes+1] = hash
  109. end
  110. end
  111. end
  112. -- Do nothing if this chunk already occupied.
  113. for k, v in ipairs(hashes) do
  114. if traversal[v] then
  115. return false
  116. end
  117. end
  118. -- Occupy this chunk!
  119. for k, v in ipairs(hashes) do
  120. traversal[v] = {
  121. -- Store chunk name for debugging.
  122. -- It will be stored in "infotext" metadata for manual inspection.
  123. chunk = start,
  124. }
  125. end
  126. return true
  127. end
  128. function fortress.add_loot(pos, info, build)
  129. if not info.chests then
  130. return
  131. end
  132. for k, v in ipairs(info.chests) do
  133. -- Spawn loot chest only if chance succeeds.
  134. if math_random(1, 100) <= v.chance then
  135. local p2 = table.copy(v.pos)
  136. -- The position adjustment setting may specify min/max values for each
  137. -- dimension coordinate.
  138. if p2.x_min then
  139. p2.x = math_random(p2.x_min, p2.x_max)
  140. p2.x_min = nil
  141. p2.x_max = nil
  142. end
  143. if p2.y_min then
  144. p2.y = math_random(p2.y_min, p2.y_max)
  145. p2.y_min = nil
  146. p2.y_max = nil
  147. end
  148. if p2.z_min then
  149. p2.z = math_random(p2.z_min, p2.z_max)
  150. p2.z_min = nil
  151. p2.z_max = nil
  152. end
  153. local loc = vector.add(pos, p2)
  154. build.chests[(#build.chests)+1] = {
  155. pos = loc,
  156. loot = v.loot,
  157. }
  158. end
  159. end
  160. end
  161. function fortress.add_schematics(pos, start, info, internal, traversal, build)
  162. -- Obtain relevant parameters for this section of fortress.
  163. -- A chunk may contain multiple schematics to place, each with their own
  164. -- parameters and chance to spawn.
  165. --
  166. -- Chunks may be limited how often they can be used in the fortress pattern.
  167. -- Here, we increment the limit-counter if limit is finite (non-zero).
  168. -- Elsewhere in code we read the current limit and stop chunk from being
  169. -- chosen accordingly. Zero means no limit imposed.
  170. local limit = info.limit or 0
  171. internal.limit[start] = internal.limit[start] or 0
  172. if limit > 0 then
  173. internal.limit[start] = internal.limit[start] + 1
  174. end
  175. -- Calculate size of chunk.
  176. local size = vector.multiply(info.size or {x=1, y=1, z=1}, internal.step)
  177. -- Add schems which are part of this chunk.
  178. -- A chunk may have multiple schems with different parameters.
  179. local thischunk = info.schem
  180. for k, v in ipairs(thischunk) do
  181. local chance = v.chance or 100
  182. if math_random(1, 100) <= chance then
  183. local file = v.file
  184. local path = fortress.schempath .. "/" .. file .. ".mts"
  185. local adjust = table.copy(v.adjust or {x=0, y=0, z=0})
  186. local force = true
  187. local priority = v.priority or 0
  188. -- The position adjustment setting may specify min/max values for each
  189. -- dimension coordinate.
  190. if adjust.x_min then
  191. adjust.x = math_random(adjust.x_min, adjust.x_max)
  192. adjust.x_min = nil
  193. adjust.x_max = nil
  194. end
  195. if adjust.y_min then
  196. adjust.y = math_random(adjust.y_min, adjust.y_max)
  197. adjust.y_min = nil
  198. adjust.y_max = nil
  199. end
  200. if adjust.z_min then
  201. adjust.z = math_random(adjust.z_min, adjust.z_max)
  202. adjust.z_min = nil
  203. adjust.z_max = nil
  204. end
  205. if type(v.force) == "boolean" then
  206. force = v.force
  207. end
  208. local rotation = v.rotation or "0"
  209. local schempos = vector.add(pos, adjust)
  210. -- Add fortress section to construction queue.
  211. build.schems[(#build.schems)+1] = {
  212. file = path,
  213. pos = vector.new(schempos),
  214. size = size,
  215. rotation = rotation,
  216. force = force,
  217. replacements = internal.data.replacements,
  218. priority = priority,
  219. }
  220. end
  221. end
  222. end
  223. function fortress.add_next(pos, info, internal, traversal, build)
  224. -- Current chunk may not have next chunks defined.
  225. -- Thus we have reached a dead-end.
  226. if not info.next then
  227. return
  228. end
  229. local exceeding_soft_extent = false
  230. -- For debugging.
  231. --exceeding_soft_extent = true
  232. local sp = internal.spawn_pos
  233. if pos.x < (sp.x - internal.soft_extent.x) or pos.x > (sp.x + internal.soft_extent.x) or
  234. pos.y < (sp.y - internal.soft_extent.y) or pos.y > (sp.y + internal.soft_extent.y) or
  235. pos.z < (sp.z - internal.soft_extent.z) or pos.z > (sp.z + internal.soft_extent.z) then
  236. exceeding_soft_extent = true
  237. end
  238. -- Recursively generate further chunks.
  239. for dir, chunks4dir in pairs(info.next) do
  240. local dirvec = keydirs[dir]
  241. local p2 = vector_round(vector.add(vector.multiply(dirvec, internal.step), pos))
  242. -- We're looking at the direction-specific neighbor list.
  243. -- Add up the max chance value by accumulating each chunk's individual chance.
  244. -- This complicated chance-calculation code is simply to give all chunks a
  245. -- relatively fair chance to be chosen, regardless of their absolute chance
  246. -- values.
  247. local all_chance = {}
  248. local max_chance = 0
  249. local avg_chance = 0
  250. -- First, calculate the average chance of all chunks having a chance specified.
  251. -- The average chance becomes the default chance for any chunks not having their
  252. -- chance specified, in later calculations.
  253. local chunks_with_chance = 0
  254. for index, neighbor in ipairs(chunks4dir) do
  255. if neighbor.chance then
  256. if neighbor.chance > 0 then
  257. avg_chance = avg_chance + neighbor.chance
  258. chunks_with_chance = chunks_with_chance + 1
  259. end
  260. end
  261. end
  262. if chunks_with_chance > 0 then
  263. avg_chance = math.floor(avg_chance / chunks_with_chance)
  264. end
  265. if avg_chance <= 0 then
  266. avg_chance = 1
  267. end
  268. -- Calculate each chunk's chance range (min, max).
  269. -- If the neighbor is a fallback, and its chance is not specified, then by
  270. -- default its chance is 1/4 the average chance of all other chunks.
  271. for index, neighbor in ipairs(chunks4dir) do
  272. -- Default fallback chance is 1/4 the average chance, but can't be less than 1.
  273. -- To make the chance for a fallback section 0, you must explicitly set the chance.
  274. local def_fb_chance = math.max(math.floor(avg_chance / 4), 1)
  275. local chunk_chance = math.floor(neighbor.chance or ((neighbor.fallback and def_fb_chance) or avg_chance))
  276. local chunk_limit = (info and info.limit) or 0
  277. -- Calculate the position that would be occupied by the next chunk.
  278. local p3 = vector.multiply(neighbor.shift or {x=0, y=0, z=0}, internal.step)
  279. local loc = vector_round(vector.add(p3, p2))
  280. -- Zeroize chance if chosen chunk is over the limit for this chunk,
  281. -- and the chunk is limited (has a positive, non-zero limit).
  282. if chunk_limit > 0 then
  283. local count = internal.limit[neighbor.chunk] or 0
  284. if count > chunk_limit then
  285. chunk_chance = 0
  286. end
  287. end
  288. -- If exceeding max soft extents, then chunk chances are always zero,
  289. -- and only 'fallback' chunks may be placed, if any are available.
  290. if exceeding_soft_extent then
  291. chunk_chance = 0
  292. end
  293. -- Data must exist for this chunk.
  294. if not internal.data.chunks[neighbor.chunk] then
  295. chunk_chance = 0
  296. else
  297. -- Don't give this section a chance if there would be no room for it anyway.
  298. if not fortress.space_free(loc, internal.data.chunks[neighbor.chunk], internal, traversal) then
  299. chunk_chance = 0
  300. end
  301. end
  302. if chunk_chance > 0 then
  303. local cur_chance = max_chance + 1
  304. max_chance = max_chance + chunk_chance
  305. all_chance[neighbor.chunk] = {min=cur_chance, max=max_chance}
  306. -- Check that the 'chance ranges' are in consecutive order with no gaps.
  307. --minetest.log('action', neighbor.chunk .. " CHANCE: min=" .. all_chance[neighbor.chunk].min .. ", max=" .. all_chance[neighbor.chunk].max)
  308. end
  309. end
  310. -- Get a random number between 1 and max chance value.
  311. -- If 0, then random chance was NOT chosen!
  312. local random_chance = 0
  313. if max_chance >= 1 then
  314. random_chance = math.random(1, max_chance)
  315. end
  316. -- Null chance range.
  317. local fallback_range = {min=0, max=0}
  318. -- For all chunks in direction-specific neighbor list (+/-X, +/-Y, +/-Z).
  319. for index, neighbor in ipairs(chunks4dir) do
  320. -- Chunk's chance range, or null range if not present.
  321. local chance_range = all_chance[neighbor.chunk] or fallback_range
  322. --minetest.log('action', neighbor.chunk .. " chance: min=" .. chance_range.min .. ", max=" .. chance_range.max)
  323. -- Add chunk data to fortress pattern if chance test succeeds.
  324. -- Note that once a chunk passes the 'chance test', no further chunk will
  325. -- be checked/added, UNLESS the 'continue' flag was set on the successful chunk.
  326. if (random_chance > 0 and random_chance >= chance_range.min and random_chance <= chance_range.max)
  327. or neighbor.fallback then
  328. -- If chunk had the 'fallback' flag set, it is ALWAYS permitted.
  329. -- For this reason you should ensure that 'fallback' chunks always come
  330. -- last in their list, otherwise you WILL get overlaps. Multiple fallback
  331. -- chunks will overlap. This may or may not be a problem for you.
  332. --[[
  333. if dir == "+x" or dir == "-x" or dir == "+z" or dir == "-z" then
  334. minetest.log('action', 'looking at: ' .. neighbor.chunk)
  335. end
  336. --]]
  337. local continue = false
  338. if type(neighbor.continue) == "boolean" then
  339. continue = neighbor.continue
  340. end
  341. -- Calculate position to spawn the next chunk.
  342. local p3 = vector.multiply(neighbor.shift or {x=0, y=0, z=0}, internal.step)
  343. local loc = vector_round(vector.add(p3, p2))
  344. internal.depth = internal.depth + 1
  345. --minetest.log("action", "depth " .. internal.depth .. "!")
  346. -- Using minetest.after() to avoid stack overflow.
  347. -- Instead of doing recusion on the stack, we do recursion through time.
  348. minetest.after(0, function()
  349. internal.depth = internal.depth - 1
  350. assert(internal.depth >= 0)
  351. fortress.spawn_fortress(loc, internal.data, neighbor.chunk, traversal, build, internal)
  352. end)
  353. -- Generated chunk. Don't need to continue through chunks for this dir.
  354. if not continue then
  355. break
  356. end
  357. end
  358. end
  359. end
  360. end
  361. -- Spawn a fortress starting at a position.
  362. -- Public API function. Pass valid values for `pos` and `data`.
  363. function fortress.spawn_fortress(pos, data, start, traversal, build, internal)
  364. -- Initialize if needed.
  365. pos, data, start, traversal, build, internal =
  366. fortress.initialize(pos, data, start, traversal, build, internal)
  367. -- Chosen chunk must have associated data.
  368. local info = data.chunks[start]
  369. if not info then
  370. fortress.check_done(internal, traversal, build)
  371. return
  372. end
  373. local sp = internal.spawn_pos
  374. -- Distance from inital pos must not be too large! Hard abort.
  375. -- This prevents trying to generate HUGE fortresses that would slow things.
  376. if pos.x < (sp.x - internal.max_extent.x) or pos.x > (sp.x + internal.max_extent.x) or
  377. pos.y < (sp.y - internal.max_extent.y) or pos.y > (sp.y + internal.max_extent.y) or
  378. pos.z < (sp.z - internal.max_extent.z) or pos.z > (sp.z + internal.max_extent.z) then
  379. --minetest.log('action', 'stopping placement of ' .. start)
  380. fortress.check_done(internal, traversal, build)
  381. return
  382. end
  383. if fortress.claim_space(pos, start, info, internal, traversal) then
  384. fortress.add_schematics(pos, start, info, internal, traversal, build)
  385. fortress.add_loot(pos, info, build)
  386. fortress.add_next(pos, info, internal, traversal, build)
  387. end
  388. fortress.check_done(internal, traversal, build)
  389. end
  390. -- To be called whenever we need to check if we're done preparing the fortress design.
  391. -- If no further design is to be generated, then start the actual mapgen/build process.
  392. function fortress.check_done(internal, traversal, build)
  393. if internal.depth > 0 then
  394. return
  395. end
  396. local minp = table.copy(internal.spawn_pos)
  397. local maxp = table.copy(internal.spawn_pos)
  398. -- Calculate voxelmanip area bounds.
  399. for k, v in ipairs(build.schems) do
  400. if v.pos.x < minp.x then
  401. minp.x = v.pos.x
  402. end
  403. if v.pos.x + v.size.x > maxp.x then
  404. maxp.x = v.pos.x + v.size.x
  405. end
  406. if v.pos.y < minp.y then
  407. minp.y = v.pos.y
  408. end
  409. if v.pos.y + v.size.y > maxp.y then
  410. maxp.y = v.pos.y + v.size.y
  411. end
  412. if v.pos.z < minp.z then
  413. minp.z = v.pos.z
  414. end
  415. if v.pos.z + v.size.z > maxp.z then
  416. maxp.z = v.pos.z + v.size.z
  417. end
  418. end
  419. minetest.log("action", "Fortress pos: " .. minetest.pos_to_string(internal.spawn_pos))
  420. minetest.log("action", "Fortress minp: " .. minetest.pos_to_string(minp))
  421. minetest.log("action", "Fortress maxp: " .. minetest.pos_to_string(maxp))
  422. internal.vm_minp = minp
  423. internal.vm_maxp = maxp
  424. -- Build callback function. When the map is loaded, we can spawn the fortress.
  425. local cb = function(blockpos, action, calls_remaining)
  426. -- Check if there was an error.
  427. if action == core.EMERGE_CANCELLED or action == core.EMERGE_ERRORED then
  428. minetest.log("error", "Failed to emerge area to spawn fortress.")
  429. return
  430. end
  431. -- We don't do anything until the last callback.
  432. if calls_remaining ~= 0 then
  433. return
  434. end
  435. -- Actually spawn the fortress once map completely loaded.
  436. fortress.apply_design(internal, traversal, build)
  437. end
  438. -- Load entire map region, generating chunks as needed.
  439. -- Overgenerate ceiling to try to avoid lighting issues in caverns.
  440. -- Doing this seems to be the trick.
  441. -- This will FAIL if in cavern, but ceiling is more than 100 nodes up!
  442. local omaxp = vector.offset(maxp, 0, 100, 0)
  443. minetest.emerge_area(minp, omaxp, cb)
  444. end
  445. -- To be called once map region fully loaded.
  446. function fortress.apply_design(internal, traversal, build)
  447. local minp = table.copy(internal.vm_minp)
  448. local maxp = table.copy(internal.vm_maxp)
  449. if fortress.is_protected(minp, maxp) then
  450. minetest.log("error", "Cannot spawn fortress, protection is present.")
  451. return
  452. end
  453. if not fortress.debug_layout then
  454. local vm = minetest.get_voxel_manip(minp, maxp)
  455. -- Note: replacements can only be sensibly defined for the entire fortress
  456. -- sheet as a whole. Defining custom replacement lists for individual fortress
  457. -- sections would NOT work the way you expect! Blame Minetest.
  458. local rp = internal.data.replacements or {}
  459. -- Sort chunks by priority. Lowest priority first.
  460. table.sort(build.schems,
  461. function(a, b)
  462. return a.priority < b.priority
  463. end)
  464. for k, v in ipairs(build.schems) do
  465. minetest.place_schematic_on_vmanip(vm, v.pos, v.file, v.rotation, rp, v.force)
  466. end
  467. vm:write_to_map(true)
  468. end
  469. -- Add loot chests, but only when not in debug-layout mode.
  470. if not fortress.debug_layout then
  471. local chest_names = {
  472. "morechests:woodchest_public_closed",
  473. "chests:chest_public_closed",
  474. "morechests:ironchest_public_closed",
  475. }
  476. for k, v in ipairs(build.chests) do
  477. local p = v.pos
  478. local n = minetest.get_node(p)
  479. -- Only if location not already occupied.
  480. if n.name == "air" then
  481. local param2 = math_random(0, 3)
  482. local cname = chest_names[math_random(1, #chest_names)]
  483. minetest.set_node(p, {name=cname, param2=param2})
  484. fortress.add_loot_items(p, v.loot)
  485. end
  486. end
  487. end
  488. -- Display hash locations.
  489. if fortress.debug_layout then
  490. for k, v in pairs(traversal) do
  491. local p = minetest.get_position_from_hash(k)
  492. minetest.set_node(p, {name="wool:red"})
  493. local meta = minetest.get_meta(p)
  494. meta:set_string("infotext", "Chunk: " .. v.chunk)
  495. end
  496. end
  497. mapfix.work(minp, maxp)
  498. minetest.log("action", "Finished generating fortress pattern in " .. math_floor(os.time()-internal.time) .. " seconds!")
  499. end
  500. function fortress.add_loot_items(pos, loot)
  501. local meta = minetest.get_meta(pos)
  502. if not meta then return end
  503. local inv = meta:get_inventory()
  504. if not inv then return end
  505. local list = inv:get_list("main")
  506. if not list then return end
  507. local lootdef = fortress.loot[loot]
  508. if not lootdef then return end
  509. local chosen_items = {}
  510. local chosen_positions = {}
  511. -- Size of chest inventory.
  512. local inv_size = inv:get_size("main")
  513. for i = 1, inv_size do
  514. chosen_positions[i] = i
  515. end
  516. table.shuffle(chosen_positions)
  517. for k, v in ipairs(lootdef.item_list) do
  518. local min = v.min or 1
  519. local max = v.max or 1
  520. local chance = v.chance or 100
  521. if math_random(1, 100) <= chance then
  522. -- Only if named item actually exists.
  523. if minetest.registered_items[v.item] then
  524. local itemstr = (v.item .. " " .. math_random(min, max))
  525. chosen_items[#chosen_items+1] = itemstr
  526. end
  527. end
  528. end
  529. -- Randomize the order of items, and we will chose the first few up to
  530. -- 'max_items' allowed.
  531. table.shuffle(chosen_items)
  532. for k, v in ipairs(chosen_items) do
  533. -- Don't add more items than would actually fit, if for some reason the
  534. -- number of chosen items is larger than the inventory size.
  535. if k <= inv_size then
  536. list[chosen_positions[k]] = v
  537. -- Stop once max-items is reached.
  538. if k >= lootdef.max_items then
  539. break
  540. end
  541. end
  542. end
  543. inv:set_list("main", list)
  544. end
  545. -- Quickly check for protection in an area.
  546. function fortress.is_protected(minp, maxp)
  547. -- Step size, to avoid checking every single node.
  548. -- This assumes protections cannot be smaller than this size.
  549. local ss = 5
  550. local check = minetest.test_protection
  551. for x=minp.x, maxp.x, ss do
  552. for y=minp.y, maxp.y, ss do
  553. for z=minp.z, maxp.z, ss do
  554. if check({x=x, y=y, z=z}, "") then
  555. -- Protections are present.
  556. return true
  557. end
  558. end
  559. end
  560. end
  561. -- Nothing in the area is protected.
  562. return false
  563. end
  564. -- Public API function.
  565. -- Name must be a fortress data sheet.
  566. function fortress.generate(pos, name)
  567. if fortress[name] then
  568. fortress.spawn_fortress(pos, fortress[name])
  569. end
  570. end
  571. function fortress.chat_command(name, param)
  572. local player = minetest.get_player_by_name(name)
  573. if not player or not player:is_player() then
  574. return
  575. end
  576. local pos = vector_round(player:get_pos())
  577. fortress.spawn_fortress(pos, fortress.default)
  578. end
  579. if not fortress.run_once then
  580. minetest.register_chatcommand("spawn_fortress", {
  581. params = "",
  582. description = "Spawn a fortress starting at your current location.",
  583. privs = {server=true},
  584. func = function(...)
  585. fortress.chat_command(...)
  586. return true
  587. end,
  588. })
  589. local c = "fortress:core"
  590. local f = fortress.modpath .. "/init.lua"
  591. reload.register_file(c, f, false)
  592. fortress.run_once = true
  593. end