2 local S
= minetest
.get_translator("mcl_furnaces")
4 local LIGHT_ACTIVE_FURNACE
= 13
10 local function active_formspec(fuel_percent
, item_percent
)
11 return "size[9,8.75]"..
12 "label[0,4;"..minetest
.formspec_escape(minetest
.colorize("#313131", S("Inventory"))).."]"..
13 "list[current_player;main;0,4.5;9,3;9]"..
14 mcl_formspec
.get_itemslot_bg(0,4.5,9,3)..
15 "list[current_player;main;0,7.74;9,1;]"..
16 mcl_formspec
.get_itemslot_bg(0,7.74,9,1)..
17 "label[2.75,0;"..minetest
.formspec_escape(minetest
.colorize("#313131", S("Furnace"))).."]"..
18 "list[current_name;src;2.75,0.5;1,1;]"..
19 mcl_formspec
.get_itemslot_bg(2.75,0.5,1,1)..
20 "list[current_name;fuel;2.75,2.5;1,1;]"..
21 mcl_formspec
.get_itemslot_bg(2.75,2.5,1,1)..
22 "list[current_name;dst;5.75,1.5;1,1;]"..
23 mcl_formspec
.get_itemslot_bg(5.75,1.5,1,1)..
24 "image[2.75,1.5;1,1;default_furnace_fire_bg.png^[lowpart:"..
25 (100-fuel_percent
)..":default_furnace_fire_fg.png]"..
26 "image[4.1,1.5;1.5,1;gui_furnace_arrow_bg.png^[lowpart:"..
27 (item_percent
)..":gui_furnace_arrow_fg.png^[transformR270]"..
28 -- Craft guide button temporarily removed due to Minetest bug.
29 -- TODO: Add it back when the Minetest bug is fixed.
30 --"image_button[8,0;1,1;craftguide_book.png;craftguide;]"..
31 --"tooltip[craftguide;"..minetest.formspec_escape(S("Recipe book")).."]"..
32 "listring[current_name;dst]"..
33 "listring[current_player;main]"..
34 "listring[current_name;src]"..
35 "listring[current_player;main]"..
36 "listring[current_name;fuel]"..
37 "listring[current_player;main]"
40 local inactive_formspec
= "size[9,8.75]"..
41 "label[0,4;"..minetest
.formspec_escape(minetest
.colorize("#313131", S("Inventory"))).."]"..
42 "list[current_player;main;0,4.5;9,3;9]"..
43 mcl_formspec
.get_itemslot_bg(0,4.5,9,3)..
44 "list[current_player;main;0,7.74;9,1;]"..
45 mcl_formspec
.get_itemslot_bg(0,7.74,9,1)..
46 "label[2.75,0;"..minetest
.formspec_escape(minetest
.colorize("#313131", S("Furnace"))).."]"..
47 "list[current_name;src;2.75,0.5;1,1;]"..
48 mcl_formspec
.get_itemslot_bg(2.75,0.5,1,1)..
49 "list[current_name;fuel;2.75,2.5;1,1;]"..
50 mcl_formspec
.get_itemslot_bg(2.75,2.5,1,1)..
51 "list[current_name;dst;5.75,1.5;1,1;]"..
52 mcl_formspec
.get_itemslot_bg(5.75,1.5,1,1)..
53 "image[2.75,1.5;1,1;default_furnace_fire_bg.png]"..
54 "image[4.1,1.5;1.5,1;gui_furnace_arrow_bg.png^[transformR270]"..
55 -- Craft guide button temporarily removed due to Minetest bug.
56 -- TODO: Add it back when the Minetest bug is fixed.
57 --"image_button[8,0;1,1;craftguide_book.png;craftguide;]"..
58 --"tooltip[craftguide;"..minetest.formspec_escape(S("Recipe book")).."]"..
59 "listring[current_name;dst]"..
60 "listring[current_player;main]"..
61 "listring[current_name;src]"..
62 "listring[current_player;main]"..
63 "listring[current_name;fuel]"..
64 "listring[current_player;main]"
66 local receive_fields
= function(pos
, formname
, fields
, sender
)
67 if fields
.craftguide
then
68 mcl_craftguide
.show(sender
:get_player_name())
73 -- Node callback functions that are the same for active and inactive furnace
76 local function allow_metadata_inventory_put(pos
, listname
, index
, stack
, player
)
77 local name
= player
:get_player_name()
78 if minetest
.is_protected(pos
, name
) then
79 minetest
.record_protection_violation(pos
, name
)
82 local meta
= minetest
.get_meta(pos
)
83 local inv
= meta
:get_inventory()
84 if listname
== "fuel" then
85 -- Special case: empty bucket (not a fuel, but used for sponge drying)
86 if stack
:get_name() == "mcl_buckets:bucket_empty" then
87 if inv
:get_stack(listname
, index
):get_count() == 0 then
94 -- Test stack with size 1 because we burn one fuel at a time
95 local teststack
= ItemStack(stack
)
96 teststack
:set_count(1)
97 local output
, decremented_input
= minetest
.get_craft_result({method
="fuel", width
=1, items
={teststack
}})
98 if output
.time
~= 0 then
99 -- Only allow to place 1 item if fuel get replaced by recipe.
100 -- This is the case for lava buckets.
101 local replace_item
= decremented_input
.items
[1]
102 if replace_item
:is_empty() then
103 -- For most fuels, just allow to place everything
104 return stack
:get_count()
106 if inv
:get_stack(listname
, index
):get_count() == 0 then
115 elseif listname
== "src" then
116 return stack
:get_count()
117 elseif listname
== "dst" then
122 local function allow_metadata_inventory_move(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
123 local meta
= minetest
.get_meta(pos
)
124 local inv
= meta
:get_inventory()
125 local stack
= inv
:get_stack(from_list
, from_index
)
126 return allow_metadata_inventory_put(pos
, to_list
, to_index
, stack
, player
)
129 local function allow_metadata_inventory_take(pos
, listname
, index
, stack
, player
)
130 local name
= player
:get_player_name()
131 if minetest
.is_protected(pos
, name
) then
132 minetest
.record_protection_violation(pos
, name
)
135 return stack
:get_count()
138 local function on_metadata_inventory_take(pos
, listname
, index
, stack
, player
)
139 -- Award smelting achievements
140 if listname
== "dst" then
141 if stack
:get_name() == "mcl_core:iron_ingot" then
142 awards
.unlock(player
:get_player_name(), "mcl:acquireIron")
143 elseif stack
:get_name() == "mcl_fishing:fish_cooked" then
144 awards
.unlock(player
:get_player_name(), "mcl:cookFish")
149 local function swap_node(pos
, name
)
150 local node
= minetest
.get_node(pos
)
151 if node
.name
== name
then
155 minetest
.swap_node(pos
, node
)
158 local function furnace_reset_delta_time(pos
)
159 local meta
= minetest
.get_meta(pos
)
160 local time_multiplier
= 86400 / (minetest
.settings
:get('time_speed') or 72)
161 local current_game_time
= .0 + ((minetest
.get_day_count() + minetest
.get_timeofday()) * time_multiplier
)
163 -- TODO: Change meta:get/set_string() to get/set_float() for 'last_gametime'.
164 -- In Windows *_float() works OK but under Linux it returns rounded unusable values like 449540.000000000
165 local last_game_time
= meta
:get_string("last_gametime")
166 if last_game_time
then
167 last_game_time
= tonumber(last_game_time
)
169 if not last_game_time
or last_game_time
< 1 or math
.abs(last_game_time
- current_game_time
) <= 1.5 then
173 meta
:set_string("last_gametime", tostring(current_game_time
))
176 local function furnace_get_delta_time(pos
)
177 local meta
= minetest
.get_meta(pos
)
178 local time_multiplier
= 86400 / (minetest
.settings
:get('time_speed') or 72)
179 local current_game_time
= .0 + ((minetest
.get_day_count() + minetest
.get_timeofday()) * time_multiplier
)
181 local last_game_time
= meta
:get_string("last_gametime")
182 if last_game_time
then
183 last_game_time
= tonumber(last_game_time
)
185 if not last_game_time
or last_game_time
< 1 then
186 last_game_time
= current_game_time
187 elseif last_game_time
== current_game_time
then
188 current_game_time
= current_game_time
+ 1.0
191 local elapsed_game_time
= .0 + current_game_time
- last_game_time
193 meta
:set_string("last_gametime", tostring(current_game_time
))
195 return meta
, elapsed_game_time
198 local function furnace_node_timer(pos
, elapsed
)
200 -- Inizialize metadata
202 local meta
, elapsed_game_time
= furnace_get_delta_time(pos
)
204 local fuel_time
= meta
:get_float("fuel_time") or 0
205 local src_time
= meta
:get_float("src_time") or 0
206 local src_item
= meta
:get_string("src_item") or ""
207 local fuel_totaltime
= meta
:get_float("fuel_totaltime") or 0
209 local inv
= meta
:get_inventory()
210 local srclist
, fuellist
212 local cookable
, cooked
216 srclist
= inv
:get_list("src")
217 fuellist
= inv
:get_list("fuel")
219 -- Check if src item has been changed
220 if srclist
[1]:get_name() ~= src_item
then
221 -- Reset cooking progress in this case
223 src_item
= srclist
[1]:get_name()
227 while elapsed_game_time
> 0.00001 and update
do
232 local el
= elapsed_game_time
234 -- Check if we have cookable content: cookable
236 cooked
, aftercooked
= minetest
.get_craft_result({method
= "cooking", width
= 1, items
= srclist
})
237 cookable
= cooked
.time
~= 0
239 -- Successful cooking requires space in dst slot and time
240 if not inv
:room_for_item("dst", cooked
.item
) then
245 if cookable
then -- fuel lasts long enough, adjust el to cooking duration
246 el
= math
.min(el
, cooked
.time
- src_time
)
249 -- Check if we have enough fuel to burn
250 active
= fuel_time
< fuel_totaltime
251 if cookable
and not active
then
252 -- We need to get new fuel
254 fuel
, afterfuel
= minetest
.get_craft_result({method
= "fuel", width
= 1, items
= fuellist
})
256 if fuel
.time
== 0 then
257 -- No valid fuel in fuel list -- stop
262 -- Take fuel from fuel list
263 inv
:set_stack("fuel", 1, afterfuel
.items
[1])
265 fuel_totaltime
= fuel
.time
266 el
= math
.min(el
, fuel_totaltime
)
268 fuellist
= inv
:get_list("fuel")
271 el
= math
.min(el
, fuel_totaltime
- fuel_time
)
272 -- The furnace is currently active and has enough fuel
273 fuel_time
= fuel_time
+ el
276 -- If there is a cookable item then check if it is ready yet
277 if cookable
and active
then
278 src_time
= src_time
+ el
279 -- Place result in dst list if done
280 if src_time
>= cooked
.time
then
281 inv
:add_item("dst", cooked
.item
)
282 inv
:set_stack("src", 1, aftercooked
.items
[1])
284 -- Unique recipe: Pour water into empty bucket after cooking wet sponge successfully
285 if inv
:get_stack("fuel", 1):get_name() == "mcl_buckets:bucket_empty" then
286 if srclist
[1]:get_name() == "mcl_sponges:sponge_wet" then
287 inv
:set_stack("fuel", 1, "mcl_buckets:bucket_water")
288 fuellist
= inv
:get_list("fuel")
289 -- Also for river water
290 elseif srclist
[1]:get_name() == "mcl_sponges:sponge_wet_river_water" then
291 inv
:set_stack("fuel", 1, "mcl_buckets:bucket_river_water")
292 fuellist
= inv
:get_list("fuel")
296 srclist
= inv
:get_list("src")
301 elapsed_game_time
= elapsed_game_time
- el
304 if fuel
and fuel_totaltime
> fuel
.time
then
305 fuel_totaltime
= fuel
.time
307 if srclist
and srclist
[1]:is_empty() then
312 -- Update formspec and node
314 local formspec
= inactive_formspec
316 local item_percent
= 0
318 item_percent
= math
.floor(src_time
/ cooked
.time
* 100)
324 local fuel_percent
= math
.floor(fuel_time
/ fuel_totaltime
* 100)
325 formspec
= active_formspec(fuel_percent
, item_percent
)
326 swap_node(pos
, "mcl_furnaces:furnace_active")
327 -- make sure timer restarts automatically
330 swap_node(pos
, "mcl_furnaces:furnace")
331 -- stop timer on the inactive furnace
332 minetest
.get_node_timer(pos
):stop()
338 meta
:set_float("fuel_totaltime", fuel_totaltime
)
339 meta
:set_float("fuel_time", fuel_time
)
340 meta
:set_float("src_time", src_time
)
342 meta
:set_string("src_item", srclist
[1]:get_name())
344 meta
:set_string("src_item", "")
346 meta
:set_string("formspec", formspec
)
351 local function spawn_flames(pos
, param2
)
352 local minrelpos
, maxrelpos
353 local dir
= minetest
.facedir_to_dir(param2
)
355 minrelpos
= { x
= -0.6, y
= -0.05, z
= -0.25 }
356 maxrelpos
= { x
= -0.55, y
= -0.45, z
= 0.25 }
357 elseif dir
.x
< 0 then
358 minrelpos
= { x
= 0.55, y
= -0.05, z
= -0.25 }
359 maxrelpos
= { x
= 0.6, y
= -0.45, z
= 0.25 }
360 elseif dir
.z
> 0 then
361 minrelpos
= { x
= -0.25, y
= -0.05, z
= -0.6 }
362 maxrelpos
= { x
= 0.25, y
= -0.45, z
= -0.55 }
363 elseif dir
.z
< 0 then
364 minrelpos
= { x
= -0.25, y
= -0.05, z
= 0.55 }
365 maxrelpos
= { x
= 0.25, y
= -0.45, z
= 0.6 }
369 mcl_particles
.add_node_particlespawner(pos
, {
372 minpos
= vector
.add(pos
, minrelpos
),
373 maxpos
= vector
.add(pos
, maxrelpos
),
374 minvel
= { x
= -0.01, y
= 0, z
= -0.01 },
375 maxvel
= { x
= 0.01, y
= 0.1, z
= 0.01 },
380 texture
= "mcl_particles_flame.png",
381 glow
= LIGHT_ACTIVE_FURNACE
,
385 local on_rotate
, after_rotate_active
386 if minetest
.get_modpath("screwdriver") then
387 on_rotate
= screwdriver
.rotate_simple
388 after_rotate_active
= function(pos
)
389 local node
= minetest
.get_node(pos
)
390 mcl_particles
.delete_node_particlespawners(pos
)
391 spawn_flames(pos
, node
.param2
)
395 minetest
.register_node("mcl_furnaces:furnace", {
396 description
= S("Furnace"),
397 _tt_help
= S("Uses fuel to smelt or cook items"),
398 _doc_items_longdesc
= S("Furnaces cook or smelt several items, using a furnace fuel, into something else."),
399 _doc_items_usagehelp
=
400 S("Use the furnace to open the furnace menu. Place a furnace fuel in the lower slot and the source material in the upper slot. The furnace will slowly use its fuel to smelt the item. The result will be placed into the output slot at the right side.").."\n"..
401 S("Use the recipe book to see what you can smelt, what you can use as fuel and how long it will burn."),
402 _doc_items_hidden
= false,
404 "default_furnace_top.png", "default_furnace_bottom.png",
405 "default_furnace_side.png", "default_furnace_side.png",
406 "default_furnace_side.png", "default_furnace_front.png"
408 paramtype2
= "facedir",
409 groups
= {pickaxey
=1, container
=4, deco_block
=1, material_stone
=1},
410 is_ground_content
= false,
411 sounds
= mcl_sounds
.node_sound_stone_defaults(),
413 on_timer
= furnace_node_timer
,
414 after_dig_node
= function(pos
, oldnode
, oldmetadata
, digger
)
415 local meta
= minetest
.get_meta(pos
)
417 meta
:from_table(oldmetadata
)
418 local inv
= meta
:get_inventory()
419 for _
, listname
in ipairs({"src", "dst", "fuel"}) do
420 local stack
= inv
:get_stack(listname
, 1)
421 if not stack
:is_empty() then
422 local p
= {x
=pos
.x
+math
.random(0, 10)/10-0.5, y
=pos
.y
, z
=pos
.z
+math
.random(0, 10)/10-0.5}
423 minetest
.add_item(p
, stack
)
426 meta
:from_table(meta2
:to_table())
429 on_construct
= function(pos
)
430 local meta
= minetest
.get_meta(pos
)
431 meta
:set_string("formspec", inactive_formspec
)
432 local inv
= meta
:get_inventory()
433 inv
:set_size('src', 1)
434 inv
:set_size('fuel', 1)
435 inv
:set_size('dst', 1)
438 on_metadata_inventory_move
= function(pos
)
439 -- Reset accumulated game time when player works with furnace:
440 furnace_reset_delta_time(pos
)
441 minetest
.get_node_timer(pos
):start(1.0)
443 on_metadata_inventory_put
= function(pos
)
444 -- Reset accumulated game time when player works with furnace:
445 furnace_reset_delta_time(pos
)
446 -- start timer function, it will sort out whether furnace can burn or not.
447 minetest
.get_node_timer(pos
):start(1.0)
449 on_metadata_inventory_take
= function(pos
)
450 -- Reset accumulated game time when player works with furnace:
451 furnace_reset_delta_time(pos
)
452 -- start timer function, it will helpful if player clears dst slot
453 minetest
.get_node_timer(pos
):start(1.0)
456 allow_metadata_inventory_put
= allow_metadata_inventory_put
,
457 allow_metadata_inventory_move
= allow_metadata_inventory_move
,
458 allow_metadata_inventory_take
= allow_metadata_inventory_take
,
459 on_receive_fields
= receive_fields
,
460 _mcl_blast_resistance
= 3.5,
462 on_rotate
= on_rotate
,
465 minetest
.register_node("mcl_furnaces:furnace_active", {
466 description
= S("Burning Furnace"),
467 _doc_items_create_entry
= false,
469 "default_furnace_top.png", "default_furnace_bottom.png",
470 "default_furnace_side.png", "default_furnace_side.png",
471 "default_furnace_side.png", "default_furnace_front_active.png",
473 paramtype2
= "facedir",
475 light_source
= LIGHT_ACTIVE_FURNACE
,
476 drop
= "mcl_furnaces:furnace",
477 groups
= {pickaxey
=1, container
=4, deco_block
=1, not_in_creative_inventory
=1, material_stone
=1},
478 is_ground_content
= false,
479 sounds
= mcl_sounds
.node_sound_stone_defaults(),
480 on_timer
= furnace_node_timer
,
482 after_dig_node
= function(pos
, oldnode
, oldmetadata
, digger
)
483 local meta
= minetest
.get_meta(pos
)
485 meta
:from_table(oldmetadata
)
486 local inv
= meta
:get_inventory()
487 for _
, listname
in ipairs({"src", "dst", "fuel"}) do
488 local stack
= inv
:get_stack(listname
, 1)
489 if not stack
:is_empty() then
490 local p
= {x
=pos
.x
+math
.random(0, 10)/10-0.5, y
=pos
.y
, z
=pos
.z
+math
.random(0, 10)/10-0.5}
491 minetest
.add_item(p
, stack
)
494 meta
:from_table(meta2
:to_table())
497 on_construct
= function(pos
)
498 local node
= minetest
.get_node(pos
)
499 spawn_flames(pos
, node
.param2
)
501 on_destruct
= function(pos
)
502 mcl_particles
.delete_node_particlespawners(pos
)
505 allow_metadata_inventory_put
= allow_metadata_inventory_put
,
506 allow_metadata_inventory_move
= allow_metadata_inventory_move
,
507 allow_metadata_inventory_take
= allow_metadata_inventory_take
,
508 on_metadata_inventory_take
= on_metadata_inventory_take
,
509 on_receive_fields
= receive_fields
,
510 _mcl_blast_resistance
= 3.5,
512 on_rotate
= on_rotate
,
513 after_rotate
= after_rotate_active
,
516 minetest
.register_craft({
517 output
= "mcl_furnaces:furnace",
519 { "mcl_core:cobble", "mcl_core:cobble", "mcl_core:cobble" },
520 { "mcl_core:cobble", "", "mcl_core:cobble" },
521 { "mcl_core:cobble", "mcl_core:cobble", "mcl_core:cobble" },
525 -- Add entry alias for the Help
526 if minetest
.get_modpath("doc") then
527 doc
.add_entry_alias("nodes", "mcl_furnaces:furnace", "nodes", "mcl_furnaces:furnace_active")
530 minetest
.register_lbm({
531 label
= "Active furnace flame particles",
532 name
= "mcl_furnaces:flames",
533 nodenames
= {"mcl_furnaces:furnace_active"},
534 run_at_every_load
= true,
535 action
= function(pos
, node
)
536 spawn_flames(pos
, node
.param2
)
541 minetest
.register_lbm({
542 label
= "Update furnace formspecs (0.60.0",
543 name
= "mcl_furnaces:update_formspecs_0_60_0",
544 -- Only update inactive furnaces because active ones should update themselves
545 nodenames
= { "mcl_furnaces:furnace" },
546 run_at_every_load
= false,
547 action
= function(pos
, node
)
548 local meta
= minetest
.get_meta(pos
)
549 meta
:set_string("formspec", inactive_formspec
)