First commit

This commit is contained in:
haui 2024-06-12 19:36:28 +02:00
parent c8bc17bf29
commit 08655c3d50
5 changed files with 1927 additions and 0 deletions

751
enchantments.lua Normal file
View file

@ -0,0 +1,751 @@
local S = minetest.get_translator(minetest.get_current_modname())
-- Taken from https://minecraft.gamepedia.com/Enchanting
local function increase_damage(damage_group, factor)
return function(itemstack, level)
local tool_capabilities = itemstack:get_tool_capabilities()
tool_capabilities.damage_groups[damage_group] = (tool_capabilities.damage_groups[damage_group] or 0) + level * factor
itemstack:get_meta():set_tool_capabilities(tool_capabilities)
end
end
-- implemented via on_enchant and additions in mobs_mc; Slowness IV part unimplemented
mcl_enchanting.enchantments.bane_of_arthropods = {
name = S("Bane of Arthropods"),
max_level = 5,
primary = {sword = true},
secondary = {axe = true},
disallow = {},
incompatible = {smite = true, sharpness = true},
weight = 5,
description = S("Increases damage and applies Slowness IV to arthropod mobs (spiders, cave spiders, silverfish and endermites)."),
curse = false,
on_enchant = increase_damage("anthropod", 2.5),
requires_tool = false,
treasure = false,
power_range_table = {{5, 25}, {13, 33}, {21, 41}, {29, 49}, {37, 57}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- requires missing MineClone2 feature
--[[mcl_enchanting.enchantments.channeling = {
name = S("Channeling"),
max_level = 1,
primary = {trident = true},
secondary = {},
disallow = {},
incompatible = {riptide = true},
weight = 1,
description = S("Channels a bolt of lightning toward a target. Works only during thunderstorms and if target is unobstructed with opaque blocks."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{25, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- implemented in mcl_death_drop
mcl_enchanting.enchantments.curse_of_vanishing = {
name = S("Curse of Vanishing"),
max_level = 1,
primary = {},
secondary = {armor_head = true, armor_torso = true, armor_legs = true, armor_feet = true, tool = true, weapon = true},
disallow = {},
incompatible = {},
weight = 1,
description = S("Item destroyed on death."),
curse = true,
on_enchant = function() end,
requires_tool = false,
treasure = true,
power_range_table = {{25, 50}},
inv_combat_tab = true,
inv_tool_tab = true,
}
-- implemented in mcl_playerplus
mcl_enchanting.enchantments.depth_strider = {
name = S("Depth Strider"),
max_level = 3,
primary = {},
secondary = {armor_feet = true},
disallow = {non_combat_armor = true},
incompatible = {frost_walker = true},
weight = 2,
description = S("Increases underwater movement speed."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{10, 25}, {20, 35}, {30, 45}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented via on_enchant
mcl_enchanting.enchantments.efficiency = {
name = S("Efficiency"),
max_level = 5,
primary = {pickaxe = true, shovel = true, axe = true, hoe = true},
secondary = {shears = true},
disallow = {},
incompatible = {},
weight = 10,
description = S("Increases mining speed."),
curse = false,
on_enchant = function()
-- Updating digging speed is handled by update_groupcaps which
-- is called from load_enchantments.
end,
requires_tool = false,
treasure = false,
power_range_table = {{1, 61}, {11, 71}, {21, 81}, {31, 91}, {41, 101}},
inv_combat_tab = false,
inv_tool_tab = true,
}
-- implemented in mcl_mobs and via register_on_punchplayer callback
mcl_enchanting.enchantments.fire_aspect = {
name = S("Fire Aspect"),
max_level = 2,
primary = {sword = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Sets target on fire."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{10, 61}, {30, 71}},
inv_combat_tab = true,
inv_tool_tab = false,
}
minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
if hitter and hitter:is_player() then
local wielditem = hitter:get_wielded_item()
if wielditem then
local fire_aspect_level = mcl_enchanting.get_enchantment(wielditem, "fire_aspect")
if fire_aspect_level > 0 then
local player_pos = player:get_pos()
local hitter_pos = hitter:get_pos()
if vector.distance(hitter_pos, player_pos) <= 3 then
mcl_burning.set_on_fire(player, fire_aspect_level * 4)
end
end
end
end
end)
mcl_enchanting.enchantments.flame = {
name = S("Flame"),
max_level = 1,
primary = {bow = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Arrows set target on fire."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{20, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented in mcl_item_entity
mcl_enchanting.enchantments.fortune = {
name = S("Fortune"),
max_level = 3,
primary = {pickaxe = true, shovel = true, axe = true, hoe = true},
secondary = {},
disallow = {},
incompatible = {silk_touch = true},
weight = 2,
description = S("Increases certain block drops."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{15, 61}, {24, 71}, {33, 81}},
inv_combat_tab = false,
inv_tool_tab = true,
}
-- implemented via walkover.register_global
mcl_enchanting.enchantments.frost_walker = {
name = S("Frost Walker"),
max_level = 2,
primary = {},
secondary = {armor_feet = true},
disallow = {non_combat_armor = true},
incompatible = {depth_strider = true},
weight = 2,
description = S("Turns water beneath the player into frosted ice and prevents the damage from magma blocks."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = true,
power_range_table = {{10, 25}, {20, 35}},
inv_combat_tab = true,
inv_tool_tab = false,
}
walkover.register_global(function(pos, _, player)
local boots = player:get_inventory():get_stack("armor", 5)
local frost_walker = mcl_enchanting.get_enchantment(boots, "frost_walker")
if frost_walker <= 0 then
return
end
local radius = frost_walker + 2
local minp = {x = pos.x - radius, y = pos.y, z = pos.z - radius}
local maxp = {x = pos.x + radius, y = pos.y, z = pos.z + radius}
local positions = minetest.find_nodes_in_area_under_air(minp, maxp, "mcl_core:water_source")
for _, p in ipairs(positions) do
if vector.distance(pos, p) <= radius then
minetest.set_node(p, {name = "mcl_core:frosted_ice_0"})
end
end
end)
-- requires missing MineClone2 feature
--[[mcl_enchanting.enchantments.impaling = {
name = S("Impaling"),
max_level = 5,
primary = {trident = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Trident deals additional damage to ocean mobs."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{1, 21}, {9, 29}, {17, 37}, {25, 45}, {33, 53}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- implemented in mcl_bows
mcl_enchanting.enchantments.infinity = {
name = S("Infinity"),
max_level = 1,
primary = {bow = true},
secondary = {},
disallow = {},
incompatible = {mending = true},
weight = 1,
description = S("Shooting consumes no regular arrows."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{20, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented via minetest.calculate_knockback
mcl_enchanting.enchantments.knockback = {
name = S("Knockback"),
max_level = 2,
primary = {sword = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 5,
description = S("Increases knockback."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{5, 61}, {25, 71}},
inv_combat_tab = true,
inv_tool_tab = false,
}
local old_calculate_knockback = minetest.calculate_knockback
function minetest.calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
local knockback = old_calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
if hitter and hitter:is_player() and distance <= 3 then
local wielditem = hitter:get_wielded_item()
--knockback = knockback + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback")
local enchant = mcl_enchanting.get_enchantment(wielditem, "knockback")
knockback = knockback + 3.22 * enchant
-- add vertical lift to knockback
local v = player:get_velocity()
local added_v = 0
local invul = player:get_meta():get_int("mcl_damage:invulnerable")
if v and v.y <= 0.01 and v.y >= -0.01 and invul == 0 then
local regular_v = 6.4
local enchant_v = 7
regular_v = regular_v * math.abs(dir.y - 1)
enchant_v = enchant_v * math.abs(dir.y - 1)
if enchant == 0 then
player:add_velocity({x = 0, y = regular_v, z = 0})
added_v = regular_v
else
player:add_velocity({x = 0, y = enchant_v, z = 0})
added_v = enchant_v
end
-- add minimum knockback
if knockback <= 1.5 then
knockback = knockback + 4.875
elseif knockback <= 6.19 then
knockback = knockback + 0.609375
end
end
-- counteract forward velocity when hit
local self_dir_dot = (v.x * dir.x) + (v.z * dir.z)
if self_dir_dot < 0 then
player:add_velocity({x = v.x * -1, y = 0, z = v.z * -1})
end
-- add player velocity to knockback
local h_name = hitter:get_player_name()
local hv = hitter:get_velocity()
local dir_dot = (hv.x * dir.x) + (hv.z * dir.z)
local hitter_mag = math.sqrt((hv.x * hv.x) + (hv.z * hv.z))
if dir_dot > 0 and mcl_sprint.is_sprinting(h_name) then
knockback = knockback + hitter_mag * 0.6875
elseif dir_dot > 0 then
knockback = knockback + hitter_mag * 0.515625
end
-- reduce floatiness
minetest.after(0.25, function()
player:add_velocity({x = 0, y = (v.y + added_v) * -0.375, z = 0})
end)
-- reduce knockback when moving towards hitter while attacking
local self_dir_dot = (v.x * dir.x) + (v.z * dir.z)
local control = player:get_player_control()
if self_dir_dot < -4.3 and control.up and control.LMB then
knockback = knockback * 0.6
end
-- remove knockback if invulnerable
if invul > 0 then
knockback = 0
end
elseif hitter and hitter:is_player() and distance > 3 then
knockback = 0
elseif luaentity and luaentity._knockback then
local kb = knockback + luaentity._knockback / 4
local punch_dir = dir
punch_dir.y = 0
punch_dir = vector.normalize(punch_dir) * kb
punch_dir.y = 4
player:add_velocity(punch_dir)
knockback = 0
end
return knockback
end
-- implemented in mcl_mobs and mobs_mc
mcl_enchanting.enchantments.looting = {
name = S("Looting"),
max_level = 3,
primary = {sword = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Increases mob loot."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{15, 61}, {24, 71}, {33, 81}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- requires missing MineClone2 feature
--[[mcl_enchanting.enchantments.loyalty = {
name = S("Loyalty"),
max_level = 3,
primary = {trident = true},
secondary = {},
disallow = {},
incompatible = {riptide = true},
weight = 5,
description = S("Trident returns after being thrown. Higher levels reduce return time."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{12, 50}, {19, 50}, {26, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- implemented in mcl_fishing
mcl_enchanting.enchantments.luck_of_the_sea = {
name = S("Luck of the Sea"),
max_level = 3,
primary = {fishing_rod = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Increases rate of good loot (enchanting books, etc.)"),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{15, 61}, {24, 71}, {33, 81}},
inv_combat_tab = false,
inv_tool_tab = true,
}
-- implemented in mcl_fishing
mcl_enchanting.enchantments.lure = {
name = S("Lure"),
max_level = 3,
primary = {fishing_rod = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Decreases time until rod catches something."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{15, 61}, {24, 71}, {33, 81}},
inv_combat_tab = false,
inv_tool_tab = true,
}
-- implemented in mcl_experience
mcl_enchanting.enchantments.mending = {
name = S("Mending"),
max_level = 1,
primary = {},
secondary = {armor_head = true, armor_torso = true, armor_legs = true, armor_feet = true, tool = true, weapon = true},
disallow = {},
incompatible = {infinity = true},
weight = 2,
description = S("Repair the item while gaining XP orbs."),
curse = false,
on_enchant = function() end,
requires_tool = true,
treasure = true,
power_range_table = {{25, 75}},
inv_combat_tab = true,
inv_tool_tab = true,
}
mcl_experience.register_on_add_xp(function(player, xp)
local inv = player:get_inventory()
local candidates = {
{list = "main", index = player:get_wield_index()},
{list = "armor", index = 2},
{list = "armor", index = 3},
{list = "armor", index = 4},
{list = "armor", index = 5},
{list = "offhand", index = 1},
}
local final_candidates = {}
for _, can in ipairs(candidates) do
local stack = inv:get_stack(can.list, can.index)
local wear = stack:get_wear()
if mcl_enchanting.has_enchantment(stack, "mending") and wear > 0 then
can.stack = stack
can.wear = wear
table.insert(final_candidates, can)
end
end
if #final_candidates > 0 then
local can = final_candidates[math.random(#final_candidates)]
local stack, list, index, wear = can.stack, can.list, can.index, can.wear
local uses = mcl_util.calculate_durability(stack)
local multiplier = 2 * 65535 / uses
local repair = xp * multiplier
local new_wear = wear - repair
if new_wear < 0 then
xp = math.floor(-new_wear / multiplier + 0.5)
new_wear = 0
else
xp = 0
end
stack:set_wear(math.floor(new_wear))
tt.reload_itemstack_description(stack) -- update tooltip
inv:set_stack(list, index, stack)
end
return xp
end, 0)
mcl_enchanting.enchantments.multishot = {
name = S("Multishot"),
max_level = 1,
primary = {crossbow = true},
secondary = {},
disallow = {},
incompatible = {piercing = true},
weight = 2,
description = S("Shoot 3 arrows at the cost of one."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{20, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- requires missing MineClone2 feature
mcl_enchanting.enchantments.piercing = {
name = S("Piercing"),
max_level = 4,
primary = {crossbow = true},
secondary = {},
disallow = {},
incompatible = {multishot = true},
weight = 10,
description = S("Arrows passes through multiple objects."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{1, 50}, {11, 50}, {21, 50}, {31, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented in mcl_bows
mcl_enchanting.enchantments.power = {
name = S("Power"),
max_level = 5,
primary = {bow = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 10,
description = S("Increases arrow damage."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{1, 16}, {11, 26}, {21, 36}, {31, 46}, {41, 56}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented via minetest.calculate_knockback (together with the Knockback enchantment) and mcl_bows
mcl_enchanting.enchantments.punch = {
name = S("Punch"),
max_level = 2,
primary = {},
secondary = {bow = true},
disallow = {},
incompatible = {},
weight = 2,
description = S("Increases arrow knockback."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{12, 37}, {32, 57}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- requires missing MineClone2 feature
mcl_enchanting.enchantments.quick_charge = {
name = S("Quick Charge"),
max_level = 3,
primary = {crossbow = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 5,
description = S("Decreases crossbow charging time."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{12, 50}, {32, 50}, {52, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- unimplemented
--[[mcl_enchanting.enchantments.respiration = {
name = S("Respiration"),
max_level = 3,
primary = {armor_head = true},
secondary = {},
disallow = {non_combat_armor = true},
incompatible = {},
weight = 2,
description = S("Extends underwater breathing time."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{10, 40}, {20, 50}, {30, 60}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- requires missing MineClone2 feature
--[[mcl_enchanting.enchantments.riptide = {
name = S("Riptide"),
max_level = 3,
primary = {trident = true},
secondary = {},
disallow = {},
incompatible = {channeling = true, loyalty = true},
weight = 2,
description = S("Trident launches player with itself when thrown. Works only in water or rain."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{17, 50}, {24, 50}, {31, 50}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- implemented via on_enchant
mcl_enchanting.enchantments.sharpness = {
name = S("Sharpness"),
max_level = 5,
primary = {sword = true},
secondary = {axe = true},
disallow = {},
incompatible = {bane_of_arthropods = true, smite = true},
weight = 5,
description = S("Increases damage."),
curse = false,
on_enchant = increase_damage("fleshy", 0.5),
requires_tool = false,
treasure = false,
power_range_table = {{1, 21}, {12, 32}, {23, 43}, {34, 54}, {45, 65}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented in mcl_item_entity
mcl_enchanting.enchantments.silk_touch = {
name = S("Silk Touch"),
max_level = 1,
primary = {pickaxe = true, shovel = true, axe = true, hoe = true},
secondary = {shears = true},
disallow = {},
incompatible = {fortune = true},
weight = 1,
description = S("Mined blocks drop themselves."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{15, 61}},
inv_combat_tab = false,
inv_tool_tab = true,
}
-- implemented via on_enchant and additions in mobs_mc
mcl_enchanting.enchantments.smite = {
name = S("Smite"),
max_level = 5,
primary = {sword = true},
secondary = {axe = true},
disallow = {},
incompatible = {bane_of_arthropods = true, sharpness = true},
weight = 5,
description = S("Increases damage to undead mobs."),
curse = false,
on_enchant = increase_damage("undead", 2.5),
requires_tool = false,
treasure = false,
power_range_table = {{5, 25}, {13, 33}, {21, 41}, {29, 49}, {37, 57}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- implemented in mcl_playerplus
mcl_enchanting.enchantments.soul_speed = {
name = S("Soul Speed"),
max_level = 3,
primary = {},
secondary = {armor_feet = true},
disallow = {non_combat_armor = true},
incompatible = {frost_walker = true},
weight = 2,
description = S("Increases walking speed on soul sand."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = true,
power_range_table = {{10, 25}, {20, 35}, {30, 45}},
inv_combat_tab = true,
inv_tool_tab = false,
}
-- requires missing MineClone2 feature
--[[mcl_enchanting.enchantments.sweeping_edge = {
name = S("Sweeping Edge"),
max_level = 3,
primary = {sword = true},
secondary = {},
disallow = {},
incompatible = {},
weight = 2,
description = S("Increases sweeping attack damage."),
curse = false,
on_enchant = function() end,
requires_tool = false,
treasure = false,
power_range_table = {{5, 20}, {14, 29}, {23, 38}},
inv_combat_tab = true,
inv_tool_tab = false,
}]]--
-- for tools & weapons implemented via on_enchant; for bows implemented in mcl_bows; for armor implemented in mcl_armor and mcl_tt; for fishing rods implemented in mcl_fishing
mcl_enchanting.enchantments.unbreaking = {
name = S("Unbreaking"),
max_level = 3,
primary = {armor_head = true, armor_torso = true, armor_legs = true, armor_feet = true, pickaxe = true, shovel = true, axe = true, hoe = true, sword = true, fishing_rod = true, bow = true},
secondary = {tool = true},
disallow = {non_combat_armor = true},
incompatible = {},
weight = 5,
description = S("Increases item durability."),
curse = false,
on_enchant = function(itemstack, level)
local name = itemstack:get_name()
if not minetest.registered_tools[name].tool_capabilities then
return
end
local tool_capabilities = itemstack:get_tool_capabilities()
tool_capabilities.punch_attack_uses = tool_capabilities.punch_attack_uses * (1 + level)
itemstack:get_meta():set_tool_capabilities(tool_capabilities)
-- Updating digging durability is handled by update_groupcaps
-- which is called from load_enchantments.
end,
requires_tool = true,
treasure = false,
power_range_table = {{5, 61}, {13, 71}, {21, 81}},
inv_combat_tab = true,
inv_tool_tab = true,
}

732
engine.lua Normal file
View file

@ -0,0 +1,732 @@
local S = minetest.get_translator(minetest.get_current_modname())
local F = minetest.formspec_escape
function mcl_enchanting.is_book(itemname)
return itemname == "mcl_books:book" or itemname == "mcl_enchanting:book_enchanted" or
itemname == "mcl_books:book_enchanted"
end
function mcl_enchanting.get_enchantments(itemstack)
if not itemstack then
return {}
end
return minetest.deserialize(itemstack:get_meta():get_string("mcl_enchanting:enchantments")) or {}
end
function mcl_enchanting.unload_enchantments(itemstack)
local itemdef = itemstack:get_definition()
local meta = itemstack:get_meta()
if itemdef.tool_capabilities then
meta:set_tool_capabilities(nil)
meta:set_string("groupcaps_hash", "")
end
if meta:get_string("name") == "" then
meta:set_string("description", "")
meta:set_string("groupcaps_hash", "")
end
end
function mcl_enchanting.load_enchantments(itemstack, enchantments)
if not mcl_enchanting.is_book(itemstack:get_name()) then
mcl_enchanting.unload_enchantments(itemstack)
for enchantment, level in pairs(enchantments or mcl_enchanting.get_enchantments(itemstack)) do
local enchantment_def = mcl_enchanting.enchantments[enchantment]
if enchantment_def.on_enchant then
enchantment_def.on_enchant(itemstack, level)
end
end
mcl_enchanting.update_groupcaps(itemstack)
end
tt.reload_itemstack_description(itemstack)
end
function mcl_enchanting.set_enchantments(itemstack, enchantments)
itemstack:get_meta():set_string("mcl_enchanting:enchantments", minetest.serialize(enchantments))
mcl_enchanting.load_enchantments(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_util.to_roman(level))
end
function mcl_enchanting.get_colorized_enchantment_description(enchantment, level)
return minetest.colorize(mcl_enchanting.enchantments[enchantment].curse and mcl_colors.RED or mcl_colors.GRAY,
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.not_enchantable_on_enchanting_table(itemname)
return mcl_enchanting.get_enchantability(itemname) == -1
end
function mcl_enchanting.is_enchantable(itemname)
return mcl_enchanting.get_enchantability(itemname) > 0 or
mcl_enchanting.not_enchantable_on_enchanting_table(itemname)
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(itemname, 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 not enchanted_itemname or
enchanted_itemname ~= mcl_enchanting.get_enchanted_itemstring(combine_name) and
not mcl_enchanting.is_book(combine_name) then
return false
end
local enchantments = mcl_enchanting.get_enchantments(itemstack)
local any_new_enchantment = false
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 -- The enchantment already exist in the provided item
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
any_new_enchantment = any_new_enchantment or ( enchantment_level ~= enchantments[enchantment] )
elseif mcl_enchanting.item_supports_enchantment(itemname, enchantment) then -- this is a new enchantement to try to add
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
any_new_enchantment = true
end
end
if enchantment_level and enchantment_level > 0 then
enchantments[enchantment] = enchantment_level
end
end
if any_new_enchantment then
itemstack:set_name(enchanted_itemname)
mcl_enchanting.set_enchantments(itemstack, enchantments)
end
return any_new_enchantment
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
-- Returns the after_use callback function to use when registering an enchanted
-- item. The after_use callback is used to update the tool_capabilities of
-- efficiency enchanted tools with outdated digging times.
--
-- It does this by calling apply_efficiency to reapply the efficiency
-- enchantment. That function is written to use hash values to only update the
-- tool if neccessary.
--
-- This is neccessary for digging times of tools to be in sync when MineClone2
-- or mods add new hardness values.
local function get_after_use_callback(itemdef)
if itemdef.after_use then
-- If the tool already has an after_use, make sure to call that
-- one too.
return function(itemstack, user, node, digparams)
itemdef.after_use(itemstack, user, node, digparams)
mcl_enchanting.update_groupcaps(itemstack)
end
end
-- If the tool does not have after_use, add wear to the tool as if no
-- after_use was registered.
return function(itemstack, user, node, digparams)
if not minetest.is_creative_enabled(user:get_player_name()) then
itemstack:add_wear(digparams.wear)
end
--local enchantments = mcl_enchanting.get_enchantments(itemstack)
mcl_enchanting.update_groupcaps(itemstack)
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) and not mcl_enchanting.is_book(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.not_in_craft_guide = 1
new_def.groups.enchanted = 1
if new_def._mcl_armor_texture then
if type(new_def._mcl_armor_texture) == "string" then
new_def._mcl_armor_texture = new_def._mcl_armor_texture .. mcl_enchanting.overlay
end
end
new_def._mcl_enchanting_enchanted_tool = new_name
new_def.after_use = get_after_use_callback(itemdef)
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.random(pr, ...)
local r = pr and pr:next(...) or math.random(...)
if pr and not ({ ... })[1] then
r = r / 32767
end
return r
end
function mcl_enchanting.get_random_enchantment(itemstack, treasure, weighted, exclude, pr)
local possible = {}
for enchantment, enchantment_def in pairs(mcl_enchanting.enchantments) do
local can_enchant, _, _, primary = mcl_enchanting.can_enchant(itemstack, enchantment, 1)
if can_enchant and (primary or treasure) and (not exclude or table.indexof(exclude, enchantment) == -1) then
local weight = weighted and enchantment_def.weight or 1
for i = 1, weight do
table.insert(possible, enchantment)
end
end
end
return #possible > 0 and possible[mcl_enchanting.random(pr, 1, #possible)]
end
function mcl_enchanting.generate_random_enchantments(itemstack, enchantment_level, treasure, no_reduced_bonus_chance,
ignore_already_enchanted, pr)
local itemname = itemstack:get_name()
if (not mcl_enchanting.can_enchant_freshly(itemname) and not ignore_already_enchanted) or
mcl_enchanting.not_enchantable_on_enchanting_table(itemname) then
return
end
itemstack = ItemStack(itemstack)
local enchantability = minetest.get_item_group(itemname, "enchantability")
enchantability = 1 + mcl_enchanting.random(pr, 0, math.floor(enchantability / 4)) +
mcl_enchanting.random(pr, 0, math.floor(enchantability / 4))
enchantment_level = enchantment_level + enchantability
enchantment_level = enchantment_level +
enchantment_level * (mcl_enchanting.random(pr) + mcl_enchanting.random(pr) - 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 selected_enchantment = mcl_enchanting.get_random_enchantment(itemstack, treasure, true, nil, pr)
if not selected_enchantment then
break
end
local enchantment_def = mcl_enchanting.enchantments[selected_enchantment]
local power_range_table = enchantment_def.power_range_table
local enchantment_power
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
until not no_reduced_bonus_chance and mcl_enchanting.random(pr) >= (enchantment_level + 1) / 50
return enchantments, description
end
function mcl_enchanting.generate_random_enchantments_reliable(itemstack, enchantment_level, treasure, no_reduced_bonus_chance, ignore_already_enchanted, pr)
local enchantments
repeat
enchantments = mcl_enchanting.generate_random_enchantments(itemstack, enchantment_level, treasure,
no_reduced_bonus_chance, ignore_already_enchanted, pr)
until enchantments
return enchantments
end
function mcl_enchanting.enchant_randomly(itemstack, enchantment_level, treasure, no_reduced_bonus_chance,
ignore_already_enchanted, pr)
local enchantments = mcl_enchanting.generate_random_enchantments_reliable(itemstack, enchantment_level, treasure, no_reduced_bonus_chance, ignore_already_enchanted, pr)
mcl_enchanting.set_enchanted_itemstring(itemstack)
mcl_enchanting.set_enchantments(itemstack, enchantments)
return itemstack
end
function mcl_enchanting.enchant_uniform_randomly(stack, exclude, pr)
local enchantment = mcl_enchanting.get_random_enchantment(stack, true, false, exclude, pr)
if enchantment then
mcl_enchanting.enchant(stack, enchantment,
mcl_enchanting.random(pr, 1, mcl_enchanting.enchantments[enchantment].max_level))
end
return stack
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)
local itemname = itemstack:get_name()
if (not mcl_enchanting.can_enchant_freshly(itemname)) or mcl_enchanting.not_enchantable_on_enchanting_table(itemname) then
return { false, false, false }
end
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 = table.concat({
"formspec_version[4]",
"size[11.75,10.425]",
"label[0.375,0.375;" .. F(C(mcl_formspec.label_color) .. table_name) .. "]",
mcl_formspec.get_itemslot_bg_v4(1, 3.25, 1, 1),
"list[current_player;enchanting_item;1,3.25;1,1]",
mcl_formspec.get_itemslot_bg_v4(2.25, 3.25, 1, 1),
"image[2.25,3.25;1,1;mcl_enchanting_lapis_background.png]",
"list[current_player;enchanting_lapis;2.25,3.25;1,1]",
"image[4.125,0.56;7.25,4.1;mcl_enchanting_button_background.png]",
"label[0.375,4.7;" .. F(C(mcl_formspec.label_color) .. S("Inventory")) .. "]",
mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3),
"list[current_player;main;0.375,5.1;9,3;9]",
mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1),
"list[current_player;main;0.375,9.05;9,1;]",
"listring[current_player;enchanting_item]",
"listring[current_player;main]",
"listring[current_player;enchanting]",
"listring[current_player;main]",
"listring[current_player;enchanting_lapis]",
"listring[current_player;main]",
})
local itemstack = inv:get_stack("enchanting_item", 1)
local player_levels = mcl_experience.get_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_core:lapis", 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[4.125," .. y .. "]"
..
(
slot and
"tooltip[button_" ..
i ..
";" ..
C("#818181") ..
((slot.description and F(slot.description)) or "") ..
" " ..
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("@1 Enchantment Levels", 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.25,1.3;button_" .. i .. ";]"
.. (slot and "image[0,0;1.3,1.3;mcl_enchanting_number_" .. i .. ending .. ".png]" or "")
.. (slot and "label[6.8,1;" .. C(can_enchant and "#80FF20" or "#407F10") .. slot.level_requirement .. "]" or "")
.. (slot and slot.glyphs or "")
.. "container_end[]"
y = y + 1.3
end
formspec = formspec
..
"image[" ..
(any_enchantment and 1.1 or 1.67) ..
",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_core:lapis", 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_level(player)
if player_level < slot.level_requirement then
return
end
mcl_experience.set_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.show_enchanting_formspec(player)
awards.unlock(player:get_player_name(), "mcl:enchanter")
end
end
function mcl_enchanting.initialize_player(player)
local inv = player:get_inventory()
inv:set_size("enchanting", 1)
inv:set_size("enchanting_item", 1)
inv:set_size("enchanting_lapis", 1)
end
function mcl_enchanting.is_enchanting_inventory_action(action, inventory, inventory_info)
if inventory:get_location().type == "player" then
local enchanting_lists = mcl_enchanting.enchanting_lists
if action == "move" then
local is_from = table.indexof(enchanting_lists, inventory_info.from_list) ~= -1
local is_to = table.indexof(enchanting_lists, inventory_info.to_list) ~= -1
return is_from or is_to, is_to
elseif (action == "put" or action == "take") and table.indexof(enchanting_lists, inventory_info.listname) ~= -1 then
return true
end
else
return false
end
end
function mcl_enchanting.allow_inventory_action(player, action, inventory, inventory_info)
local is_enchanting_action, do_limit = mcl_enchanting.is_enchanting_inventory_action(action, inventory,
inventory_info)
if is_enchanting_action and do_limit then
if action == "move" then
local listname = inventory_info.to_list
local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
if stack:get_name() == "mcl_core:lapis" and listname ~= "enchanting_item" then
local count = stack:get_count()
local old_stack = inventory:get_stack("enchanting_lapis", 1)
if old_stack:get_name() ~= "" then
count = math.min(count, old_stack:get_free_space())
end
return count
elseif inventory:get_stack("enchanting_item", 1):get_count() == 0 and listname ~= "enchanting_lapis" then
return 1
else
return 0
end
else
return 0
end
end
end
function mcl_enchanting.on_inventory_action(player, action, inventory, inventory_info)
if mcl_enchanting.is_enchanting_inventory_action(action, inventory, inventory_info) then
if action == "move" and inventory_info.to_list == "enchanting" then
local stack = inventory:get_stack("enchanting", 1)
local result_list
if stack:get_name() == "mcl_core:lapis" then
result_list = "enchanting_lapis"
stack:add_item(inventory:get_stack("enchanting_lapis", 1))
else
result_list = "enchanting_item"
end
inventory:set_stack(result_list, 1, stack)
inventory:set_stack("enchanting", 1, nil)
end
mcl_enchanting.show_enchanting_formspec(player)
end
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, 0,
mcl_enchanting.book_animation_loop[anim] or false)
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.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

72
groupcaps.lua Normal file
View file

@ -0,0 +1,72 @@
local groupcaps_cache = {}
-- Compute a hash value.
function compute_hash(value)
return string.sub(minetest.sha1(minetest.serialize(value)), 1, 8)
end
-- Get the groupcaps and hash for an enchanted tool. If this function is called
-- repeatedly with the same values it will return data from a cache.
--
-- Parameters:
-- toolname - Name of the tool
-- level - The efficiency level of the tool
--
-- Returns a table with the following two fields:
-- values - The groupcaps table
-- hash - The hash of the groupcaps table
local function get_efficiency_groupcaps(toolname, level)
local toolcache = groupcaps_cache[toolname]
local level = level
if not toolcache then
toolcache = {}
groupcaps_cache[toolname] = toolcache
end
local levelcache = toolcache[level]
if not levelcache then
levelcache = {}
levelcache.values = mcl_autogroup.get_groupcaps(toolname, level)
levelcache.hash = compute_hash(levelcache.values)
toolcache[level] = levelcache
end
return levelcache
end
-- Update groupcaps of an enchanted tool. This function will be called
-- repeatedly to make sure the digging times stored in groupcaps stays in sync
-- when the digging times of nodes can change.
--
-- To make it more efficient it will first check a hash value to determine if
-- the tool needs to be updated.
function mcl_enchanting.update_groupcaps(itemstack)
local name = itemstack:get_name()
if not minetest.registered_tools[name] or not minetest.registered_tools[name].tool_capabilities then
return
end
local efficiency = mcl_enchanting.get_enchantment(itemstack, "efficiency")
local unbreaking = mcl_enchanting.get_enchantment(itemstack, "unbreaking")
if unbreaking == 0 and efficiency == 0 then
return
end
local groupcaps = get_efficiency_groupcaps(name, efficiency)
local hash = itemstack:get_meta():get_string("groupcaps_hash")
if not hash or hash ~= groupcaps.hash then
local tool_capabilities = itemstack:get_tool_capabilities()
tool_capabilities.groupcaps = table.copy(groupcaps.values)
-- Increase the number of uses depending on the unbreaking level
-- of the tool.
for group, capability in pairs(tool_capabilities.groupcaps) do
capability.uses = capability.uses * (1 + unbreaking)
end
itemstack:get_meta():set_tool_capabilities(tool_capabilities)
itemstack:get_meta():set_string("groupcaps_hash", groupcaps.hash)
end
end

367
init.lua Normal file
View file

@ -0,0 +1,367 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
local math = math
local vector = vector
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,
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 _, errorstring = 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 function spawn_book_entity(pos, respawn)
if respawn then
-- Check if we already have a book
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
return
end
end
end
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", "mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png", "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_objects_inside_radius(vector.subtract(self.object:get_pos(), mcl_enchanting.book_offset), 2.5)) do
if obj:is_player() 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)
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 = minetest.get_meta(pos):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_objects_inside_radius(pos, 15)) do
if obj:is_player() 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)
tt.register_priority_snippet(mcl_enchanting.enchantments_snippet)

5
mod.conf Normal file
View file

@ -0,0 +1,5 @@
name = mcl_enchanting
description = Enchanting for MineClone2
depends = tt, walkover, mcl_sounds, mcl_colors, mcl_experience, mcl_util
optional_depends = screwdriver
author = Fleckenstein