init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. -- fire/init.lua
  2. -- Global namespace for functions
  3. fire = {}
  4. -- Load support for MT game translation.
  5. local S = minetest.get_translator('fire')
  6. -- 'Enable fire' setting
  7. local fire_enabled = minetest.settings:get_bool('enable_fire')
  8. if fire_enabled == nil then
  9. -- enable_fire setting not specified, check for disable_fire
  10. local fire_disabled = minetest.settings:get_bool('disable_fire')
  11. if fire_disabled == nil then
  12. -- Neither setting specified, check whether singleplayer
  13. fire_enabled = minetest.is_singleplayer()
  14. else
  15. fire_enabled = not fire_disabled
  16. end
  17. end
  18. --
  19. -- Items
  20. --
  21. -- Flood flame function
  22. local function flood_flame(pos, oldnode, newnode)
  23. -- Play flame extinguish sound if liquid is not an 'igniter'
  24. local nodedef = minetest.registered_items[newnode.name]
  25. if not (nodedef and nodedef.groups and
  26. nodedef.groups.igniter and nodedef.groups.igniter > 0) then
  27. minetest.sound_play('fire_extinguish_flame',
  28. {pos = pos, max_hear_distance = 16, gain = 0.15})
  29. end
  30. -- Remove the flame
  31. return false
  32. end
  33. -- Flame nodes
  34. minetest.register_node('fire:basic_flame', {
  35. drawtype = 'firelike',
  36. tiles = {
  37. {
  38. name = 'fire_basic_flame_animated.png',
  39. animation = {
  40. type = 'vertical_frames',
  41. aspect_w = 16,
  42. aspect_h = 16,
  43. length = 1
  44. },
  45. },
  46. },
  47. inventory_image = 'fire_basic_flame.png',
  48. paramtype = 'light',
  49. light_source = 13,
  50. walkable = false,
  51. buildable_to = true,
  52. sunlight_propagates = true,
  53. floodable = true,
  54. damage_per_second = 4,
  55. groups = {igniter = 2, dig_immediate = 3, not_in_creative_inventory = 1},
  56. drop = '',
  57. on_timer = function(pos)
  58. local f = minetest.find_node_near(pos, 1, {'group:flammable'})
  59. if not fire_enabled or not f then
  60. minetest.remove_node(pos)
  61. return
  62. end
  63. -- Restart timer
  64. return true
  65. end,
  66. on_construct = function(pos)
  67. if not fire_enabled then
  68. minetest.remove_node(pos)
  69. else
  70. minetest.get_node_timer(pos):start(math.random(30, 60))
  71. end
  72. end,
  73. on_flood = flood_flame,
  74. on_punch = function(pos, node, puncher)
  75. minetest.remove_node(pos)
  76. minetest.log('action', puncher:get_player_name()..' put out fire at '..minetest.pos_to_string(pos))
  77. end,
  78. })
  79. minetest.register_node('fire:permanent_flame', {
  80. description = S('Permanent Flame'),
  81. drawtype = 'firelike',
  82. tiles = {
  83. {
  84. name = 'fire_basic_flame_animated.png',
  85. animation = {
  86. type = 'vertical_frames',
  87. aspect_w = 16,
  88. aspect_h = 16,
  89. length = 1
  90. },
  91. },
  92. },
  93. inventory_image = 'fire_basic_flame.png',
  94. paramtype = 'light',
  95. light_source = 13,
  96. walkable = false,
  97. buildable_to = true,
  98. sunlight_propagates = true,
  99. floodable = true,
  100. damage_per_second = 4,
  101. groups = {igniter = 2, dig_immediate = 3},
  102. drop = '',
  103. on_flood = flood_flame,
  104. })
  105. -- Flint and steel
  106. minetest.register_tool('fire:flint_and_steel', {
  107. description = S('Flint and Steel'),
  108. inventory_image = 'fire_flint_steel.png',
  109. sound = {breaks = 'default_tool_breaks'},
  110. on_use = function(itemstack, user, pointed_thing)
  111. local sound_pos = pointed_thing.above or user:get_pos()
  112. minetest.sound_play(
  113. 'fire_flint_and_steel',
  114. {pos = sound_pos, gain = 0.5, max_hear_distance = 8}
  115. )
  116. local player_name = user:get_player_name()
  117. if minetest.check_player_privs(player_name, { fire = true }) then
  118. if pointed_thing.type == 'node' then
  119. local node_under = minetest.get_node(pointed_thing.under).name
  120. local nodedef = minetest.registered_nodes[node_under]
  121. if not nodedef then
  122. return
  123. end
  124. if minetest.is_protected(pointed_thing.under, player_name) then
  125. minetest.chat_send_player(player_name, 'This area is protected')
  126. return
  127. end
  128. if nodedef.on_ignite then
  129. nodedef.on_ignite(pointed_thing.under, user)
  130. elseif minetest.get_item_group(node_under, 'flammable') >= 1
  131. and minetest.get_node(pointed_thing.above).name == 'air' then
  132. minetest.set_node(pointed_thing.above, {name = 'fire:basic_flame'})
  133. minetest.log('action', 'Player '..player_name..' started a fire.')
  134. end
  135. end
  136. if not (creative and creative.is_enabled_for
  137. and creative.is_enabled_for(player_name)) then
  138. -- Wear tool
  139. local wdef = itemstack:get_definition()
  140. itemstack:add_wear(1000)
  141. -- Tool break sound
  142. if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
  143. minetest.sound_play(wdef.sound.breaks, {pos = sound_pos, gain = 0.5})
  144. end
  145. return itemstack
  146. end
  147. end
  148. end
  149. })
  150. minetest.register_craft({
  151. output = 'fire:flint_and_steel',
  152. recipe = {
  153. {'default:flint', 'default:steel_ingot'}
  154. }
  155. })
  156. -- Override coalblock to enable permanent flame above
  157. -- Coalblock is non-flammable to avoid unwanted basic_flame nodes
  158. minetest.override_item('default:coalblock', {
  159. after_destruct = function(pos, oldnode)
  160. pos.y = pos.y + 1
  161. if minetest.get_node(pos).name == 'fire:permanent_flame' then
  162. minetest.remove_node(pos)
  163. end
  164. end,
  165. on_ignite = function(pos, igniter)
  166. local flame_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
  167. if minetest.get_node(flame_pos).name == 'air' then
  168. minetest.set_node(flame_pos, {name = 'fire:permanent_flame'})
  169. end
  170. end,
  171. })
  172. --
  173. -- Sound
  174. --
  175. local flame_sound = minetest.settings:get_bool('flame_sound')
  176. if flame_sound == nil then
  177. -- Enable if no setting present
  178. flame_sound = true
  179. end
  180. if flame_sound then
  181. local handles = {}
  182. local timer = 0
  183. -- Parameters
  184. local radius = 8 -- Flame node search radius around player
  185. local cycle = 3 -- Cycle time for sound updates
  186. -- Update sound for player
  187. function fire.update_player_sound(player)
  188. local player_name = player:get_player_name()
  189. -- Search for flame nodes in radius around player
  190. local ppos = player:get_pos()
  191. local areamin = vector.subtract(ppos, radius)
  192. local areamax = vector.add(ppos, radius)
  193. local fpos, num = minetest.find_nodes_in_area(
  194. areamin,
  195. areamax,
  196. {'fire:basic_flame', 'fire:permanent_flame'}
  197. )
  198. -- Total number of flames in radius
  199. local flames = (num['fire:basic_flame'] or 0) +
  200. (num['fire:permanent_flame'] or 0)
  201. -- Stop previous sound
  202. if handles[player_name] then
  203. minetest.sound_stop(handles[player_name])
  204. handles[player_name] = nil
  205. end
  206. -- If flames
  207. if flames > 0 then
  208. -- Find centre of flame positions
  209. local fposmid = fpos[1]
  210. -- If more than 1 flame
  211. if #fpos > 1 then
  212. local fposmin = areamax
  213. local fposmax = areamin
  214. for i = 1, #fpos do
  215. local fposi = fpos[i]
  216. if fposi.x > fposmax.x then
  217. fposmax.x = fposi.x
  218. end
  219. if fposi.y > fposmax.y then
  220. fposmax.y = fposi.y
  221. end
  222. if fposi.z > fposmax.z then
  223. fposmax.z = fposi.z
  224. end
  225. if fposi.x < fposmin.x then
  226. fposmin.x = fposi.x
  227. end
  228. if fposi.y < fposmin.y then
  229. fposmin.y = fposi.y
  230. end
  231. if fposi.z < fposmin.z then
  232. fposmin.z = fposi.z
  233. end
  234. end
  235. fposmid = vector.divide(vector.add(fposmin, fposmax), 2)
  236. end
  237. -- Play sound
  238. local handle = minetest.sound_play(
  239. 'fire_fire',
  240. {
  241. pos = fposmid,
  242. to_player = player_name,
  243. gain = math.min(0.06 * (1 + flames * 0.125), 0.18),
  244. max_hear_distance = 32,
  245. loop = true, -- In case of lag
  246. }
  247. )
  248. -- Store sound handle for this player
  249. if handle then
  250. handles[player_name] = handle
  251. end
  252. end
  253. end
  254. -- Cycle for updating players sounds
  255. minetest.register_globalstep(function(dtime)
  256. timer = timer + dtime
  257. if timer < cycle then
  258. return
  259. end
  260. timer = 0
  261. local players = minetest.get_connected_players()
  262. for n = 1, #players do
  263. fire.update_player_sound(players[n])
  264. end
  265. end)
  266. -- Stop sound and clear handle on player leave
  267. minetest.register_on_leaveplayer(function(player)
  268. local player_name = player:get_player_name()
  269. if handles[player_name] then
  270. minetest.sound_stop(handles[player_name])
  271. handles[player_name] = nil
  272. end
  273. end)
  274. end
  275. -- Deprecated function kept temporarily to avoid crashes if mod fire nodes call it
  276. function fire.update_sounds_around(pos)
  277. end
  278. --
  279. -- ABMs
  280. --
  281. if fire_enabled then
  282. -- Ignite neighboring nodes, add basic flames
  283. minetest.register_abm({
  284. label = 'Ignite flame',
  285. nodenames = {'group:flammable'},
  286. neighbors = {'group:igniter'},
  287. interval = 7,
  288. chance = 12,
  289. catch_up = false,
  290. action = function(pos)
  291. local flammable_node = minetest.get_node(pos)
  292. local def = minetest.registered_nodes[flammable_node.name]
  293. if def.on_ignite then
  294. def.on_ignite(pos)
  295. end
  296. local p = minetest.find_node_near(pos, 1, {'air'})
  297. local anti_fire = minetest.find_node_near(pos, 1, {'epic:fire_extinguishing_powder'})
  298. if p then
  299. minetest.set_node(p, {name = 'fire:basic_flame'})
  300. elseif anti_fire then
  301. epic.spray_foam(pos)
  302. minetest.remove_node(pos)
  303. end
  304. end,
  305. })
  306. -- Remove flammable nodes around basic flame
  307. minetest.register_abm({
  308. label = 'Remove flammable nodes',
  309. nodenames = {'fire:basic_flame'},
  310. neighbors = 'group:flammable',
  311. interval = 5,
  312. chance = 18,
  313. catch_up = false,
  314. action = function(pos)
  315. local p = minetest.find_node_near(pos, 1, {'group:flammable'})
  316. if not p then
  317. return
  318. end
  319. local flammable_node = minetest.get_node(p)
  320. local def = minetest.registered_nodes[flammable_node.name]
  321. if def.on_burn then
  322. def.on_burn(p)
  323. else
  324. minetest.remove_node(p)
  325. minetest.check_for_falling(p)
  326. end
  327. end,
  328. })
  329. end