Re-implement nyancat generation in Lua (they don't occur in the exact same spots...
[oweals/minetest_game.git] / mods / fire / init.lua
1 -- minetest/fire/init.lua
2
3 minetest.register_node("fire:basic_flame", {
4         description = "Fire",
5         drawtype = "glasslike",
6         tile_images = {"fire_basic_flame.png"},
7         light_source = 14,
8         groups = {igniter=3,dig_immediate=3},
9         drop = '',
10         walkable = false,
11 })
12
13 local fire = {}
14 fire.D = 6
15 -- key: position hash of low corner of area
16 -- value: {handle=sound handle, name=sound name}
17 fire.sounds = {}
18
19 function fire.get_area_p0p1(pos)
20         local p0 = {
21                 x=math.floor(pos.x/fire.D)*fire.D,
22                 y=math.floor(pos.y/fire.D)*fire.D,
23                 z=math.floor(pos.z/fire.D)*fire.D,
24         }
25         local p1 = {
26                 x=p0.x+fire.D-1,
27                 y=p0.y+fire.D-1,
28                 z=p0.z+fire.D-1
29         }
30         return p0, p1
31 end
32
33 function fire.update_sounds_around(pos)
34         local p0, p1 = fire.get_area_p0p1(pos)
35         local cp = {x=(p0.x+p1.x)/2, y=(p0.y+p1.y)/2, z=(p0.z+p1.z)/2}
36         local flames_p = minetest.env:find_nodes_in_area(p0, p1, {"fire:basic_flame"})
37         --print("number of flames at "..minetest.pos_to_string(p0).."/"
38         --              ..minetest.pos_to_string(p1)..": "..#flames_p)
39         local should_have_sound = (#flames_p > 0)
40         local wanted_sound = nil
41         if #flames_p >= 9 then
42                 wanted_sound = {name="fire_large", gain=1.5}
43         elseif #flames_p > 0 then
44                 wanted_sound = {name="fire_small", gain=1.5}
45         end
46         local p0_hash = minetest.hash_node_position(p0)
47         local sound = fire.sounds[p0_hash]
48         if not sound then
49                 if should_have_sound then
50                         fire.sounds[p0_hash] = {
51                                 handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}),
52                                 name = wanted_sound.name,
53                         }
54                 end
55         else
56                 if not wanted_sound then
57                         minetest.sound_stop(sound.handle)
58                         fire.sounds[p0_hash] = nil
59                 elseif sound.name ~= wanted_sound.name then
60                         minetest.sound_stop(sound.handle)
61                         fire.sounds[p0_hash] = {
62                                 handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}),
63                                 name = wanted_sound.name,
64                         }
65                 end
66         end
67 end
68
69 function fire.on_flame_add_at(pos)
70         --print("flame added at "..minetest.pos_to_string(pos))
71         fire.update_sounds_around(pos)
72 end
73
74 function fire.on_flame_remove_at(pos)
75         --print("flame removed at "..minetest.pos_to_string(pos))
76         fire.update_sounds_around(pos)
77 end
78
79 function fire.find_pos_for_flame_around(pos)
80         return minetest.env:find_node_near(pos, 1, {"air"})
81 end
82
83 function fire.flame_should_extinguish(pos)
84         --return minetest.env:find_node_near(pos, 1, {"group:puts_out_fire"})
85         local p0 = {x=pos.x-2, y=pos.y, z=pos.z-2}
86         local p1 = {x=pos.x+2, y=pos.y, z=pos.z+2}
87         local ps = minetest.env:find_nodes_in_area(p0, p1, {"group:puts_out_fire"})
88         return (#ps ~= 0)
89 end
90
91 minetest.register_on_placenode(function(pos, newnode, placer)
92         if newnode.name == "fire:basic_flame" then
93                 fire.on_flame_add_at(pos)
94         end
95 end)
96
97 minetest.register_on_dignode(function(pos, oldnode, digger)
98         if oldnode.name == "fire:basic_flame" then
99                 fire.on_flame_remove_at(pos)
100         end
101 end)
102
103 -- Ignite neighboring nodes
104 minetest.register_abm({
105         nodenames = {"group:flammable"},
106         neighbors = {"group:igniter"},
107         interval = 1,
108         chance = 2,
109         action = function(p0, node, _, _)
110                 -- If there is water or stuff like that around flame, don't ignite
111                 if fire.flame_should_extinguish(p0) then
112                         return
113                 end
114                 local p = fire.find_pos_for_flame_around(p0)
115                 if p then
116                         minetest.env:set_node(p, {name="fire:basic_flame"})
117                         fire.on_flame_add_at(p)
118                 end
119         end,
120 })
121
122 -- Rarely ignite things from far
123 minetest.register_abm({
124         nodenames = {"group:igniter"},
125         neighbors = {"air"},
126         interval = 2,
127         chance = 10,
128         action = function(p0, node, _, _)
129                 local reg = minetest.registered_nodes[node.name]
130                 if not reg or not reg.groups.igniter or reg.groups.igniter < 2 then
131                         return
132                 end
133                 local d = reg.groups.igniter
134                 local p = minetest.env:find_node_near(p0, d, {"group:flammable"})
135                 if p then
136                         -- If there is water or stuff like that around flame, don't ignite
137                         if fire.flame_should_extinguish(p) then
138                                 return
139                         end
140                         local p2 = fire.find_pos_for_flame_around(p)
141                         if p2 then
142                                 minetest.env:set_node(p2, {name="fire:basic_flame"})
143                                 fire.on_flame_add_at(p2)
144                         end
145                 end
146         end,
147 })
148
149 -- Remove flammable nodes and flame
150 minetest.register_abm({
151         nodenames = {"fire:basic_flame"},
152         interval = 1,
153         chance = 2,
154         action = function(p0, node, _, _)
155                 -- If there is water or stuff like that around flame, remove flame
156                 if fire.flame_should_extinguish(p0) then
157                         minetest.env:remove_node(p0)
158                         fire.on_flame_remove_at(p0)
159                         return
160                 end
161                 -- Make the following things rarer
162                 if math.random(1,3) == 1 then
163                         return
164                 end
165                 -- If there are no flammable nodes around flame, remove flame
166                 if not minetest.env:find_node_near(p0, 1, {"group:flammable"}) then
167                         minetest.env:remove_node(p0)
168                         fire.on_flame_remove_at(p0)
169                         return
170                 end
171                 if math.random(1,4) == 1 then
172                         -- remove a flammable node around flame
173                         local p = minetest.env:find_node_near(p0, 1, {"group:flammable"})
174                         if p then
175                                 -- If there is water or stuff like that around flame, don't remove
176                                 if fire.flame_should_extinguish(p0) then
177                                         return
178                                 end
179                                 minetest.env:remove_node(p)
180                         end
181                 else
182                         -- remove flame
183                         minetest.env:remove_node(p0)
184                         fire.on_flame_remove_at(p0)
185                 end
186         end,
187 })
188