VoxeLibre/mods/ITEMS/mcl_enchanting/engine.lua
2020-12-14 14:11:59 +01:00

602 lines
22 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local S = minetest.get_translator("mcl_enchanting")
local F = minetest.formspec_escape
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") .. F(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") .. F(S("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") .. F(slot.description) .. " " .. C("#FFFFFF") .. " . . . ?\n\n" .. (enough_levels and C(enough_lapis and "#818181" or "#FC5454") .. F(S("@1 × Lapis Lazuli", i)) .. "\n" .. C("#818181") .. F(S("Enchantment levels: @1", i)) or C("#FC5454") .. F(S("Level requirement: @1", 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