Damage anvil after falling
[MineClone/MineClone2.git] / mods / ITEMS / mcl_anvils / init.lua
blobf27ee247a2b733b659fba5d24277c78a5c82bc67
1 local MAX_NAME_LENGTH = 30
2 local MAX_WEAR = 65535
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%
8 MAX_WEAR, -- 100%
10 local NAME_COLOR = "#FFFF4C"
12 local function get_anvil_formspec(set_name)
13 if not set_name then
14 set_name = ""
15 end
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]"
31 end
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()
37 if wear == 0 then
38 return 0
39 end
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
46 break
47 end
48 end
49 return materials_used
50 end
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
63 else
64 return nil
65 end
66 end
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
82 -- Repair, if tool
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)
92 if boost then
93 new_health = new_health + boost
94 end
95 return math.max(0, math.min(MAX_WEAR, MAX_WEAR - new_health))
96 end
98 -- Same tool twice
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)
104 name_item = input1
105 new_output = name_item
106 -- Tool + repair item
107 else
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`
113 -- Big repair bonus
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)
128 name_item = tool
129 new_output = name_item
130 else
131 new_output = ""
133 else
134 new_output = ""
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
139 -- Just rename item
140 if input1:is_empty() then
141 name_item = input2
142 else
143 name_item = input1
145 just_rename = true
146 else
147 new_output = ""
150 -- Rename handling
151 if name_item then
152 -- No renaming allowed with group no_rename=1
153 if minetest.get_item_group(name_item:get_name(), "no_rename") == 1 then
154 new_output = ""
155 else
156 if new_name == nil then
157 new_name = ""
159 local meta = name_item:get_meta()
160 local old_name = meta:get_string("name")
161 -- Limit name length
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
165 -- Rename item
166 if new_name == "" then
167 -- Empty name
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)
171 else
172 -- Otherwise, just clear description
173 meta:set_string("description", "")
175 else
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
184 new_output = ""
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)
212 local new
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})
216 return false
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})
220 return false
221 elseif node.name == "mcl_anvils:anvil_damage_2" then
222 -- Destroy anvil
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)
227 return true
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)
234 -- 12% chance
235 if r <= 12 then
236 return damage_anvil(pos)
237 else
238 return false
242 local function damage_anvil_by_falling(pos, distance)
243 local chance
244 local r = math.random(1, 100)
245 if distance > 1 then
246 if r <= (5*distance) then
247 damage_anvil(pos)
252 local anvildef = {
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"},
255 paramtype = "light",
256 sunlight_propagates = true,
257 is_ground_content = false,
258 paramtype2 = "facedir",
259 drawtype = "nodebox",
260 node_box = {
261 type = "fixed",
262 fixed = {
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,
270 _mcl_hardness = 5,
271 _mcl_after_falling = damage_anvil_by_falling,
273 after_dig_node = function(pos, oldnode, oldmetadata, digger)
274 local meta = minetest.get_meta(pos)
275 local meta2 = meta
276 meta:from_table(oldmetadata)
277 drop_anvil_items(pos, meta)
278 meta:from_table(meta2:to_table())
279 end,
280 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
281 if listname == "output" then
282 return 0
283 else
284 return stack:get_count()
286 end,
287 allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
288 if to_list == "output" then
289 return 0
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
294 return count
295 else
296 return 0
298 else
299 return count
301 end,
302 on_metadata_inventory_put = function(pos, listname, index, stack, player)
303 local meta = minetest.get_meta(pos)
304 update_anvil_slots(meta)
305 end,
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
323 if destroyed then
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(), "")
332 end,
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)
347 tool:take_item()
348 if distinguished == "tool" then
349 input1, input2 = tool, material
350 else
351 input1, input2 = material, tool
353 inv:set_stack("input", 1, input1)
354 inv:set_stack("input", 2, input2)
355 else
356 -- Else take 1 item from each stack
357 input1:take_item()
358 input2:take_item()
359 inv:set_stack("input", 1, input1)
360 inv:set_stack("input", 2, input2)
362 else
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
376 if destroyed then
377 -- See above for justification.
378 minetest.close_formspec(player:get_player_name(), "")
380 elseif listname == "input" then
381 update_anvil_slots(meta)
383 end,
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)
391 end,
392 on_receive_fields = function(pos, formname, fields, sender)
393 if fields.name_button or fields.name then
394 local set_name
395 if fields.name == nil then
396 set_name = ""
397 else
398 set_name = fields.name
400 local meta = minetest.get_meta(pos)
401 -- Limit name length
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))
407 end,
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",
448 recipe = {
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")