function mcl_enchanting.is_book(itemname) return itemname == "mcl_books:book" or itemname == "mcl_enchanting:book_enchanted" end function mcl_enchanting.get_enchantments(itemstack) return minetest.deserialize(itemstack:get_meta():get_string("mcl_enchanting:enchantments")) or {} end function mcl_enchanting.set_enchantments(itemstack, enchantments) itemstack:get_meta():set_string("mcl_enchanting:enchantments", minetest.serialize(enchantments)) local itemdef = itemstack:get_definition() if not mcl_enchanting.is_book(itemstack:get_name()) then if itemdef.tool_capabilities then itemstack:get_meta():set_tool_capabilities(itemdef.tool_capabilities) end for enchantment, level in pairs(enchantments) do local enchantment_def = mcl_enchanting.enchantments[enchantment] if enchantment_def.on_enchant then enchantment_def.on_enchant(itemstack, level) end end end tt.reload_itemstack_description(itemstack) end function mcl_enchanting.get_enchantment(itemstack, enchantment) return mcl_enchanting.get_enchantments(itemstack)[enchantment] or 0 end function mcl_enchanting.has_enchantment(itemstack, enchantment) return mcl_enchanting.get_enchantment(itemstack, enchantment) > 0 end function mcl_enchanting.get_enchantment_description(enchantment, level) local enchantment_def = mcl_enchanting.enchantments[enchantment] return enchantment_def.name .. (enchantment_def.max_level == 1 and "" or " " .. mcl_enchanting.roman_numerals.toRoman(level)) end function mcl_enchanting.get_colorized_enchantment_description(enchantment, level) return minetest.colorize(mcl_enchanting.enchantments[enchantment].curse and "#FC5454" or "#A8A8A8", mcl_enchanting.get_enchantment_description(enchantment, level)) end function mcl_enchanting.get_enchanted_itemstring(itemname) local def = minetest.registered_items[itemname] return def and def._mcl_enchanting_enchanted_tool end function mcl_enchanting.set_enchanted_itemstring(itemstack) itemstack:set_name(mcl_enchanting.get_enchanted_itemstring(itemstack:get_name())) end function mcl_enchanting.is_enchanted(itemname) return minetest.get_item_group(itemname, "enchanted") > 0 end function mcl_enchanting.is_enchantable(itemname) return mcl_enchanting.get_enchantability(itemname) > 0 end function mcl_enchanting.can_enchant_freshly(itemname) return mcl_enchanting.is_enchantable(itemname) and not mcl_enchanting.is_enchanted(itemname) end function mcl_enchanting.get_enchantability(itemname) return minetest.get_item_group(itemname, "enchantability") end function mcl_enchanting.item_supports_enchantment(itemname, enchantment, early) if not mcl_enchanting.is_enchantable(itemname) then return false end local enchantment_def = mcl_enchanting.enchantments[enchantment] if mcl_enchanting.is_book(itemname) then return true, (not enchantment_def.treasure) end local itemdef = minetest.registered_items[itemname] if itemdef.type ~= "tool" and enchantment_def.requires_tool then return false end for disallow in pairs(enchantment_def.disallow) do if minetest.get_item_group(itemname, disallow) > 0 then return false end end for group in pairs(enchantment_def.primary) do if minetest.get_item_group(itemname, group) > 0 then return true, true end end for group in pairs(enchantment_def.secondary) do if minetest.get_item_group(itemname, group) > 0 then return true, false end end return false end function mcl_enchanting.can_enchant(itemstack, enchantment, level) local enchantment_def = mcl_enchanting.enchantments[enchantment] if not enchantment_def then return false, "enchantment invalid" end local itemname = itemstack:get_name() if itemname == "" then return false, "item missing" end local supported, primary = mcl_enchanting.item_supports_enchantment(itemstack:get_name(), enchantment) if not supported then return false, "item not supported" end if not level then return false, "level invalid" end if level > enchantment_def.max_level then return false, "level too high", enchantment_def.max_level elseif level < 1 then return false, "level too small", 1 end local item_enchantments = mcl_enchanting.get_enchantments(itemstack) local enchantment_level = item_enchantments[enchantment] if enchantment_level then return false, "incompatible", mcl_enchanting.get_enchantment_description(enchantment, enchantment_level) end if not mcl_enchanting.is_book(itemname) then for incompatible in pairs(enchantment_def.incompatible) do local incompatible_level = item_enchantments[incompatible] if incompatible_level then return false, "incompatible", mcl_enchanting.get_enchantment_description(incompatible, incompatible_level) end end end return true, nil, nil, primary end function mcl_enchanting.enchant(itemstack, enchantment, level) mcl_enchanting.set_enchanted_itemstring(itemstack) local enchantments = mcl_enchanting.get_enchantments(itemstack) enchantments[enchantment] = level mcl_enchanting.set_enchantments(itemstack, enchantments) return itemstack end function mcl_enchanting.combine(itemstack, combine_with) local itemname = itemstack:get_name() local combine_name = combine_with:get_name() local enchanted_itemname = mcl_enchanting.get_enchanted_itemstring(itemname) if enchanted_itemname ~= mcl_enchanting.get_enchanted_itemstring(combine_name) and not mcl_enchanting.is_book(itemname) then return false end local enchantments = mcl_enchanting.get_enchantments(itemstack) for enchantment, combine_level in pairs(mcl_enchanting.get_enchantments(combine_with)) do local enchantment_def = mcl_enchanting.enchantments[enchantment] local enchantment_level = enchantments[enchantment] if enchantment_level then if enchantment_level == combine_level then enchantment_level = math.min(enchantment_level + 1, enchantment_def.max_level) else enchantment_level = math.max(enchantment_level, combine_level) end elseif mcl_enchanting.item_supports_enchantment(itemname, enchantment) then local supported = true for incompatible in pairs(enchantment_def.incompatible) do if enchantments[incompatible] then supported = false break end end if supported then enchantment_level = combine_level end end if enchantment_level and enchantment_level > 0 then enchantments[enchantment] = enchantment_level end end local any_enchantment = false for enchantment, enchantment_level in pairs(enchantments) do if enchantment_level > 0 then any_enchantment = true break end end if any_enchantment then itemstack:set_name(enchanted_itemname) end mcl_enchanting.set_enchantments(itemstack, enchantments) return true end function mcl_enchanting.enchantments_snippet(_, _, itemstack) if not itemstack then return end local enchantments = mcl_enchanting.get_enchantments(itemstack) local text = "" for enchantment, level in pairs(enchantments) do text = text .. mcl_enchanting.get_colorized_enchantment_description(enchantment, level) .. "\n" end if text ~= "" then if not itemstack:get_definition()._tt_original_description then text = text:sub(1, text:len() - 1) end return text, false end end function mcl_enchanting.initialize() local register_tool_list = {} local register_item_list = {} for itemname, itemdef in pairs(minetest.registered_items) do if mcl_enchanting.can_enchant_freshly(itemname) then local new_name = itemname .. "_enchanted" minetest.override_item(itemname, {_mcl_enchanting_enchanted_tool = new_name}) local new_def = table.copy(itemdef) new_def.inventory_image = itemdef.inventory_image .. mcl_enchanting.overlay if new_def.wield_image then new_def.wield_image = new_def.wield_image .. mcl_enchanting.overlay end new_def.groups.not_in_creative_inventory = 1 new_def.groups.enchanted = 1 new_def.texture = itemdef.texture or itemname:gsub("%:", "_") new_def._mcl_enchanting_enchanted_tool = new_name local register_list = register_item_list if itemdef.type == "tool" then register_list = register_tool_list end register_list[":" .. new_name] = new_def end end for new_name, new_def in pairs(register_item_list) do minetest.register_craftitem(new_name, new_def) end for new_name, new_def in pairs(register_tool_list) do minetest.register_tool(new_name, new_def) end end function mcl_enchanting.get_possible_enchantments(itemstack, enchantment_level, treasure) local possible_enchantments, weights, accum_weight = {}, {}, 0 for enchantment, enchantment_def in pairs(mcl_enchanting.enchantments) do local supported, _, _, primary = mcl_enchanting.can_enchant(itemstack, enchantment, 1) if primary or treasure then table.insert(possible_enchantments, enchantment) accum_weight = accum_weight + enchantment_def.weight weights[enchantment] = accum_weight end end return possible_enchantments, weights, accum_weight end function mcl_enchanting.generate_random_enchantments(itemstack, enchantment_level, treasure, no_reduced_bonus_chance) local itemname = itemstack:get_name() if not mcl_enchanting.can_enchant_freshly(itemname) then return end itemstack = ItemStack(itemstack) local enchantability = minetest.get_item_group(itemname, "enchantability") enchantability = 1 + math.random(0, math.floor(enchantability / 4)) + math.random(0, math.floor(enchantability / 4)) enchantment_level = enchantment_level + enchantability enchantment_level = enchantment_level + enchantment_level * (math.random() + math.random() - 1) * 0.15 enchantment_level = math.max(math.floor(enchantment_level + 0.5), 1) local enchantments = {} local description enchantment_level = enchantment_level * 2 repeat enchantment_level = math.floor(enchantment_level / 2) if enchantment_level == 0 then break end local possible, weights, accum_weight = mcl_enchanting.get_possible_enchantments(itemstack, enchantment_level, treasure) local selected_enchantment, enchantment_power if #possible > 0 then local r = math.random(accum_weight) for _, enchantment in ipairs(possible) do if weights[enchantment] >= r then selected_enchantment = enchantment break end end local enchantment_def = mcl_enchanting.enchantments[selected_enchantment] local power_range_table = enchantment_def.power_range_table for i = enchantment_def.max_level, 1, -1 do local power_range = power_range_table[i] if enchantment_level >= power_range[1] and enchantment_level <= power_range[2] then enchantment_power = i break end end if not description then if not enchantment_power then return end description = mcl_enchanting.get_enchantment_description(selected_enchantment, enchantment_power) end if enchantment_power then enchantments[selected_enchantment] = enchantment_power mcl_enchanting.enchant(itemstack, selected_enchantment, enchantment_power) end else break end until not no_reduced_bonus_chance and math.random() >= (enchantment_level + 1) / 50 return enchantments, description end function mcl_enchanting.enchant_randomly(itemstack, enchantment_level, treasure, no_reduced_bonus_chance) local enchantments = mcl_enchanting.generate_random_enchantments(itemstack, enchantment_level, treasure, no_reduced_bonus_chance) if enchantments then mcl_enchanting.set_enchanted_itemstring(itemstack) mcl_enchanting.set_enchantments(itemstack, enchantments) end return itemstack end function mcl_enchanting.get_randomly_enchanted_book(enchantment_level, treasure, no_reduced_bonus_chance) return mcl_enchanting.enchant_randomly(enchantment_level, treasure, no_reduced_bonus_chance) end function mcl_enchanting.get_random_glyph_row() local glyphs = "" local x = 1.3 for i = 1, 9 do glyphs = glyphs .. "image[".. x .. ",0.1;0.5,0.5;mcl_enchanting_glyph_" .. math.random(18) .. ".png^[colorize:#675D49:255]" x = x + 0.6 end return glyphs end function mcl_enchanting.generate_random_table_slots(itemstack, num_bookshelves) local base = math.random(8) + math.floor(num_bookshelves / 2) + math.random(0, num_bookshelves) local required_levels = { math.max(base / 3, 1), (base * 2) / 3 + 1, math.max(base, num_bookshelves * 2) } local slots = {} for i, enchantment_level in ipairs(required_levels) do local slot = false local enchantments, description = mcl_enchanting.generate_random_enchantments(itemstack, enchantment_level) if enchantments then slot = { enchantments = enchantments, description = description, glyphs = mcl_enchanting.get_random_glyph_row(), level_requirement = math.max(i, math.floor(enchantment_level)), } end slots[i] = slot end return slots end function mcl_enchanting.get_table_slots(player, itemstack, num_bookshelves) if not mcl_enchanting.can_enchant_freshly(itemstack:get_name()) then return {false, false, false} end local itemname = itemstack:get_name() local meta = player:get_meta() local player_slots = minetest.deserialize(meta:get_string("mcl_enchanting:slots")) or {} local player_bookshelves_slots = player_slots[num_bookshelves] or {} local player_bookshelves_item_slots = player_bookshelves_slots[itemname] if player_bookshelves_item_slots then return player_bookshelves_item_slots else player_bookshelves_item_slots = mcl_enchanting.generate_random_table_slots(itemstack, num_bookshelves) if player_bookshelves_item_slots then player_bookshelves_slots[itemname] = player_bookshelves_item_slots player_slots[num_bookshelves] = player_bookshelves_slots meta:set_string("mcl_enchanting:slots", minetest.serialize(player_slots)) return player_bookshelves_item_slots else return {false, false, false} end end end function mcl_enchanting.reset_table_slots(player) player:get_meta():set_string("mcl_enchanting:slots", "") end function mcl_enchanting.show_enchanting_formspec(player) local C = minetest.get_color_escape_sequence local name = player:get_player_name() local meta = player:get_meta() local inv = player:get_inventory() local num_bookshelves = meta:get_int("mcl_enchanting:num_bookshelves") local table_name = meta:get_string("mcl_enchanting:table_name") local formspec = "" .. "size[9.07,8.6;]" .. "formspec_version[3]" .. "label[0,0;" .. C("#313131") .. table_name .. "]" .. mcl_formspec.get_itemslot_bg(0.2, 2.4, 1, 1) .. "list[detached:" .. name .. "_enchanting;enchanting;0.2,2.4;1,1;1]" .. mcl_formspec.get_itemslot_bg(1.1, 2.4, 1, 1) .. "image[1.1,2.4;1,1;mcl_enchanting_lapis_background.png]" .. "list[detached:" .. name .. "_enchanting;enchanting;1.1,2.4;1,1;2]" .. "label[0,4;" .. C("#313131") .. "Inventory]" .. mcl_formspec.get_itemslot_bg(0, 4.5, 9, 3) .. mcl_formspec.get_itemslot_bg(0, 7.74, 9, 1) .. "list[current_player;main;0,4.5;9,3;9]" .. "listring[detached:" .. name .. "_enchanting;enchanting]" .. "listring[current_player;main]" .. "list[current_player;main;0,7.74;9,1;]" .. "real_coordinates[true]" .. "image[3.15,0.6;7.6,4.1;mcl_enchanting_button_background.png]" local itemstack = inv:get_stack("enchanting_item", 1) local player_levels = mcl_experience.get_player_xp_level(player) local y = 0.65 local any_enchantment = false local table_slots = mcl_enchanting.get_table_slots(player, itemstack, num_bookshelves) for i, slot in ipairs(table_slots) do any_enchantment = any_enchantment or slot local enough_lapis = inv:contains_item("enchanting_lapis", ItemStack({name = "mcl_dye:blue", count = i})) local enough_levels = slot and slot.level_requirement <= player_levels local can_enchant = (slot and enough_lapis and enough_levels) local ending = (can_enchant and "" or "_off") local hover_ending = (can_enchant and "_hovered" or "_off") formspec = formspec .. "container[3.2," .. y .. "]" .. (slot and "tooltip[button_" .. i .. ";" .. C("#818181") .. slot.description .. " " .. C("#FFFFFF") .. " . . . ?\n\n" .. (enough_levels and C(enough_lapis and "#818181" or "#FC5454") .. i .. " Lapis Lazuli\n" .. C("#818181") .. i .. " Enchantment Levels" or C("#FC5454") .. "Level Requirement: " .. slot.level_requirement) .. "]" or "") .. "style[button_" .. i .. ";bgimg=mcl_enchanting_button" .. ending .. ".png;bgimg_hovered=mcl_enchanting_button" .. hover_ending .. ".png;bgimg_pressed=mcl_enchanting_button" .. hover_ending .. ".png]" .. "button[0,0;7.5,1.3;button_" .. i .. ";]" .. (slot and "image[0,0;1.3,1.3;mcl_enchanting_number_" .. i .. ending .. ".png]" or "") .. (slot and "label[7.2,1.1;" .. C(can_enchant and "#80FF20" or "#407F10") .. slot.level_requirement .. "]" or "") .. (slot and slot.glyphs or "") .. "container_end[]" y = y + 1.35 end formspec = formspec .. "image[" .. (any_enchantment and 0.58 or 1.15) .. ",1.2;" .. (any_enchantment and 2 or 0.87) .. ",1.43;mcl_enchanting_book_" .. (any_enchantment and "open" or "closed") .. ".png]" minetest.show_formspec(name, "mcl_enchanting:table", formspec) end function mcl_enchanting.handle_formspec_fields(player, formname, fields) if formname == "mcl_enchanting:table" then local button_pressed for i = 1, 3 do if fields["button_" .. i] then button_pressed = i end end if not button_pressed then return end local name = player:get_player_name() local inv = player:get_inventory() local meta = player:get_meta() local num_bookshelfes = meta:get_int("mcl_enchanting:num_bookshelves") local itemstack = inv:get_stack("enchanting_item", 1) local cost = ItemStack({name = "mcl_dye:blue", count = button_pressed}) if not inv:contains_item("enchanting_lapis", cost) then return end local slots = mcl_enchanting.get_table_slots(player, itemstack, num_bookshelfes) local slot = slots[button_pressed] if not slot then return end local player_level = mcl_experience.get_player_xp_level(player) if player_level < slot.level_requirement then return end mcl_experience.set_player_xp_level(player, player_level - button_pressed) inv:remove_item("enchanting_lapis", cost) mcl_enchanting.set_enchanted_itemstring(itemstack) mcl_enchanting.set_enchantments(itemstack, slot.enchantments) inv:set_stack("enchanting_item", 1, itemstack) minetest.sound_play("mcl_enchanting_enchant", {to_player = name, gain = 5.0}) mcl_enchanting.reset_table_slots(player) mcl_enchanting.reload_inventory(player) mcl_enchanting.show_enchanting_formspec(player) end end function mcl_enchanting.initialize_player(player) local player_inv = player:get_inventory() player_inv:set_size("enchanting_lapis", 1) player_inv:set_size("enchanting_item", 1) local name = player:get_player_name() local detached_inv = minetest.create_detached_inventory(name .. "_enchanting", { allow_put = function(inv, listname, index, stack, player) if player:get_player_name() ~= name then return 0 end if stack:get_name() == "mcl_dye:blue" and index ~= 2 then return math.min(inv:get_stack(listname, 3):get_free_space(), stack:get_count()) elseif index ~= 3 and inv:get_stack(listname, 2):get_count() == 0 then return 1 else return 0 end end, allow_take = function(inv, listname, index, stack, player) if player:get_player_name() ~= name or index == 1 then return 0 end return stack:get_count() end, allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) return 0 end, on_put = function(inv, listname, index, stack, player) local result_list if index == 1 then if stack:get_name() == "mcl_dye:blue" then result_list = "lapis" stack:add_item(inv:get_stack(listname, 3)) inv:set_stack(listname, 1, nil) inv:set_stack(listname, 3, stack) else result_list = "item" inv:set_stack(listname, 1, nil) inv:set_stack(listname, 2, stack) end elseif index == 2 then result_list = "item" elseif index == 3 then result_list = "lapis" stack = inv:get_stack(listname, 3) end player:get_inventory():set_stack("enchanting_" .. result_list, 1, stack) mcl_enchanting.show_enchanting_formspec(player) end, on_take = function(inv, listname, index, stack, player) local result_list if index == 2 then result_list = "item" elseif index == 3 then result_list = "lapis" end player:get_inventory():set_stack("enchanting_" .. result_list, 1, nil) mcl_enchanting.show_enchanting_formspec(player) end }, name) detached_inv:set_size("enchanting", 3) mcl_enchanting.reload_inventory(player) end function mcl_enchanting.reload_inventory(player) local player_inv = player:get_inventory() local detached_inv = minetest.get_inventory({type = "detached", name = player:get_player_name() .. "_enchanting"}) detached_inv:set_stack("enchanting", 2, player_inv:get_stack("enchanting_item", 1)) detached_inv:set_stack("enchanting", 3, player_inv:get_stack("enchanting_lapis", 1)) end function mcl_enchanting.schedule_book_animation(self, anim) self.scheduled_anim = {timer = self.anim_length, anim = anim} end function mcl_enchanting.set_book_animation(self, anim) local anim_index = mcl_enchanting.book_animations[anim] local start, stop = mcl_enchanting.book_animation_steps[anim_index], mcl_enchanting.book_animation_steps[anim_index + 1] self.object:set_animation({x = start, y = stop}, mcl_enchanting.book_animation_speed) self.scheduled_anim = nil self.anim_length = (stop - start) / 40 end function mcl_enchanting.check_animation_schedule(self, dtime) local schedanim = self.scheduled_anim if schedanim then schedanim.timer = schedanim.timer - dtime if schedanim.timer <= 0 then mcl_enchanting.set_book_animation(self, schedanim.anim) end end end function mcl_enchanting.look_at(self, pos2) local pos1 = self.object:get_pos() local vec = vector.subtract(pos1, pos2) local yaw = math.atan(vec.z / vec.x) - math.pi/2 yaw = yaw + (pos1.x >= pos2.x and math.pi or 0) self.object:set_yaw(yaw + math.pi) end function mcl_enchanting.check_book(pos) local obj_pos = vector.add(pos, mcl_enchanting.book_offset) for _, obj in pairs(minetest.get_objects_inside_radius(obj_pos, 0.1)) do local luaentity = obj:get_luaentity() if luaentity and luaentity.name == "mcl_enchanting:book" then if minetest.get_node(pos).name ~= "mcl_enchanting:table" then obj:remove() end return end end minetest.add_entity(obj_pos, "mcl_enchanting:book") end function mcl_enchanting.get_bookshelves(pos) local absolute, relative = {}, {} for i, rp in ipairs(mcl_enchanting.bookshelf_positions) do local airp = vector.add(pos, mcl_enchanting.air_positions[i]) local ap = vector.add(pos, rp) if minetest.get_node(ap).name == "mcl_books:bookshelf" and minetest.get_node(airp).name == "air" then table.insert(absolute, ap) table.insert(relative, rp) end end return absolute, relative end