Mineclonia/mods/ITEMS/mcl_enchanting/init.lua

375 lines
15 KiB
Lua

local modpath = minetest.get_modpath("mcl_enchanting")
local S = minetest.get_translator("mcl_enchanting")
mcl_enchanting = {
book_offset = vector.new(0, 0.75, 0),
book_animations = {["close"] = 1, ["opening"] = 2, ["open"] = 3, ["closing"] = 4},
book_animation_steps = {0, 640, 680, 700, 740},
book_animation_loop = {["open"] = true, ["close"] = true},
book_animation_speed = 40,
roman_numerals = dofile(modpath .. "/roman_numerals.lua"), -- https://exercism.io/tracks/lua/exercises/roman-numerals/solutions/73c2fb7521e347209312d115f872fa49
enchantments = {},
overlay = "^[colorize:purple:50",
--overlay = "^[invert:rgb^[multiply:#4df44d:50^[invert:rgb",
enchanting_lists = {"enchanting", "enchanting_item", "enchanting_lapis"},
bookshelf_positions = {
{x = -2, y = 0, z = -2}, {x = -2, y = 1, z = -2},
{x = -1, y = 0, z = -2}, {x = -1, y = 1, z = -2},
{x = 0, y = 0, z = -2}, {x = 0, y = 1, z = -2},
{x = 1, y = 0, z = -2}, {x = 1, y = 1, z = -2},
{x = 2, y = 0, z = -2}, {x = 2, y = 1, z = -2},
{x = -2, y = 0, z = 2}, {x = -2, y = 1, z = 2},
{x = -1, y = 0, z = 2}, {x = -1, y = 1, z = 2},
{x = 0, y = 0, z = 2}, {x = 0, y = 1, z = 2},
{x = 1, y = 0, z = 2}, {x = 1, y = 1, z = 2},
{x = 2, y = 0, z = 2}, {x = 2, y = 1, z = 2},
-- {x = -2, y = 0, z = -2}, {x = -2, y = 1, z = -2},
{x = -2, y = 0, z = -1}, {x = -2, y = 1, z = -1},
{x = -2, y = 0, z = 0}, {x = -2, y = 1, z = 0},
{x = -2, y = 0, z = 1}, {x = -2, y = 1, z = 1},
-- {x = -2, y = 0, z = 2}, {x = -2, y = 1, z = 2},
-- {x = 2, y = 0, z = -2}, {x = 2, y = 1, z = -2},
{x = 2, y = 0, z = -1}, {x = 2, y = 1, z = -1},
{x = 2, y = 0, z = 0}, {x = 2, y = 1, z = 0},
{x = 2, y = 0, z = 1}, {x = 2, y = 1, z = 1},
-- {x = 2, y = 0, z = 2}, {x = 2, y = 1, z = 2},
},
air_positions = {
{x = -1, y = 0, z = -1}, {x = -1, y = 1, z = -1},
{x = -1, y = 0, z = -1}, {x = -1, y = 1, z = -1},
{x = 0, y = 0, z = -1}, {x = 0, y = 1, z = -1},
{x = 1, y = 0, z = -1}, {x = 1, y = 1, z = -1},
{x = 1, y = 0, z = -1}, {x = 1, y = 1, z = -1},
{x = -1, y = 0, z = 1}, {x = -1, y = 1, z = 1},
{x = -1, y = 0, z = 1}, {x = -1, y = 1, z = 1},
{x = 0, y = 0, z = 1}, {x = 0, y = 1, z = 1},
{x = 1, y = 0, z = 1}, {x = 1, y = 1, z = 1},
{x = 1, y = 0, z = 1}, {x = 1, y = 1, z = 1},
-- {x = -1, y = 0, z = -1}, {x = -1, y = 1, z = -1},
{x = -1, y = 0, z = -1}, {x = -1, y = 1, z = -1},
{x = -1, y = 0, z = 0}, {x = -1, y = 1, z = 0},
{x = -1, y = 0, z = 1}, {x = -1, y = 1, z = 1},
-- {x = -1, y = 0, z = 1}, {x = -1, y = 1, z = 1},
-- {x = 1, y = 0, z = -1}, {x = 1, y = 1, z = -1},
{x = 1, y = 0, z = -1}, {x = 1, y = 1, z = -1},
{x = 1, y = 0, z = 0}, {x = 1, y = 1, z = 0},
{x = 1, y = 0, z = 1}, {x = 1, y = 1, z = 1},
-- {x = 1, y = 0, z = 1}, {x = 1, y = 1, z = 1},
},
}
dofile(modpath .. "/engine.lua")
dofile(modpath .. "/groupcaps.lua")
dofile(modpath .. "/enchantments.lua")
minetest.register_chatcommand("enchant", {
description = S("Enchant an item"),
params = S("<player> <enchantment> [<level>]"),
privs = {give = true},
func = function(_, param)
local sparam = param:split(" ")
local target_name = sparam[1]
local enchantment = sparam[2]
local level_str = sparam[3]
local level = tonumber(level_str or "1")
if not target_name or not enchantment then
return false, S("Usage: /enchant <player> <enchantment> [<level>]")
end
local target = minetest.get_player_by_name(target_name)
if not target then
return false, S("Player '@1' cannot be found.", target_name)
end
local itemstack = target:get_wielded_item()
local can_enchant, errorstring, extra_info = mcl_enchanting.can_enchant(itemstack, enchantment, level)
if not can_enchant then
if errorstring == "enchantment invalid" then
return false, S("There is no such enchantment '@1'.", enchantment)
elseif errorstring == "item missing" then
return false, S("The target doesn't hold an item.")
elseif errorstring == "item not supported" then
return false, S("The selected enchantment can't be added to the target item.")
elseif errorstring == "level invalid" then
return false, S("'@1' is not a valid number", level_str)
elseif errorstring == "level too high" then
return false, S("The number you have entered (@1) is too big, it must be at most @2.", level_str, extra_info)
elseif errorstring == "level too small" then
return false, S("The number you have entered (@1) is too small, it must be at least @2.", level_str, extra_info)
elseif errorstring == "incompatible" then
return false, S("@1 can't be combined with @2.", mcl_enchanting.get_enchantment_description(enchantment, level), extra_info)
end
else
target:set_wielded_item(mcl_enchanting.enchant(itemstack, enchantment, level))
return true, S("Enchanting succeded.")
end
end
})
minetest.register_chatcommand("forceenchant", {
description = S("Forcefully enchant an item"),
params = S("<player> <enchantment> [<level>]"),
privs = {give = true},
func = function(_, param)
local sparam = param:split(" ")
local target_name = sparam[1]
local enchantment = sparam[2]
local level_str = sparam[3]
local level = tonumber(level_str or "1")
if not target_name or not enchantment then
return false, S("Usage: /forceenchant <player> <enchantment> [<level>]")
end
local target = minetest.get_player_by_name(target_name)
if not target then
return false, S("Player '@1' cannot be found.", target_name)
end
local itemstack = target:get_wielded_item()
local can_enchant, errorstring, extra_info = mcl_enchanting.can_enchant(itemstack, enchantment, level)
if errorstring == "enchantment invalid" then
return false, S("There is no such enchantment '@1'.", enchantment)
elseif errorstring == "item missing" then
return false, S("The target doesn't hold an item.")
elseif errorstring == "item not supported" and not mcl_enchanting.is_enchantable(itemstack:get_name()) then
return false, S("The target item is not enchantable.")
elseif errorstring == "level invalid" then
return false, S("'@1' is not a valid number.", level_str)
else
target:set_wielded_item(mcl_enchanting.enchant(itemstack, enchantment, level))
return true, S("Enchanting succeded.")
end
end
})
minetest.register_craftitem("mcl_enchanting:book_enchanted", {
description = S("Enchanted Book"),
inventory_image = "mcl_enchanting_book_enchanted.png" .. mcl_enchanting.overlay,
groups = {enchanted = 1, not_in_creative_inventory = 1, enchantability = 1},
_mcl_enchanting_enchanted_tool = "mcl_enchanting:book_enchanted",
stack_max = 1,
})
minetest.register_alias("mcl_books:book_enchanted", "mcl_enchanting:book_enchanted")
local spawn_book_entity = function(pos, respawn)
local cnt=0
-- Check if we already have books within 5 nodes distance to avoid too many entities
local objs = minetest.get_objects_inside_radius(pos, 5)
for o=1, #objs do
local obj = objs[o]
local lua = obj:get_luaentity()
if lua and lua.name == "mcl_enchanting:book" then
if respawn and lua._table_pos and vector.equals(pos, lua._table_pos) then
return
end
cnt=cnt+1
end
end
if cnt > 10 then return end
local obj = minetest.add_entity(vector.add(pos, mcl_enchanting.book_offset), "mcl_enchanting:book")
if obj then
local lua = obj:get_luaentity()
if lua then
lua._table_pos = table.copy(pos)
end
end
end
minetest.register_entity("mcl_enchanting:book", {
initial_properties = {
visual = "mesh",
mesh = "mcl_enchanting_book.b3d",
visual_size = {x = 12.5, y = 12.5},
collisionbox = {0, 0, 0},
pointable = false,
physical = false,
textures = {"mcl_enchanting_book_entity.png"},
static_save = false,
},
_player_near = false,
_table_pos = nil,
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
mcl_enchanting.set_book_animation(self, "close")
end,
on_step = function(self, dtime)
local old_player_near = self._player_near
local player_near = false
local player
for _,obj in pairs(minetest.get_connected_players()) do
if vector.distance(obj:get_pos(),self.object:get_pos()) < 2.5 then
player_near = true
player = obj
end
end
if player_near and not old_player_near then
mcl_enchanting.set_book_animation(self, "opening")
mcl_enchanting.schedule_book_animation(self, "open")
elseif old_player_near and not player_near then
mcl_enchanting.set_book_animation(self, "closing")
mcl_enchanting.schedule_book_animation(self, "close")
end
if player then
mcl_enchanting.look_at(self, player:get_pos())
end
self._player_near = player_near
mcl_enchanting.check_animation_schedule(self, dtime)
if self._table_pos then
local tn=minetest.get_node_or_nil(self._table_pos)
if tn and tn.name ~= "mcl_enchanting:table" then
self.object:remove()
end
end
end,
})
local rotate
if minetest.get_modpath("screwdriver") then
rotate = screwdriver.rotate_simple
end
minetest.register_node("mcl_enchanting:table", {
description = S("Enchanting Table"),
_tt_help = S("Spend experience, and lapis to enchant various items."),
_doc_items_longdesc = S("Enchanting Tables will let you enchant armors, tools, weapons, and books with various abilities. But, at the cost of some experience, and lapis lazuli."),
_doc_items_usagehelp =
S("Rightclick the Enchanting Table to open the enchanting menu.").."\n"..
S("Place a tool, armor, weapon or book into the top left slot, and then place 1-3 Lapis Lazuli in the slot to the right.").."\n".."\n"..
S("After placing your items in the slots, the enchanting options will be shown. Hover over the options to read what is available to you.").."\n"..
S("These options are randomized, and dependent on experience level; but the enchantment strength can be increased.").."\n".."\n"..
S("To increase the enchantment strength, place bookshelves around the enchanting table. However, you will need to keep 1 air node between the table, & the bookshelves to empower the enchanting table.").."\n".."\n"..
S("After finally selecting your enchantment; left-click on the selection, and you will see both the lapis lazuli and your experience levels consumed. And, an enchanted item left in its place."),
_doc_items_hidden = false,
drawtype = "nodebox",
tiles = {"mcl_enchanting_table_top.png", "mcl_enchanting_table_bottom.png", "mcl_enchanting_table_side.png", "mcl_enchanting_table_side.png", "mcl_enchanting_table_side.png", "mcl_enchanting_table_side.png"},
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
node_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, 0.25, 0.5},
},
sounds = mcl_sounds.node_sound_stone_defaults(),
groups = {pickaxey = 2, deco_block = 1},
on_rotate = rotate,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local player_meta = clicker:get_meta()
local table_meta = minetest.get_meta(pos)
local num_bookshelves = table_meta:get_int("mcl_enchanting:num_bookshelves")
local table_name = table_meta:get_string("name")
if table_name == "" then
table_name = S("Enchant")
end
local bookshelves = mcl_enchanting.get_bookshelves(pos)
player_meta:set_int("mcl_enchanting:num_bookshelves", math.min(15, #bookshelves))
player_meta:set_string("mcl_enchanting:table_name", table_name)
mcl_enchanting.show_enchanting_formspec(clicker)
-- Respawn book entity just in case it got lost
spawn_book_entity(pos, true)
end,
on_construct = function(pos)
spawn_book_entity(pos)
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
local dname = (digger and digger:get_player_name()) or ""
if minetest.is_creative_enabled(dname) then
return
end
local itemstack = ItemStack("mcl_enchanting:table")
local meta = minetest.get_meta(pos)
local itemmeta = itemstack:get_meta()
itemmeta:set_string("name", meta:get_string("name"))
itemmeta:set_string("description", meta:get_string("description"))
minetest.add_item(pos, itemstack)
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local itemmeta = itemstack:get_meta()
meta:set_string("name", itemmeta:get_string("name"))
meta:set_string("description", itemmeta:get_string("description"))
end,
after_destruct = function(pos)
local objs = minetest.get_objects_inside_radius(pos, 1)
for o=1, #objs do
local obj = objs[o]
local lua = obj:get_luaentity()
if lua and lua.name == "mcl_enchanting:book" then
if lua._table_pos and vector.equals(pos, lua._table_pos) then
obj:remove()
end
end
end
end,
drop = "",
_mcl_blast_resistance = 1200,
_mcl_hardness = 5,
})
minetest.register_craft({
output = "mcl_enchanting:table",
recipe = {
{"", "mcl_books:book", ""},
{"mcl_core:diamond", "mcl_core:obsidian", "mcl_core:diamond"},
{"mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian"}
}
})
minetest.register_abm({
label = "Enchanting table bookshelf particles",
interval = 1,
chance = 1,
nodenames = "mcl_enchanting:table",
action = function(pos)
local playernames = {}
for _,obj in pairs(minetest.get_connected_players()) do
if vector.distance(obj:get_pos(),pos) < 15 then
table.insert(playernames, obj:get_player_name())
end
end
if #playernames < 1 then
return
end
local absolute, relative = mcl_enchanting.get_bookshelves(pos)
for i, ap in ipairs(absolute) do
if math.random(5) == 1 then
local rp = relative[i]
local t = math.random()+1 --time
local d = {x = rp.x, y=rp.y-0.7, z=rp.z} --distance
local v = {x = -math.random()*d.x, y = math.random(), z = -math.random()*d.z} --velocity
local a = {x = 2*(-v.x*t - d.x)/t/t, y = 2*(-v.y*t - d.y)/t/t, z = 2*(-v.z*t - d.z)/t/t} --acceleration
local s = math.random()+0.9 --size
t = t - 0.1 --slightly decrease time to avoid texture overlappings
local tx = "mcl_enchanting_glyph_" .. math.random(18) .. ".png"
for _, name in pairs(playernames) do
minetest.add_particle({
pos = ap,
velocity = v,
acceleration = a,
expirationtime = t,
size = s,
texture = tx,
collisiondetection = false,
playername = name
})
end
end
end
end
})
minetest.register_lbm({
label = "(Re-)spawn book entity above enchanting table",
name = "mcl_enchanting:spawn_book_entity",
nodenames = {"mcl_enchanting:table"},
run_at_every_load = true,
action = function(pos)
spawn_book_entity(pos, true)
end,
})
minetest.register_on_mods_loaded(mcl_enchanting.initialize)
minetest.register_on_joinplayer(mcl_enchanting.initialize_player)
minetest.register_on_player_receive_fields(mcl_enchanting.handle_formspec_fields)
minetest.register_allow_player_inventory_action(mcl_enchanting.allow_inventory_action)
minetest.register_on_player_inventory_action(mcl_enchanting.on_inventory_action)
table.insert(tt.registered_snippets, 1, mcl_enchanting.enchantments_snippet)