1 local MAX_NAME_LENGTH
= 30
3 local SAME_TOOL_REPAIR_BOOST
= math
.ceil(MAX_WEAR
* 0.12) -- 12%
4 local MATERIAL_TOOL_REPAIR_BOOST
= {
5 math
.ceil(MAX_WEAR
* 0.25), -- 25%
6 math
.ceil(MAX_WEAR
* 0.5), -- 50%
7 math
.ceil(MAX_WEAR
* 0.75), -- 75%
10 local NAME_COLOR
= "#FFFF4C"
12 local function get_anvil_formspec(set_name
)
16 return "size[9,8.75]"..
17 "background[-0.19,-0.25;9.41,9.49;mcl_anvils_inventory.png]"..
18 mcl_vars
.inventory_header
..
19 "list[current_player;main;0,4.5;9,3;9]"..
20 "list[current_player;main;0,7.74;9,1;]"..
21 "list[context;input;1,2.5;1,1;]"..
22 "list[context;input;4,2.5;1,1;1]"..
23 "list[context;output;8,2.5;1,1;]"..
24 "field[3.25,1;4,1;name;;"..minetest
.formspec_escape(set_name
).."]"..
25 "field_close_on_enter[name;false]"..
26 "button[7,0.7;2,1;name_button;Set Name]"..
27 "listring[context;output]"..
28 "listring[current_player;main]"..
29 "listring[context;input]"..
30 "listring[current_player;main]"
33 -- Given a tool and material stack, returns how many items of the material stack
34 -- needs to be used up to repair the tool.
35 local function get_consumed_materials(tool
, material
)
36 local wear
= tool
:get_wear()
40 local health
= (MAX_WEAR
- wear
)
41 local matsize
= material
:get_count()
42 local materials_used
= 0
43 for m
=1, math
.min(4, matsize
) do
44 materials_used
= materials_used
+ 1
45 if (wear
- MATERIAL_TOOL_REPAIR_BOOST
[m
]) <= 0 then
52 -- Given 2 input stacks, tells you which is the tool and which is the material.
53 -- Returns ("tool", input1, input2) if input1 is tool and input2 is material.
54 -- Returns ("material", input2, input1) if input1 is material and input2 is tool.
55 -- Returns nil otherwise.
56 local function distinguish_tool_and_material(input1
, input2
)
57 local def1
= input1
:get_definition()
58 local def2
= input2
:get_definition()
59 if def1
.type == "tool" and def1
._repair_material
then
60 return "tool", input1
, input2
61 elseif def2
.type == "tool" and def2
._repair_material
then
62 return "material", input2
, input1
68 -- Update the inventory slots of an anvil node.
69 -- meta: Metadata of anvil node
70 local function update_anvil_slots(meta
)
71 local inv
= meta
:get_inventory()
72 local new_name
= meta
:get_string("set_name")
73 local input1
, input2
, output
74 input1
= inv
:get_stack("input", 1)
75 input2
= inv
:get_stack("input", 2)
76 output
= inv
:get_stack("output", 1)
77 local new_output
, name_item
78 local just_rename
= false
80 -- Both input slots occupied
81 if (not input1
:is_empty() and not input2
:is_empty()) then
83 local def1
= input1
:get_definition()
84 local def2
= input2
:get_definition()
86 -- Repair calculation helper.
87 -- Adds the “inverse” values of wear1 and wear2.
88 -- Then adds a boost health value directly.
89 -- Returns the resulting (capped) wear.
90 local function calculate_repair(wear1
, wear2
, boost
)
91 local new_health
= (MAX_WEAR
- wear1
) + (MAX_WEAR
- wear2
)
93 new_health
= new_health
+ boost
95 return math
.max(0, math
.min(MAX_WEAR
, MAX_WEAR
- new_health
))
99 if input1
:get_name() == input2
:get_name() and def1
.type == "tool" and (input1
:get_wear() > 0 or input2
:get_wear() > 0) then
100 -- Add tool health together plus a small bonus
101 -- TODO: Combine tool enchantments
102 local new_wear
= calculate_repair(input1
:get_wear(), input2
:get_wear(), SAME_TOOL_REPAIR_BOOST
)
103 input1
:set_wear(new_wear
)
105 new_output
= name_item
106 -- Tool + repair item
108 -- Any tool can have a repair item. This may be defined in the tool's item definition
109 -- as an itemstring in the field `_repair_material`. Only if this field is set, the
110 -- tool can be repaired with a material item.
111 -- Example: Iron Pickaxe + Iron Ingot. `_repair_material = mcl_core:iron_ingot`
114 -- TODO: Combine tool enchantments
115 local distinguished
, tool
, material
= distinguish_tool_and_material(input1
, input2
)
116 if distinguished
then
117 local tooldef
= tool
:get_definition()
118 local has_correct_material
= false
119 if string.sub(tooldef
._repair_material
, 1, 6) == "group:" then
120 has_correct_material
= minetest
.get_item_group(material
:get_name(), string.sub(tooldef
._repair_material
, 7)) ~= 0
121 elseif material
:get_name() == tooldef
._repair_material
then
122 has_correct_material
= true
124 if has_correct_material
and tool
:get_wear() > 0 then
125 local materials_used
= get_consumed_materials(tool
, material
)
126 local new_wear
= calculate_repair(tool
:get_wear(), MAX_WEAR
, MATERIAL_TOOL_REPAIR_BOOST
[materials_used
])
127 tool
:set_wear(new_wear
)
129 new_output
= name_item
137 -- Exactly 1 input slot occupied
138 elseif (not input1
:is_empty() and input2
:is_empty()) or (input1
:is_empty() and not input2
:is_empty()) then
140 if input1
:is_empty() then
152 -- No renaming allowed with group no_rename=1
153 if minetest
.get_item_group(name_item
:get_name(), "no_rename") == 1 then
156 if new_name
== nil then
159 local meta
= name_item
:get_meta()
160 local old_name
= meta
:get_string("name")
162 new_name
= string.sub(new_name
, 1, MAX_NAME_LENGTH
)
163 -- Don't rename if names are identical
164 if new_name
~= old_name
then
166 if new_name
== "" then
168 if name_item
:get_definition()._mcl_generate_description
then
169 -- _mcl_generate_description(itemstack): If defined, set custom item description of itemstack.
170 name_item
:get_definition()._mcl_generate_description(name_item
)
172 -- Otherwise, just clear description
173 meta
:set_string("description", "")
176 -- Custom name set. Colorize it!
177 -- This makes the name visually different from unnamed items
178 meta
:set_string("description", core
.colorize(NAME_COLOR
, new_name
))
180 -- Save the raw name internally, too
181 meta
:set_string("name", new_name
)
182 new_output
= name_item
183 elseif just_rename
then
189 -- Set the new output slot
190 if new_output
~= nil then
191 inv
:set_stack("output", 1, new_output
)
195 -- Drop input items of anvil at pos with metadata meta
196 local function drop_anvil_items(pos
, meta
)
197 local inv
= meta
:get_inventory()
198 for i
=1, inv
:get_size("input") do
199 local stack
= inv
:get_stack("input", i
)
200 if not stack
:is_empty() then
201 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}
202 minetest
.add_item(p
, stack
)
207 -- Damage the anvil by 1 level.
208 -- Destroy anvil when at highest damage level.
209 -- Returns true if anvil was destroyed.
210 local function damage_anvil(pos
)
211 local node
= minetest
.get_node(pos
)
213 if node
.name
== "mcl_anvils:anvil" then
214 minetest
.swap_node(pos
, {name
="mcl_anvils:anvil_damage_1", param2
=node
.param2
})
215 minetest
.sound_play(mcl_sounds
.node_sound_metal_defaults().dig
, {pos
=pos
, max_hear_distance
=16})
217 elseif node
.name
== "mcl_anvils:anvil_damage_1" then
218 minetest
.swap_node(pos
, {name
="mcl_anvils:anvil_damage_2", param2
=node
.param2
})
219 minetest
.sound_play(mcl_sounds
.node_sound_metal_defaults().dig
, {pos
=pos
, max_hear_distance
=16})
221 elseif node
.name
== "mcl_anvils:anvil_damage_2" then
223 local meta
= minetest
.get_meta(pos
)
224 drop_anvil_items(pos
, meta
)
225 minetest
.sound_play(mcl_sounds
.node_sound_metal_defaults().dug
, {pos
=pos
, max_hear_distance
=16})
226 minetest
.remove_node(pos
)
231 -- Roll a virtual dice and damage anvil at a low chance.
232 local function damage_anvil_by_using(pos
)
233 local r
= math
.random(1, 100)
236 return damage_anvil(pos
)
242 local function damage_anvil_by_falling(pos
, distance
)
244 local r
= math
.random(1, 100)
246 if r
<= (5*distance
) then
253 groups
= {pickaxey
=1, falling_node
=1, falling_node_damage
=1, crush_after_fall
=1, deco_block
=1, anvil
=1},
254 tiles
= {"mcl_anvils_anvil_top_damaged_0.png^[transformR90", "mcl_anvils_anvil_base.png", "mcl_anvils_anvil_side.png"},
256 sunlight_propagates
= true,
257 is_ground_content
= false,
258 paramtype2
= "facedir",
259 drawtype
= "nodebox",
263 {-8/16, 2/16, -5/16, 8/16, 8/16, 5/16}, -- top
264 {-5/16, -4/16, -2/16, 5/16, 5/16, 2/16}, -- middle
265 {-8/16, -8/16, -5/16, 8/16, -4/16, 5/16}, -- base
268 sounds
= mcl_sounds
.node_sound_metal_defaults(),
269 _mcl_blast_resistance
= 6000,
271 _mcl_after_falling
= damage_anvil_by_falling
,
273 after_dig_node
= function(pos
, oldnode
, oldmetadata
, digger
)
274 local meta
= minetest
.get_meta(pos
)
276 meta
:from_table(oldmetadata
)
277 drop_anvil_items(pos
, meta
)
278 meta
:from_table(meta2
:to_table())
280 allow_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
281 if listname
== "output" then
284 return stack
:get_count()
287 allow_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
288 if to_list
== "output" then
290 elseif from_list
== "output" and to_list
== "input" then
291 local meta
= minetest
.get_meta(pos
)
292 local inv
= meta
:get_inventory()
293 if inv
:get_stack(to_list
, to_index
):is_empty() then
302 on_metadata_inventory_put
= function(pos
, listname
, index
, stack
, player
)
303 local meta
= minetest
.get_meta(pos
)
304 update_anvil_slots(meta
)
306 on_metadata_inventory_move
= function(pos
, from_list
, from_index
, to_list
, to_index
, count
, player
)
307 local meta
= minetest
.get_meta(pos
)
308 if from_list
== "output" and to_list
== "input" then
309 local inv
= meta
:get_inventory()
310 for i
=1, inv
:get_size("input") do
311 if i
~= to_index
then
312 local istack
= inv
:get_stack("input", i
)
313 istack
:set_count(math
.max(0, istack
:get_count() - count
))
314 inv
:set_stack("input", i
, istack
)
318 update_anvil_slots(meta
)
320 if from_list
== "output" then
321 local destroyed
= damage_anvil_by_using(pos
)
322 -- Close formspec if anvil was destroyed
324 --[[ Closing the formspec w/ emptyformname is discouraged. But this is justified
325 because node formspecs seem to only have an empty formname in MT 0.4.16.
326 Also, sice this is on_metadata_inventory_take, we KNOW which formspec has
327 been opened by the player. So this should be safe nonetheless.
328 TODO: Update this line when node formspecs get proper identifiers in Minetest. ]]
329 minetest
.close_formspec(player
:get_player_name(), "")
333 on_metadata_inventory_take
= function(pos
, listname
, index
, stack
, player
)
334 local meta
= minetest
.get_meta(pos
)
335 if listname
== "output" then
336 local inv
= meta
:get_inventory()
337 local input1
= inv
:get_stack("input", 1)
338 local input2
= inv
:get_stack("input", 2)
339 -- Both slots occupied?
340 if not input1
:is_empty() and not input2
:is_empty() then
341 -- Take as many items as needed
342 local distinguished
, tool
, material
= distinguish_tool_and_material(input1
, input2
)
343 if distinguished
then
344 -- Tool + material: Take tool and as many materials as needed
345 local materials_used
= get_consumed_materials(tool
, material
)
346 material
:set_count(material
:get_count() - materials_used
)
348 if distinguished
== "tool" then
349 input1
, input2
= tool
, material
351 input1
, input2
= material
, tool
353 inv
:set_stack("input", 1, input1
)
354 inv
:set_stack("input", 2, input2
)
356 -- Else take 1 item from each stack
359 inv
:set_stack("input", 1, input1
)
360 inv
:set_stack("input", 2, input2
)
363 -- Otherwise: Rename mode. Remove the same amount of items from input
364 -- as has been taken from output
365 if not input1
:is_empty() then
366 input1
:set_count(math
.max(0, input1
:get_count() - stack
:get_count()))
367 inv
:set_stack("input", 1, input1
)
369 if not input2
:is_empty() then
370 input2
:set_count(math
.max(0, input2
:get_count() - stack
:get_count()))
371 inv
:set_stack("input", 2, input2
)
374 local destroyed
= damage_anvil_by_using(pos
)
375 -- Close formspec if anvil was destroyed
377 -- See above for justification.
378 minetest
.close_formspec(player
:get_player_name(), "")
380 elseif listname
== "input" then
381 update_anvil_slots(meta
)
384 on_construct
= function(pos
)
385 local meta
= minetest
.get_meta(pos
)
386 local inv
= meta
:get_inventory()
387 inv
:set_size("input", 2)
388 inv
:set_size("output", 1)
389 local form
= get_anvil_formspec()
390 meta
:set_string("formspec", form
)
392 on_receive_fields
= function(pos
, formname
, fields
, sender
)
393 if fields
.name_button
or fields
.name
then
395 if fields
.name
== nil then
398 set_name
= fields
.name
400 local meta
= minetest
.get_meta(pos
)
402 set_name
= string.sub(set_name
, 1, MAX_NAME_LENGTH
)
403 meta
:set_string("set_name", set_name
)
404 update_anvil_slots(meta
)
405 meta
:set_string("formspec", get_anvil_formspec(set_name
))
409 if minetest
.get_modpath("screwdriver") then
410 anvildef
.on_rotate
= screwdriver
.rotate_simple
413 local anvildef0
= table.copy(anvildef
)
414 anvildef0
.description
= "Anvil"
415 anvildef0
._doc_items_longdesc
=
416 [[The anvil allows you to repair tools and armor, and to give names to items. It has a limited durability, however. Don't let it fall on your head, it could be quite painful!]]
417 anvildef0
._doc_items_usagehelp
=
418 "To use an anvil, rightclick it. An anvil has 2 input slots (on the left) and one output slot.".."\n"..
419 "To rename items, put an item stack in one of the item slots while keeping the other input slot empty. Type in a name, hit enter or “Set Name”, then take the renamed item from the output slot.".."\n"..
420 "There are two possibilities to repair tools (and armor):".."\n"..
421 "• Tool + Tool: Place two tools of the same type in the input slots. The “health” of the repaired tool is the sum of the “health” of both input tools, plus a 12% bonus.".."\n"..
422 "• Tool + Material: Some tools can also be repaired by combining them with an item that it's made of. For example, iron pickaxes can be repaired with iron ingots. This repairs the tool by 25%.".."\n"..
423 "Armor counts as a tool. It is possible to repair and rename a tool in a single step.".."\n\n"..
424 "The anvil has limited durability and 3 damage levels: undamaged, slightly damaged and very damaged. Each time you repair or rename something, there is a 12% chance the anvil gets damaged. Anvils also have a chance of being damaged when they fall by more than 1 block. If a very damaged anvil is damaged again, it is destroyed."
426 local anvildef1
= table.copy(anvildef
)
427 anvildef1
.description
= "Slightly Damaged Anvil"
428 anvildef1
._doc_items_create_entry
= false
429 anvildef1
.groups
.not_in_creative_inventory
= 1
430 anvildef1
.groups
.anvil
= 2
431 anvildef1
._doc_items_create_entry
= false
432 anvildef1
.tiles
= {"mcl_anvils_anvil_top_damaged_1.png^[transformR90", "mcl_anvils_anvil_base.png", "mcl_anvils_anvil_side.png"}
434 local anvildef2
= table.copy(anvildef
)
435 anvildef2
.description
= "Very Damaged Anvil"
436 anvildef2
._doc_items_create_entry
= false
437 anvildef2
.groups
.not_in_creative_inventory
= 1
438 anvildef2
.groups
.anvil
= 3
439 anvildef2
._doc_items_create_entry
= false
440 anvildef2
.tiles
= {"mcl_anvils_anvil_top_damaged_2.png^[transformR90", "mcl_anvils_anvil_base.png", "mcl_anvils_anvil_side.png"}
442 minetest
.register_node("mcl_anvils:anvil", anvildef0
)
443 minetest
.register_node("mcl_anvils:anvil_damage_1", anvildef1
)
444 minetest
.register_node("mcl_anvils:anvil_damage_2", anvildef2
)
446 minetest
.register_craft({
447 output
= "mcl_anvils:anvil",
449 { "mcl_core:ironblock", "mcl_core:ironblock", "mcl_core:ironblock" },
450 { "", "mcl_core:iron_ingot", "" },
451 { "mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot" },
455 if minetest
.get_modpath("doc") then
456 doc
.add_entry_alias("nodes", "mcl_anvils:anvil", "nodes", "mcl_anvils:anvil_damage_1")
457 doc
.add_entry_alias("nodes", "mcl_anvils:anvil", "nodes", "mcl_anvils:anvil_damage_2")