explosion.lua 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. local get_node_or_nil = minetest.get_node_or_nil
  2. local hash_node_pos = minetest.hash_node_position
  3. local vec_add = vector.add
  4. local floor = math.floor
  5. local ceil = math.ceil
  6. --implement a simple stack
  7. --TODO: make this a proper class
  8. local pop_to_visit
  9. local add_to_visit
  10. local kill_to_visit
  11. do
  12. to_visit_counter = 0
  13. to_visit = {}
  14. add_to_visit = function(tab)
  15. counter = to_visit_counter + 1
  16. to_visit[counter] = tab
  17. end
  18. pop_to_visit = function()
  19. local ret = to_visit[counter]
  20. to_visit[counter] = nil
  21. counter = counter - 1
  22. return ret
  23. end
  24. kill_to_visit = function()
  25. to_visit = {}
  26. counter = 0
  27. end
  28. end
  29. local sides = {
  30. {x = 1, y = 0, z = 0},
  31. {x = -1, y = 0, z = 0},
  32. {x = 0, y = 1, z = 0},
  33. {x = 0, y = -1, z = 0},
  34. {x = 0, y = 0, z = 1},
  35. {x = 0, y = 0, z = -1},
  36. }
  37. local calculate_resistance
  38. do
  39. local relevant_groups =
  40. {
  41. crumbly = 0.9,
  42. cracky = 0.9,
  43. snappy = 0.1,
  44. explody = 0.1,
  45. choppy = 0.5,
  46. }
  47. calculate_resistance = function(groups)
  48. local resistance = 0
  49. for groupname, weight in pairs(relevant_groups)
  50. do
  51. local g = groups[groupname]
  52. if g
  53. then
  54. resistance = resistance + (4 - g) * weight
  55. end
  56. end
  57. return resistance
  58. end
  59. end
  60. local reg_nodes = minetest.registered_nodes
  61. local get_erosion_result
  62. local cid_def_map = {}
  63. local cid_air
  64. local cid_fire
  65. erosion.do_once_postprocessing_done(function()
  66. cid_air = minetest.get_content_id("air")
  67. cid_fire = minetest.get_content_id("fire:basic_flame")
  68. for name, def in pairs(reg_nodes)
  69. do
  70. cid_def_map[minetest.get_content_id(name)] =
  71. {
  72. on_blast = def.on_blast,
  73. water = (def.groups or {water = 0}).water or 0,
  74. should_fly = (def.groups or {crumbly = 0}).crumbly or 0 > 0 or nil
  75. }
  76. end
  77. get_erosion_result = erosion.get_erosion_result
  78. end)
  79. local voxel_buffer = {}
  80. local function blast_effect(pos, power)
  81. minetest.add_particlespawner(
  82. {
  83. amount = 50,
  84. time = 0.1,
  85. minpos = pos,
  86. maxpos = pos,
  87. minvel = {x = -10, y = -10, z = -10},
  88. maxvel = {x = 10, y = 10, z = 10},
  89. minexptime = 0.1,
  90. maxexptime = 0.5,
  91. minsize = power,
  92. maxsize = power,
  93. texture = "gunpowder_barrels_spark.png",
  94. glow = 14,
  95. })
  96. --TODO: sound
  97. local radius = 5 - 5 / power
  98. if radius < 0 then radius = 0 end
  99. local vm = minetest.get_voxel_manip()
  100. local minpos = {
  101. x = floor(pos.x - radius),
  102. y = floor(pos.y - radius),
  103. z = floor(pos.z - radius)
  104. }
  105. local maxpos = {
  106. x = ceil(pos.x + radius),
  107. y = ceil(pos.y + radius),
  108. z = ceil(pos.z + radius)
  109. }
  110. local emin, emax = vm:read_from_map(minpos, maxpos)
  111. local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
  112. local data = vm:get_data(voxel_buffer)
  113. local flyers = {} --stores nodes to be changed to falling
  114. for iz = minpos.z, maxpos.z
  115. do
  116. for iy = minpos.y, maxpos.y
  117. do
  118. for ix = minpos.x, maxpos.x
  119. do
  120. --possible optimation: cache area:index
  121. local position_vector = {x = ix, y = iy, z = iz}
  122. local dist = vector.distance(pos, position_vector)
  123. if dist < radius and math.random() < 0.8
  124. then
  125. local cid = data[area:index(ix, iy, iz)]
  126. local def = cid_def_map[cid]
  127. if def
  128. then
  129. --call on_blast callback
  130. --nodes aren't removed by anything.
  131. if def.on_blast
  132. then
  133. local replace = def.on_blast(position_vector,
  134. power / (dist ~= 0 and dist or 0.1))
  135. else
  136. replace = get_erosion_result(cid, power /
  137. (dist ~= 0 and dist or 0.1))
  138. if cid_def_map[replace or cid].should_fly
  139. then
  140. flyers[#flyers + 1] = position_vector
  141. end
  142. end
  143. if replace
  144. then
  145. data[area:index(ix, iy, iz)] = replace
  146. end
  147. --set fire to things
  148. if cid == cid_air and
  149. iy - 1 > emin.y and
  150. math.random() < 0.02
  151. then
  152. local lowcid = data[area:index(ix, iy - 1, iz)]
  153. local lowdef = cid_def_map[lowcid]
  154. if lowcid ~= cid_air and lowdef and lowdef.water == 0
  155. then
  156. data[area:index(ix, iy, iz)] = cid_fire
  157. end
  158. end
  159. end
  160. end
  161. end
  162. end
  163. end
  164. vm:set_data(data)
  165. vm:update_liquids()
  166. vm:write_to_map(true)
  167. for _, v in ipairs(flyers)
  168. do
  169. minetest.spawn_falling_node(v)
  170. end
  171. local objs = minetest.get_objects_inside_radius(pos, radius)
  172. for _, obj in ipairs(objs)
  173. do
  174. local p = obj:get_pos()
  175. local dist = vector.distance(pos, p)
  176. if dist == 0
  177. then
  178. dist = 0.01 --prevent division by zero
  179. end
  180. local upvel = power / dist
  181. if upvel ~= upvel --nan check
  182. then
  183. upvel = 10
  184. end
  185. if obj:is_player()
  186. then
  187. obj:add_player_velocity({x = 0, y = upvel, z = 0})
  188. else
  189. obj:setvelocity({x = 0, y = upvel, z = 0})
  190. end
  191. obj:punch(obj, 10,
  192. {
  193. damage_groups =
  194. {
  195. fleshy = (power / dist * 2)
  196. }
  197. })
  198. end
  199. end
  200. local propagatable_threshold = 0.2
  201. local propagate = function(pos, power)
  202. --make a value for how confined it is
  203. --decrease power for low confinement
  204. local propagatable_num = 0
  205. local epicentres = {}
  206. local already_visited = {}
  207. add_to_visit({vec = pos, power = power})
  208. while true
  209. do
  210. local p = pop_to_visit()
  211. if not p
  212. then
  213. break
  214. end
  215. local hash = hash_node_pos(p.vec)
  216. already_visited[hash] = true
  217. power = p.power
  218. if power > 0.1 --don't propagate further if too weak
  219. then
  220. local pneighbors = {}
  221. local pcount = 0
  222. for i = 1, 6
  223. do
  224. local neighbor = vec_add(sides[i], p.vec)
  225. if not already_visited[hash_node_pos(neighbor)]
  226. then
  227. local node = get_node_or_nil(neighbor)
  228. if node
  229. then
  230. node = reg_nodes[node.name]
  231. local resistance = calculate_resistance(node.groups or {groups = {}})
  232. if resistance < propagatable_threshold
  233. then
  234. pcount = pcount + 1
  235. pneighbors[pcount] = neighbor
  236. pneighbors[pcount + 0.5] = resistance
  237. --TODO: take resistance into account
  238. end--if
  239. end--if
  240. end--if
  241. end--for
  242. power = power / (pcount + 1)
  243. p.power = power
  244. propagatable_num = propagatable_num + 1
  245. epicentres[propagatable_num] = p
  246. for i = 1, pcount, 1
  247. do
  248. local pow = power - pneighbors[i + 0.5]
  249. if pow < 0 then pow = 0 end
  250. add_to_visit({vec = pneighbors[i], power = pow})
  251. end--for
  252. end--if
  253. end--while
  254. kill_to_visit()
  255. for i = 1, propagatable_num
  256. do
  257. epicentre = epicentres[i]
  258. blast_effect(epicentre.vec, epicentre.power)
  259. end
  260. end--function
  261. local explode = function(self)
  262. if self.fuse_handle
  263. then
  264. minetest.sound_stop(self.fuse_handle)
  265. end
  266. minetest.sound_play(
  267. "gunpowder_barrels_blast",
  268. {
  269. object = self.object,
  270. max_hear_distance = 128,
  271. gain = 4,
  272. })
  273. local pos = self.object:get_pos()
  274. local power = 20-- + math.random() * 10
  275. self.object:remove()
  276. propagate(pos, power)
  277. end
  278. gunpowder_barrels.explode = explode