init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. -- Todo:
  2. -- Allow fortress chunks to extend downward without limit until rock.
  3. fortress = fortress or {}
  4. fortress.modpath = minetest.get_modpath("fortress")
  5. fortress.worldpath = minetest.get_worldpath()
  6. fortress.schempath = fortress.modpath .. "/schems"
  7. fortress.pending = fortress.pending or {}
  8. fortress.active = fortress.active or {}
  9. fortress.dirty = true
  10. -- Localize for performance.
  11. local vector_round = vector.round
  12. local math_floor = math.floor
  13. local math_random = math.random
  14. dofile(fortress.modpath .. "/default.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. -- Spawn a fortress starting at a position.
  24. -- Public API function. Pass valid values for `pos` and `data`.
  25. function fortress.spawn_fortress(pos, data, start, traversal, build, internal)
  26. -- Build traversal table if not provided. The traversal table allows us to
  27. -- know if a section of fortress was already generated at a cell location. The
  28. -- table contains the hashes of locations where fortress was generated.
  29. if not traversal then traversal = {} end
  30. if not build then build = {} end
  31. if not internal then
  32. internal = {
  33. depth = 0,
  34. time = os.clock(),
  35. limit = {},
  36. pos = {x=pos.x, y=pos.y, z=pos.z},
  37. }
  38. minetest.log("action", "Computing fortress pattern @ " .. minetest.pos_to_string(vector_round(pos)) .. "!")
  39. end
  40. pos = vector_round(pos)
  41. -- Use `initial` if not specified.
  42. -- Multiple initial start-points may be specified, pick a random one.
  43. if not start then
  44. start = data.initial[math_random(1, #data.initial)]
  45. end
  46. -- Chosen chunk must have associated data.
  47. local info = data.chunks[start]
  48. if not info then goto checkdone end
  49. -- Use default offset if none specified.
  50. do
  51. local offset = info.offset or {x=0, y=0, z=0}
  52. pos = vector_round(vector.add(pos, vector.multiply(offset, data.step)))
  53. end
  54. -- Calculate all positions this chunk will potentially occupy.
  55. do
  56. local hash = minetest.hash_node_position(pos)
  57. local hashes = {}
  58. local size = info.size or {x=1, y=1, z=1}
  59. for x = 0, size.x-1, 1 do
  60. for y = 0, size.y-1, 1 do
  61. for z = 0, size.z-1, 1 do
  62. local p3 = vector_round(vector.add(pos, vector.multiply({x=x, y=y, z=z}, data.step)))
  63. local hash = minetest.hash_node_position(p3)
  64. hashes[#hashes+1] = hash
  65. end
  66. end
  67. end
  68. -- Do nothing if this chunk already occupied.
  69. for k, v in ipairs(hashes) do
  70. if traversal[v] then
  71. goto checkdone
  72. end
  73. end
  74. -- Occupy this chunk!
  75. for k, v in ipairs(hashes) do
  76. traversal[v] = true
  77. end
  78. end
  79. -- Obtain relevant parameters for this section of fortress.
  80. -- A chunk may contain multiple schematics to place, each with their own
  81. -- parameters and chance to spawn.
  82. do
  83. -- Chunks may be limited how often they can be used in the fortress pattern.
  84. -- Here, we increment limit if limit is finite (non-zero).
  85. -- Elsewhere in code we read the current limit and reduce chance of
  86. -- chunk being chosen accordingly. Zero means no limit imposed.
  87. local limit = info.limit or 0
  88. internal.limit[start] = internal.limit[start] or 0
  89. if limit > 0 then
  90. internal.limit[start] = internal.limit[start] + 1
  91. end
  92. -- Calculate size of chunk.
  93. local size = vector.multiply(info.size or {x=1, y=1, z=1}, data.step)
  94. -- Add schems which are part of this chunk.
  95. -- A chunk may have multiple schems with different parameters.
  96. local thischunk = info.schem
  97. for k, v in ipairs(thischunk) do
  98. local chance = v.chance or 100
  99. if math_random(1, 100) <= chance then
  100. local file = v.file
  101. local path = fortress.schempath .. "/" .. file .. ".mts"
  102. local adjust = v.adjust or {x=0, y=0, z=0}
  103. local force = true
  104. if type(v.force) == "boolean" then
  105. force = v.force
  106. end
  107. local rotation = v.rotation or "0"
  108. local schempos = vector.add(pos, adjust)
  109. -- Add fortress section to construction queue.
  110. build[#build+1] = {
  111. file = path,
  112. pos = vector.new(schempos),
  113. size = size,
  114. rotation = rotation,
  115. force = force,
  116. }
  117. end
  118. end
  119. end
  120. -- Current chunk may not have next chunks defined.
  121. -- Thus we have reached a dead-end.
  122. if not info.next then
  123. goto checkdone
  124. end
  125. -- Recursively generate further chunks.
  126. for dir, chunks in pairs(info.next) do
  127. local dirvec = keydirs[dir]
  128. local p2 = vector_round(vector.add(vector.multiply(dirvec, data.step), pos))
  129. for index, chunk in ipairs(chunks) do
  130. local info = data.chunks[chunk.chunk]
  131. -- Current chunk must have associated data.
  132. if not info then
  133. goto skipme
  134. end
  135. --minetest.chat_send_all(dump(chunk))
  136. local chance = chunk.chance or 100
  137. local limit = info.limit or 0
  138. -- Adjust chance if chosen chunk is over the limit for this chunk,
  139. -- and the chunk is limited (has a positive, non-zero limit).
  140. if limit > 0 then
  141. local limit2 = internal.limit[chunk.chunk] or 0
  142. if limit2 > limit then
  143. local diff = math_floor(limit2 - limit)
  144. -- Every 1 count past the limit reduces chance by 10.
  145. chance = chance - diff * 10
  146. end
  147. end
  148. -- Add chunk data to fortress pattern if chance test succeeds.
  149. if math_random(1, 100) <= chance then
  150. local continue = false
  151. if type(chunk.continue) == "boolean" then
  152. continue = chunk.continue
  153. end
  154. local p3 = vector.multiply(chunk.shift or {x=0, y=0, z=0}, data.step)
  155. local loc = vector_round(vector.add(p3, p2))
  156. local delay = (math_random(1, 10)/10)+1.0
  157. internal.depth = internal.depth + 1
  158. --minetest.chat_send_all("# Server: Depth " .. internal.depth .. "!")
  159. minetest.after(delay, function()
  160. internal.depth = internal.depth - 1
  161. assert(internal.depth >= 0)
  162. fortress.spawn_fortress(loc, data, chunk.chunk, traversal, build, internal)
  163. end)
  164. -- Generated chunk. Don't need to continue through chunks for this dir.
  165. if not continue then
  166. break
  167. end
  168. end
  169. ::skipme::
  170. end
  171. end
  172. -- Check if all build-data is gathered yet.
  173. ::checkdone::
  174. if internal.depth == 0 then
  175. minetest.log("action", "Finished generating fortress pattern in " .. math_floor(os.clock()-internal.time) .. " seconds!")
  176. -- Push build data to pending queue.
  177. for k, v in ipairs(build) do
  178. (fortress.pending)[#(fortress.pending)+1] = v
  179. end
  180. fortress.dirty = true
  181. -- Save data for later, perhaps after a restart.
  182. -- But more commonly, fortress will be generated this session.
  183. fortress.save_data()
  184. end
  185. end
  186. -- Using data in the pending table, construct a fortress.
  187. -- This data is created by attempting to spawn a fortress,
  188. -- and is saved on shutdown to allow resuming after restart.
  189. -- This function is called once a second.
  190. function fortress.resume_construction()
  191. if #fortress.pending == 0 then
  192. -- Nothing to write!
  193. return
  194. end
  195. -- Count entries in active list (starts out contigious, may have holes).
  196. local count = 0
  197. for k, v in pairs(fortress.active) do
  198. count = count + 1
  199. end
  200. if count > 0 then
  201. -- Write already in progress!
  202. return
  203. end
  204. -- Swap buffers.
  205. fortress.active = fortress.pending
  206. fortress.pending = {}
  207. fortress.dirty = true
  208. local internal = {
  209. depth = 0,
  210. time = os.clock(),
  211. }
  212. minetest.log("action", "Generating fortress structure!")
  213. -- The first time we iterate over the active list it is a contigious
  214. -- array. During the process that this starts, the array gains holes.
  215. local timer = 1
  216. for k, v in pairs(fortress.active) do
  217. internal.depth = internal.depth + 1
  218. minetest.after(timer, function()
  219. -- Positions to preload. Need a larger area in order to make sure any protections are discovered.
  220. local minp = vector.add(v.pos, vector.new(-16, -16, -16))
  221. local maxp = vector.add(v.pos, vector.add(v.size, vector.new(16, 16, 16)))
  222. local tbparam = {}
  223. -- Callback function. Will be called when area is emerged.
  224. local cb = function(blockpos, action, calls_remaining, param)
  225. -- We don't do anything until the last callback.
  226. if calls_remaining ~= 0 then
  227. -- Don't decrement the depth counter.
  228. -- We haven't actually done anything.
  229. goto checkdone
  230. end
  231. if action == core.EMERGE_CANCELLED or action == core.EMERGE_ERRORED then
  232. minetest.log("error", "Failed to emerge area for fortress chunk at " .. minetest.pos_to_string(v.pos) .. "!")
  233. -- Don't decrement the depth counter.
  234. -- We haven't actually done anything.
  235. goto checkdone
  236. end
  237. internal.depth = internal.depth - 1
  238. if fortress.is_protected(minp, maxp) then
  239. minetest.log("error", "Cannot place fortress chunk at " .. minetest.pos_to_string(v.pos) .. " due to protection!")
  240. fortress.active[k] = nil
  241. fortress.dirty = true
  242. goto checkdone
  243. end
  244. minetest.place_schematic(v.pos, v.file, v.rotation, {}, v.force)
  245. fortress.active[k] = nil
  246. fortress.dirty = true
  247. minetest.log("action", "Placed fortress section @ " .. minetest.pos_to_string(v.pos) .. "!")
  248. ::checkdone::
  249. if internal.depth == 0 then
  250. minetest.log("action", "Fortress fully generated in " .. math_floor(os.clock()-internal.time) .. " seconds!")
  251. fortress.save_data()
  252. end
  253. end
  254. -- The fortress chunk is placed after generating map.
  255. minetest.emerge_area(minp, maxp, cb, tbparam)
  256. end)
  257. -- Separate calls to build fortress sections by random time, sequentially.
  258. timer = timer + (math_random(1, 100)/20)
  259. end
  260. end
  261. -- Quickly check for protection in an area.
  262. function fortress.is_protected(minp, maxp)
  263. -- Step size, to avoid checking every single node.
  264. -- This assumes protections cannot be smaller than this size.
  265. local ss = 5
  266. local check = minetest.test_protection
  267. for x=minp.x, maxp.x, ss do
  268. for y=minp.y, maxp.y, ss do
  269. for z=minp.z, maxp.z, ss do
  270. if check({x=x, y=y, z=z}, "") then
  271. -- Protections are present.
  272. return true
  273. end
  274. end
  275. end
  276. end
  277. -- Nothing in the area is protected.
  278. return false
  279. end
  280. function fortress.save_data()
  281. if not fortress.dirty then
  282. return
  283. end
  284. local data = {}
  285. for k, v in ipairs(fortress.pending) do
  286. data[#data+1] = v
  287. end
  288. for k, v in pairs(fortress.active) do
  289. data[#data+1] = v
  290. end
  291. local str = minetest.serialize(data)
  292. if type(str) ~= "string" then
  293. return
  294. end
  295. local file = io.open(fortress.worldpath .. "/fortress.dat", "w")
  296. if file then
  297. file:write(str)
  298. file:close()
  299. minetest.log("action", "Saved " .. #data .. " pending fortress sections!")
  300. fortress.dirty = false
  301. end
  302. end
  303. function fortress.load_data()
  304. local file = io.open(fortress.worldpath .. "/fortress.dat", "r")
  305. if file then
  306. -- Data should always be a contigious array.
  307. local data = minetest.deserialize(file:read("*all"))
  308. if type(data) == "table" then
  309. fortress.pending = data
  310. fortress.dirty = false
  311. end
  312. file:close()
  313. minetest.log("action", "Loaded " .. #data .. " pending fortress sections!")
  314. end
  315. end
  316. function fortress.chat_command(name, param)
  317. local player = minetest.get_player_by_name(name)
  318. if not player or not player:is_player() then
  319. return
  320. end
  321. local pos = vector_round(player:get_pos())
  322. fortress.spawn_fortress(pos, fortress.default)
  323. end
  324. if not fortress.run_once then
  325. fortress.load_data()
  326. local delay = 1
  327. local timer = 0
  328. minetest.register_globalstep(function(dtime)
  329. timer = timer + dtime
  330. if timer < delay then
  331. return
  332. end
  333. timer = 0
  334. return fortress.resume_construction()
  335. end)
  336. minetest.register_on_shutdown(function()
  337. return fortress.save_data()
  338. end)
  339. minetest.register_on_mapsave(function()
  340. return fortress.save_data()
  341. end)
  342. minetest.register_chatcommand("spawn_fortress", {
  343. params = "",
  344. description = "Spawn a fortress starting at your current location.",
  345. privs = {server=true},
  346. func = function(...)
  347. fortress.chat_command(...)
  348. return true
  349. end,
  350. })
  351. local c = "fortress:core"
  352. local f = fortress.modpath .. "/init.lua"
  353. reload.register_file(c, f, false)
  354. fortress.run_once = true
  355. end