Compare commits

..

9 Commits

13 changed files with 574 additions and 152 deletions

104
README.md
View File

@ -8,6 +8,7 @@ Adds Enchanting Mechanics and API.
* adds enchanting table * adds enchanting table
* supports all registered tools with known tool groups: pickaxe, shovel, axe, sword, e.g. `groups = {pickaxe = 1}` * supports all registered tools with known tool groups: pickaxe, shovel, axe, sword, e.g. `groups = {pickaxe = 1}`
* support all bows with group `{bow = 1}`
* supports `default:bookshelf` or anything with `group:bookshelf` * supports `default:bookshelf` or anything with `group:bookshelf`
* enchanting trade is for `default:mese_crystal` or anything with `groups = {enchanting_trade = 1}` * enchanting trade is for `default:mese_crystal` or anything with `groups = {enchanting_trade = 1}`
* adds enchantability for all MT default tools, for custom tools the enchantability can be set in the item group, e.g. `groups = {enchantability = 15}` * adds enchantability for all MT default tools, for custom tools the enchantability can be set in the item group, e.g. `groups = {enchantability = 15}`
@ -50,22 +51,123 @@ Items enchantibility from worst to best:
Increases melee damage. Increases melee damage.
groups: sword
#### Fortune #### Fortune
Increases the number and/or chances of specific item drops. This value is not used in the engine; it is the responsibility of the game/mod code to implement this. Increases the number and/or chances of specific item drops. Works with groups: stone, soil, sand, snowy, slippery, tree, leaves and all registered ores.
Incompatible: Silk Touch
groups: pickaxe, shovel, axe
#### Unbreaking #### Unbreaking
Increases the item's durability. Increases the item's durability.
groups: any
#### Efficiency #### Efficiency
Increases the player's mining speed. Also adds mining groupcaps to item, e.g. enchanted wood pickaxe can mine level 1 nodes (e.g. obsidian) after enchantment. Increases the player's mining speed. Also adds mining groupcaps to item, e.g. enchanted wood pickaxe can mine level 1 nodes (e.g. obsidian) after enchantment.
groups: pickaxe, shovel, axe
#### Silk Touch #### Silk Touch
Causes certain blocks to drop themselves as items instead of their usual drops when mined. Mods can prevent this behaviour with adding group `{ no_silktouch = 1 }` to the nodes. Causes certain blocks to drop themselves as items instead of their usual drops when mined. Mods can prevent this behaviour with adding group `{ no_silktouch = 1 }` to the nodes.
Incompatible: Fortune
groups: pickaxe, shovel, axe
#### Curse of Vanishing
Causes the item to disappear on death.
groups: any
#### Knockback
Increases knockback (players only).
groups: sword
#### Looting
Cause mobs to drop more items. This value is not used in the engine; it is the responsibility of the game/mod code to implement this.
groups: sword
#### Power
Increases arrow damage.
Damage has to be calculated in the MOD where the bow comes from!
groups: bow
#### Punch
Increases arrow knockback.
Knockback has to be calculated in the MOD where the bow comes from!
This can be obtained from tool meta:
groups: bow
#### Infinity
Prevents regular arrows from being consumed when shot.
One arrow is needed INSIDE QUIVER to use a bow enchanted with Infinity.
Fired arrows cannot be retrieved even if they are not fired from Quiver.
Only set in item meta, logic for this has to be in the MOD where the bow comes from!
groups: bow
## API
`ItemStackMetaRef`
* `get_float(key)`: Returns `0` if key not present. `key` can be enchantment id prefixed with `is_`,
e.g. enchantment `punch` would have stored meta as `is_punch`. If returned value is bigger than zero
then the value represents enchantment level bonus. See below fields for bow:
* `power` Increase percentage
* `punch` Multiplier
* `infinity` If `1` then it is infinity enchanted
* `get_string(key)`: Returns `""` if key not present. See below fields for all enchantments:
* `x_enchanting` Serialized table with key/value pairs where: `key` is enchantment `id` and `value` is `Enchantment` definition
`Enchantment` definition
* `value` number, Value of the enchantment based on level, e.g. multiplier, percentage/number increase...
example:
```lua
-- For simplicity assuming that all meta are present (biggger than zero or not "")
-- MODs have to add those checks individually
local itemstack_meta = itemstack:get_meta()
local power_value = itemstack_meta:get_float('is_power')
local punch_value = itemstack_meta:get_float('is_punch')
local infinity_value = itemstack_meta:get_float('is_infinity')
-- Or for list of all enchantments
local x_enchanting = minetest.deserialize(itemstack_meta:get_string('x_enchanting')) or {}
local power_enchantment = enchantments.power
local punch_enchantment = enchantments.punch
local infinity_enchantment = enchantments.infinity
-- Custom logic
local new_damage = damage + damage * (punch_enchantment.value / 100)
local new_knockback = knockback * punch_enchantment.value
if enchantments.infinity.value > 0 then
-- Some logic for infinity
end
```
## Dependencies ## Dependencies
- none - none

397
api.lua
View File

@ -62,7 +62,29 @@ XEnchanting = {
[4] = 5, [4] = 5,
[5] = 6.25, [5] = 6.25,
}, },
weight = 10 weight = 10,
groups = {
'sword'
}
},
looting = {
name = S('Looting'),
-- what level should be taken, `level = min/max values`
final_level_range = {
[1] = { 15, 65 },
[2] = { 24, 74 },
[3] = { 33, 83 }
},
-- level definition, `level = number to add`
level_def = {
[1] = 1,
[2] = 2,
[3] = 3
},
weight = 2,
groups = {
'sword'
}
}, },
fortune = { fortune = {
name = S('Fortune'), name = S('Fortune'),
@ -78,7 +100,13 @@ XEnchanting = {
[2] = 2, [2] = 2,
[3] = 3 [3] = 3
}, },
weight = 2 weight = 2,
groups = {
'pickaxe',
'shovel',
'axe'
},
incompatible = { 'silk_touch' }
}, },
unbreaking = { unbreaking = {
name = S('Unbreaking'), name = S('Unbreaking'),
@ -94,7 +122,9 @@ XEnchanting = {
[2] = 200, [2] = 200,
[3] = 300 [3] = 300
}, },
weight = 5 weight = 5,
-- all applicable
groups = nil
}, },
efficiency = { efficiency = {
name = S('Efficiency'), name = S('Efficiency'),
@ -114,7 +144,12 @@ XEnchanting = {
[4] = 40, [4] = 40,
[5] = 45, [5] = 45,
}, },
weight = 10 weight = 10,
groups = {
'pickaxe',
'shovel',
'axe'
}
}, },
silk_touch = { silk_touch = {
name = S('Silk Touch'), name = S('Silk Touch'),
@ -122,19 +157,116 @@ XEnchanting = {
[1] = { 15, 65 } [1] = { 15, 65 }
}, },
level_def = { level_def = {
[1] = 'silk_touch' [1] = 1
}, },
weight = 1 weight = 1,
secondary = true,
groups = {
'pickaxe',
'shovel',
'axe'
},
incompatible = { 'fortune' }
},
curse_of_vanishing = {
name = S('Curse of Vanishing'),
final_level_range = {
[1] = { 25, 50 }
},
level_def = {
[1] = 1
},
weight = 1,
secondary = true,
-- all applicable
groups = nil
},
knockback = {
name = S('Knockback'),
final_level_range = {
[1] = { 5, 55 },
[2] = { 25, 75 }
},
-- increase %
level_def = {
[1] = 105,
[2] = 190
},
weight = 5,
groups = {
'sword'
} }
}, },
randomseed = os.time(), power = {
-- Increases arrow damage.
-- Damage has to be calculated in the MOD where the bow comes from!
name = S('Power'),
final_level_range = {
[1] = { 1, 16 },
[2] = { 11, 26 },
[3] = { 21, 36 },
[4] = { 31, 46 },
[5] = { 41, 56 }
},
-- increase %
level_def = {
[1] = 50,
[2] = 75,
[3] = 100,
[4] = 125,
[5] = 150
},
weight = 10,
groups = {
'bow'
}
},
punch = {
-- Increases arrow knockback.
-- Knockback has to be calculated in the MOD where the bow comes from!
name = S('Punch'),
final_level_range = {
[1] = { 12, 37 },
[2] = { 32, 57 }
},
-- multiplier
level_def = {
[1] = 3,
[2] = 6
},
weight = 2,
groups = {
'bow'
}
},
infinity = {
-- Prevents regular arrows from being consumed when shot.
-- One arrow is needed to use a bow enchanted with Infinity.
-- Only set in item meta, logic for this has to be in the MOD where the bow comes from!
name = S('Infinity'),
final_level_range = {
[1] = { 20, 50 }
},
-- will be set in meta as float
level_def = {
[1] = 1
},
weight = 1,
secondary = true,
groups = {
'bow'
}
},
},
form_context = {}, form_context = {},
player_seeds = {},
scroll_animations = { scroll_animations = {
scroll_open = { { x = 1, y = 40 }, 80, 0, false }, scroll_open = { { x = 1, y = 40 }, 80, 0, false },
scroll_close = { { x = 45, y = 84 }, 80, 0, false }, scroll_close = { { x = 45, y = 84 }, 80, 0, false },
scroll_open_idle = { { x = 41, y = 42 }, 0, 0, false }, scroll_open_idle = { { x = 41, y = 42 }, 0, 0, false },
scroll_closed_idle = { { x = 43, y = 44 }, 0, 0, false } scroll_closed_idle = { { x = 43, y = 44 }, 0, 0, false }
} },
registered_ores = {}
} }
---Merge two tables with key/value pair ---Merge two tables with key/value pair
@ -146,6 +278,18 @@ local function mergeTables(t1, t2)
return t1 return t1
end end
---Gets length of hashed table
---@param table any
---@return integer
local function get_table_length(table)
local length = 0
for _ in pairs(table) do
length = length + 1
end
return length
end
function XEnchanting.has_tool_group(self, name) function XEnchanting.has_tool_group(self, name)
if minetest.get_item_group(name, 'pickaxe') > 0 then if minetest.get_item_group(name, 'pickaxe') > 0 then
return 'pickaxe' return 'pickaxe'
@ -155,17 +299,22 @@ function XEnchanting.has_tool_group(self, name)
return 'axe' return 'axe'
elseif minetest.get_item_group(name, 'sword') > 0 then elseif minetest.get_item_group(name, 'sword') > 0 then
return 'sword' return 'sword'
elseif minetest.get_item_group(name, 'bow') > 0 then
return 'bow'
end end
return false return false
end end
function XEnchanting.set_tool_enchantability(self, tool_def) function XEnchanting.set_tool_enchantability(self, tool_def)
if minetest.get_item_group(tool_def.name, 'enchantability') > 0 then
-- enchantability is already set, we dont need to override the item
return
end
local _enchantability = 1 local _enchantability = 1
if minetest.get_item_group(tool_def.name, 'enchantability') > 0 then if self.tools_enchantability[tool_def.name] then
_enchantability = minetest.get_item_group(tool_def.name, 'enchantability')
elseif self.tools_enchantability[tool_def.name] then
_enchantability = self.tools_enchantability[tool_def.name] _enchantability = self.tools_enchantability[tool_def.name]
end end
@ -177,8 +326,6 @@ end
function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantments) function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantments)
local tool_stack = ItemStack({ name = tool_def.name }) local tool_stack = ItemStack({ name = tool_def.name })
local tool_capabilities = tool_stack:get_tool_capabilities() local tool_capabilities = tool_stack:get_tool_capabilities()
local enchantments_desc = {}
local enchantments_desc_masked = {}
for i, enchantment in ipairs(enchantments) do for i, enchantment in ipairs(enchantments) do
-- Efficiency -- Efficiency
@ -246,18 +393,6 @@ function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantment
tool_capabilities.full_punch_interval = new_fpi tool_capabilities.full_punch_interval = new_fpi
end end
if tool_capabilities.groupcaps or tool_capabilities.full_punch_interval then
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
if #enchantments_desc_masked == 0 then
enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
end
end
end end
-- Unbreaking -- Unbreaking
@ -282,18 +417,6 @@ function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantment
tool_capabilities.punch_attack_uses = new_uses tool_capabilities.punch_attack_uses = new_uses
end end
if tool_capabilities.groupcaps or tool_capabilities.punch_attack_uses then
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
if #enchantments_desc_masked == 0 then
enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
end
end
end end
-- Sharpness -- Sharpness
@ -304,42 +427,48 @@ function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantment
tool_capabilities.damage_groups[group_name] = new_damage tool_capabilities.damage_groups[group_name] = new_damage
end end
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
if #enchantments_desc_masked == 0 then
enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
end
end end
-- Fortune -- Fortune
if enchantment.id == 'fortune' and tool_capabilities.max_drop_level then if enchantment.id == 'fortune' or enchantment.id == 'looting' and tool_capabilities.max_drop_level then
local old_max_drop_level = tool_capabilities.max_drop_level local old_max_drop_level = tool_capabilities.max_drop_level
local new_max_drop_level = old_max_drop_level + enchantment.value local new_max_drop_level = old_max_drop_level + enchantment.value
tool_capabilities.max_drop_level = new_max_drop_level tool_capabilities.max_drop_level = new_max_drop_level
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
if #enchantments_desc_masked == 0 then
enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
end end
end end
-- Silk Touch return tool_capabilities
if enchantment.id == 'silk_touch' then end
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
if #enchantments_desc_masked == 0 then function XEnchanting.get_randomseed(self)
return tonumber(tostring(os.time()):reverse():sub(1, 9)) --[[@as integer]]
end
function XEnchanting.get_enchanted_descriptions(self, enchantments)
local enchantments_desc = {}
local enchantments_desc_masked = {}
for i, enchantment in ipairs(enchantments) do
local add_roman_numbers = true
if get_table_length(self.enchantment_defs[enchantment.id].final_level_range) == 1 then
add_roman_numbers = false
end
if add_roman_numbers then
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
.. ' '
.. self.roman_numbers[enchantment.level]
else
enchantments_desc[#enchantments_desc + 1] = self.enchantment_defs[enchantment.id].name
end
if #enchantments_desc_masked == 0 and not enchantment.secondary then
enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name enchantments_desc_masked[#enchantments_desc_masked + 1] = self.enchantment_defs[enchantment.id].name
if add_roman_numbers then
enchantments_desc_masked[#enchantments_desc_masked + 1] = ' ' .. self.roman_numbers[enchantment.level]
end end
end end
end end
@ -349,7 +478,6 @@ function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantment
enchantments_desc_masked = table.concat(enchantments_desc_masked, '') .. '...?' enchantments_desc_masked = table.concat(enchantments_desc_masked, '') .. '...?'
return { return {
tool_capabilities = tool_capabilities,
enchantments_desc = enchantments_desc, enchantments_desc = enchantments_desc,
enchantments_desc_masked = enchantments_desc_masked enchantments_desc_masked = enchantments_desc_masked
} }
@ -357,8 +485,13 @@ end
function XEnchanting.set_enchanted_tool(self, pos, itemstack, level, player_name) function XEnchanting.set_enchanted_tool(self, pos, itemstack, level, player_name)
local data = self.form_context[player_name].data local data = self.form_context[player_name].data
local capabilities = data.slots[level].tool_cap_data.tool_capabilities
local description = data.slots[level].tool_cap_data.enchantments_desc if not data then
return
end
local capabilities = data.slots[level].tool_cap_data
local description = data.slots[level].descriptions.enchantments_desc
local final_enchantments = data.slots[level].final_enchantments local final_enchantments = data.slots[level].final_enchantments
local inv = minetest.get_meta(pos):get_inventory() local inv = minetest.get_meta(pos):get_inventory()
local tool_def = minetest.registered_tools[itemstack:get_name()] local tool_def = minetest.registered_tools[itemstack:get_name()]
@ -369,24 +502,22 @@ function XEnchanting.set_enchanted_tool(self, pos, itemstack, level, player_name
end end
local stack_meta = itemstack:get_meta() local stack_meta = itemstack:get_meta()
local is_silk_touch = 0 ---@type table<string, {["value"]: number}>
local final_enchantments_meta = {}
for i, val in ipairs(final_enchantments) do for i, enchantment in ipairs(final_enchantments) do
if val.id == 'silk_touch' then stack_meta:set_float('is_' .. enchantment.id, enchantment.value)
is_silk_touch = 1 -- store only necessary data, keeping the meta optimized
break final_enchantments_meta[enchantment.id] = {
end value = enchantment.value
}
end end
stack_meta:set_tool_capabilities(capabilities) stack_meta:set_tool_capabilities(capabilities)
stack_meta:set_string('description', itemstack:get_description() .. '\n' .. description) stack_meta:set_string('description', itemstack:get_description() .. '\n' .. description)
stack_meta:set_string('short_description', S('Enchanted') .. ' ' .. itemstack:get_short_description()) stack_meta:set_string('short_description', S('Enchanted') .. ' ' .. itemstack:get_short_description())
stack_meta:set_int('is_enchanted', 1) stack_meta:set_int('is_enchanted', 1)
stack_meta:set_string('x_enchanting', minetest.serialize({ enchantments = final_enchantments })) stack_meta:set_string('x_enchanting', minetest.serialize(final_enchantments_meta))
if is_silk_touch > 0 then
stack_meta:set_int('is_silk_touch', 1)
end
inv:set_stack('item', 1, itemstack) inv:set_stack('item', 1, itemstack)
@ -395,7 +526,7 @@ function XEnchanting.set_enchanted_tool(self, pos, itemstack, level, player_name
inv:set_stack('trade', 1, trade_stack) inv:set_stack('trade', 1, trade_stack)
-- set new seed -- set new seed
self.randomseed = tonumber(tostring(os.time()):reverse():sub(1, 9)) --[[@as number]] self.player_seeds[player_name] = self:get_randomseed()
local formspec = self:get_formspec(pos, player_name) local formspec = self:get_formspec(pos, player_name)
node_meta:set_string('formspec', formspec) node_meta:set_string('formspec', formspec)
@ -407,7 +538,10 @@ function XEnchanting.set_enchanted_tool(self, pos, itemstack, level, player_name
}, true) }, true)
end end
function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def) function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_def)
local p_name = player:get_player_name()
local randomseed = self.player_seeds[p_name] or self:get_randomseed()
math.randomseed(randomseed)
local _nr_of_bookshelfs = nr_of_bookshelfs local _nr_of_bookshelfs = nr_of_bookshelfs
local data = { local data = {
slots = {} slots = {}
@ -417,12 +551,30 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
_nr_of_bookshelfs = 15 _nr_of_bookshelfs = 15
end end
----
-- Filter out enchantments compatible for this item group
----
local group_enchantments = {}
for enchantment_name, enchantment_def in pairs(self.enchantment_defs) do
if not enchantment_def.groups then
group_enchantments[enchantment_name] = enchantment_def
else
for i, group in ipairs(enchantment_def.groups) do
if minetest.get_item_group(tool_def.name, group) > 0 then
group_enchantments[enchantment_name] = enchantment_def
break
end
end
end
end
---- ----
-- 0 Show slots in formspec -- 0 Show slots in formspec
---- ----
-- Base enchantment -- Base enchantment
math.randomseed(self.randomseed)
local base = math.random(1, 8) + math.floor(_nr_of_bookshelfs / 2) + math.random(0, _nr_of_bookshelfs) local base = math.random(1, 8) + math.floor(_nr_of_bookshelfs / 2) + math.random(0, _nr_of_bookshelfs)
local top_slot_base_level = math.floor(math.max(base / 3, 1)) local top_slot_base_level = math.floor(math.max(base / 3, 1))
local middle_slot_base_level = math.floor((base * 2) / 3 + 1) local middle_slot_base_level = math.floor((base * 2) / 3 + 1)
@ -437,12 +589,10 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
-- Applying modifiers to the enchantment level -- Applying modifiers to the enchantment level
local enchantability = minetest.get_item_group(tool_def.name, 'enchantability') local enchantability = minetest.get_item_group(tool_def.name, 'enchantability')
-- Generate a random number between 1 and 1+(enchantability/2), with a triangular distribution -- Generate a random number between 1 and 1+(enchantability/2), with a triangular distribution
math.randomseed(self.randomseed)
local rand_enchantability = 1 + math.random(enchantability / 4 + 1) + math.random(enchantability / 4 + 1) local rand_enchantability = 1 + math.random(enchantability / 4 + 1) + math.random(enchantability / 4 + 1)
-- Choose the enchantment level -- Choose the enchantment level
local k = chosen_enchantment_level + rand_enchantability local k = chosen_enchantment_level + rand_enchantability
-- A random bonus, between .85 and 1.15 -- A random bonus, between .85 and 1.15
math.randomseed(self.randomseed)
local rand_bonus_percent = 1 + ((math.random(0, 99) / 100) + (math.random(0, 99) / 100) - 1) * 0.15 local rand_bonus_percent = 1 + ((math.random(0, 99) / 100) + (math.random(0, 99) / 100) - 1) * 0.15
-- Finally, we calculate the level -- Finally, we calculate the level
local final_level = math.round(k * rand_bonus_percent) local final_level = math.round(k * rand_bonus_percent)
@ -455,14 +605,16 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
-- 2 Find possible enchantments -- 2 Find possible enchantments
---- ----
---@type Enchantment[]
local possible_enchantments = {} local possible_enchantments = {}
-- Get level -- Get level
-- If the modified level is within two overlapping ranges for the same -- If the modified level is within two overlapping ranges for the same
-- enchantment type, the higher power value is used. -- enchantment type, the higher power value is used.
for enchantment_name, enchantment_def in pairs(self.enchantment_defs) do for enchantment_name, enchantment_def in pairs(group_enchantments) do
local levels = {} local levels = {}
-- find matching levels
for level, final_level_range in ipairs(enchantment_def.final_level_range) do for level, final_level_range in ipairs(enchantment_def.final_level_range) do
local min = final_level_range[1] local min = final_level_range[1]
local max = final_level_range[2] local max = final_level_range[2]
@ -472,13 +624,16 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
end end
end end
-- pick the highest level
local level = levels[#levels] local level = levels[#levels]
if level then if level then
table.insert(possible_enchantments, { table.insert(possible_enchantments, {
id = enchantment_name, id = enchantment_name,
value = enchantment_def.level_def[level], value = enchantment_def.level_def[level],
level = level level = level,
secondary = enchantment_def.secondary,
incompatible = enchantment_def.incompatible
}) })
end end
end end
@ -487,6 +642,7 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
-- 3 Select a set of enchantments from the list -- 3 Select a set of enchantments from the list
---- ----
---@type Enchantment[]
local final_enchantments = {} local final_enchantments = {}
local total_weight = 0 local total_weight = 0
@ -495,19 +651,78 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
total_weight = total_weight + self.enchantment_defs[enchantment.id].weight total_weight = total_weight + self.enchantment_defs[enchantment.id].weight
end end
math.randomseed(self.randomseed) -- Pick a random integer in the half range [0; total_weight / 2] as a number `rand_weight`
local rand_weight = math.random(0, total_weight / 2) local rand_weight = math.random(0, total_weight / 2)
-- local probability = (final_level + 1) / 50
local probability_level = final_level
---@type Enchantment[]
local possible_enchantments_excl_secodnary = {}
-- select final enchantments for _, enchantment in pairs(possible_enchantments) do
if not enchantment.secondary then
table.insert(possible_enchantments_excl_secodnary, enchantment)
end
end
-- Select final enchantments
-- Iterate through each enchantment in the list, subtracting its weight from `rand_weight`.
-- If `rand_weight` is now negative, select the current enchantment.
for j = 1, #possible_enchantments, 1 do for j = 1, #possible_enchantments, 1 do
math.randomseed(self.randomseed)
local rand_ench_idx = math.random(1, #possible_enchantments) local rand_ench_idx = math.random(1, #possible_enchantments)
local rand_ench = possible_enchantments[rand_ench_idx] local rand_ench = possible_enchantments[rand_ench_idx]
table.remove(possible_enchantments, rand_ench_idx) if j == 1 then
rand_weight = rand_weight - self.enchantment_defs[rand_ench.id].weight -- First pick
-- Dont add cursed/secondary enchantment as first pick
rand_ench_idx = math.random(1, #possible_enchantments_excl_secodnary)
rand_ench = possible_enchantments_excl_secodnary[rand_ench_idx]
table.insert(final_enchantments, rand_ench) table.insert(final_enchantments, rand_ench)
for idx, value in pairs(possible_enchantments) do
if rand_ench.id == value.id then
table.remove(possible_enchantments, idx)
end
-- remove incomaptible enchantments
if rand_ench.incompatible
and table.indexof(rand_ench.incompatible, value.id) ~= -1
then
table.remove(possible_enchantments, idx)
end
end
else
local probability = (probability_level + 1) / 50
table.insert(final_enchantments, rand_ench)
table.remove(possible_enchantments, rand_ench_idx)
for idx, value in pairs(possible_enchantments) do
-- remove incomaptible enchantments
if rand_ench.incompatible
and table.indexof(rand_ench.incompatible, value.id) ~= -1
then
table.remove(possible_enchantments, idx)
end
end
-- With probability (`final_level` + 1) / 50, keep going. Otherwise, stop picking bonus enchantments.
local rand_probability = math.random()
if rand_probability < probability then
-- Divide the `final_level` in half, rounded down
-- (this does not affect the possible enchantments themselves,
-- because they were all pre-calculated in Step Two).
break
end
-- Repeat from the beginning.
probability_level = probability_level / 2
end
rand_weight = rand_weight - self.enchantment_defs[rand_ench.id].weight
-- If `rand_weight` is now negative, select the current enchantment and stop. -- If `rand_weight` is now negative, select the current enchantment and stop.
if rand_weight < 0 then if rand_weight < 0 then
break break
@ -515,11 +730,13 @@ function XEnchanting.get_enchantment_data(self, nr_of_bookshelfs, tool_def)
end end
local tool_cap_data = self:get_enchanted_tool_capabilities(tool_def, final_enchantments) local tool_cap_data = self:get_enchanted_tool_capabilities(tool_def, final_enchantments)
local descriptions = self:get_enchanted_descriptions(final_enchantments)
table.insert(data.slots, i, { table.insert(data.slots, i, {
level = slot_lvl, level = slot_lvl,
final_enchantments = final_enchantments, final_enchantments = final_enchantments,
tool_cap_data = tool_cap_data tool_cap_data = tool_cap_data,
descriptions = descriptions
}) })
end end
@ -561,10 +778,10 @@ function XEnchanting.get_formspec(self, pos, player_name, data)
if inv:get_stack('trade', 1):get_count() >= i then if inv:get_stack('trade', 1):get_count() >= i then
---@diagnostic disable-next-line: codestyle-check ---@diagnostic disable-next-line: codestyle-check
formspec[#formspec + 1] = 'image_button[2.5,' .. -0.5 + i .. ';5,1;x_enchanting_image_button.png;slot_' .. i .. ';' .. slot.tool_cap_data.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']' formspec[#formspec + 1] = 'image_button[2.5,' .. -0.5 + i .. ';5,1;x_enchanting_image_button.png;slot_' .. i .. ';' .. slot.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']'
else else
---@diagnostic disable-next-line: codestyle-check ---@diagnostic disable-next-line: codestyle-check
formspec[#formspec + 1] = 'image_button[2.5,' .. -0.5 + i .. ';5,1;x_enchanting_image_button_disabled.png;slot_' .. i .. ';' .. slot.tool_cap_data.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']' formspec[#formspec + 1] = 'image_button[2.5,' .. -0.5 + i .. ';5,1;x_enchanting_image_button_disabled.png;slot_' .. i .. ';' .. slot.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']'
end end
formspec[#formspec + 1] = 'image[2.5,' .. -0.5 + i .. ';1,1;x_enchanting_image_trade_' .. i .. '.png;]' formspec[#formspec + 1] = 'image[2.5,' .. -0.5 + i .. ';1,1;x_enchanting_image_trade_' .. i .. '.png;]'

128
init.lua
View File

@ -13,17 +13,26 @@ minetest.register_on_mods_loaded(function()
XEnchanting:set_tool_enchantability(tool_def) XEnchanting:set_tool_enchantability(tool_def)
end end
end end
for _, def in pairs(minetest.registered_ores) do
if not XEnchanting.registered_ores[def.ore] then
XEnchanting.registered_ores[def.ore] = true
end
end
end) end)
minetest.register_on_joinplayer(function(player, last_login) minetest.register_on_joinplayer(function(player, last_login)
XEnchanting.form_context[player:get_player_name()] = nil XEnchanting.form_context[player:get_player_name()] = nil
if not XEnchanting.player_seeds[player:get_player_name()] then
XEnchanting.player_seeds[player:get_player_name()] = XEnchanting.get_randomseed()
end
end) end)
minetest.register_on_leaveplayer(function(player, timed_out) minetest.register_on_leaveplayer(function(player, timed_out)
XEnchanting.form_context[player:get_player_name()] = nil XEnchanting.form_context[player:get_player_name()] = nil
end) end)
-- Silk Touch
local old_handle_node_drops = minetest.handle_node_drops local old_handle_node_drops = minetest.handle_node_drops
function minetest.handle_node_drops(pos, drops, digger) function minetest.handle_node_drops(pos, drops, digger)
@ -33,35 +42,116 @@ function minetest.handle_node_drops(pos, drops, digger)
return old_handle_node_drops(pos, drops, digger) return old_handle_node_drops(pos, drops, digger)
end end
local node = minetest.get_node(pos)
local wield_stack = digger:get_wielded_item() local wield_stack = digger:get_wielded_item()
local wield_stack_meta = wield_stack:get_meta() local wield_stack_meta = wield_stack:get_meta()
if wield_stack_meta:get_int('is_silk_touch') == 0 then
return old_handle_node_drops(pos, drops, digger)
end
local wield_stack_name = wield_stack:get_name() -- Fortune
local node = minetest.get_node(pos) local fortune = wield_stack_meta:get_float('is_fortune')
local silk_touch_group
if minetest.get_item_group(wield_stack_name, 'pickaxe') > 0 then if fortune > 0 then
silk_touch_group = 'cracky' local new_drops = {}
elseif minetest.get_item_group(wield_stack_name, 'shovel') > 0 then
silk_touch_group = 'crumbly'
elseif minetest.get_item_group(wield_stack_name, 'axe') > 0 then
silk_touch_group = 'choppy'
elseif minetest.get_item_group(wield_stack_name, 'sword') > 0 then
silk_touch_group = 'snappy'
end
if not silk_touch_group for _, itemstring in ipairs(drops) do
or minetest.get_item_group(node.name, silk_touch_group) == 0 if XEnchanting.registered_ores[node.name]
or minetest.get_item_group(node.name, 'no_silktouch') == 1 or minetest.get_item_group(node.name, 'stone') > 0
or minetest.get_item_group(node.name, 'soil') > 0
or minetest.get_item_group(node.name, 'sand') > 0
or minetest.get_item_group(node.name, 'snowy') > 0
or minetest.get_item_group(node.name, 'slippery') > 0
or minetest.get_item_group(node.name, 'tree') > 0
or minetest.get_item_group(node.name, 'leaves') > 0
then then
local tool_capabilities = wield_stack:get_tool_capabilities()
local stack = ItemStack(itemstring)
local chance = math.random(1, tool_capabilities.max_drop_level)
stack:set_count(stack:get_count() * chance)
if stack:get_count() > 0 then
table.insert(new_drops, stack)
end
end
end
if #new_drops > 0 then
return old_handle_node_drops(pos, new_drops, digger)
end
return old_handle_node_drops(pos, drops, digger) return old_handle_node_drops(pos, drops, digger)
end end
-- Silk Touch
local silk_touch = wield_stack_meta:get_float('is_silk_touch')
if silk_touch > 0
and minetest.get_item_group(node.name, 'no_silktouch') == 0
then
-- drop raw item/node -- drop raw item/node
return old_handle_node_drops(pos, { ItemStack(node.name) }, digger) return old_handle_node_drops(pos, { ItemStack(node.name) }, digger)
end
return old_handle_node_drops(pos, drops, digger)
end
minetest.register_on_player_hpchange(function(player, hp_change, reason)
-- Curse of Vanishing
if (player:get_hp() + hp_change) <= 0 then
-- Going to die
local player_inv = player:get_inventory() --[[@as InvRef]]
local player_inventory_lists = { 'main', 'craft' }
for _, list_name in ipairs(player_inventory_lists) do
if not player_inv:is_empty(list_name) then
for i = 1, player_inv:get_size(list_name) do
local stack = player_inv:get_stack(list_name, i)
local stack_meta = stack:get_meta()
if stack_meta:get_float('is_curse_of_vanishing') > 0 then
player_inv:set_stack(list_name, i, ItemStack(''))
end
end
end
end
end
return hp_change
end, true)
-- Knockback (only for players)
local old_calculate_knockback = minetest.calculate_knockback
function minetest.calculate_knockback(player, hitter, time_from_last_punch,
tool_capabilities, dir, distance, damage)
if hitter and hitter:is_player() then
local hitter_wield_stack = hitter:get_wielded_item()
local hitter_wield_stack_meta = hitter_wield_stack:get_meta()
local ench_knockback = hitter_wield_stack_meta:get_float('is_knockback')
if ench_knockback > 0 then
local orig_knockback = old_calculate_knockback(
player,
hitter,
time_from_last_punch,
tool_capabilities,
dir,
distance,
damage
)
orig_knockback = orig_knockback + 1
local new_knockback = orig_knockback + (orig_knockback * (ench_knockback / 100))
player:add_velocity(vector.new(0, new_knockback, 0))
return new_knockback
end
end
return old_calculate_knockback(player, hitter, time_from_last_punch,
tool_capabilities, dir, distance, damage)
end end
local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000 local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000

View File

@ -3,6 +3,9 @@ Sharpness=
Fortune= Fortune=
Unbreaking= Unbreaking=
Efficiency= Efficiency=
Silk Touch=
Curse of Vanishing=
Knockback=
Enchanted= Enchanted=
Enchant= Enchant=
level= level=

View File

@ -3,6 +3,9 @@ Sharpness=Ostrosť
Fortune=Šťastie Fortune=Šťastie
Unbreaking=Nelámavosť Unbreaking=Nelámavosť
Efficiency=Výkonnosť Efficiency=Výkonnosť
Silk Touch=Hodvábny dotyk
Curse of Vanishing=Kliatba zmiznutia
Knockback=Spätný úder
Enchanted=Očarený Enchanted=Očarený
Enchant=Očarovať Enchant=Očarovať
level=level level=level

View File

@ -60,6 +60,7 @@ extract.on('end', () => {
process.exit(1) process.exit(1)
} }
console.log(`${command} --logpath "${logPath}" --check "${cwd}"`)
console.log(`\n${stdout}`) console.log(`\n${stdout}`)
if (fs.existsSync('./logs/check.json')) { if (fs.existsSync('./logs/check.json')) {

View File

@ -102,9 +102,13 @@ minetest.register_node('x_enchanting:table', {
local inv = minetest.get_meta(pos):get_inventory() local inv = minetest.get_meta(pos):get_inventory()
if not inv:is_empty('item') then if not inv:is_empty('item') and inv:get_stack('item', 1):get_meta():get_int('is_enchanted') ~= 1 then
local item_stack = inv:get_stack('item', 1) local item_stack = inv:get_stack('item', 1)
local data = XEnchanting:get_enchantment_data(#bookshelfs, minetest.registered_tools[item_stack:get_name()]) local data = XEnchanting:get_enchantment_data(
clicker,
#bookshelfs,
minetest.registered_tools[item_stack:get_name()]
)
local formspec = XEnchanting:get_formspec(pos, p_name, data) local formspec = XEnchanting:get_formspec(pos, p_name, data)
meta:set_string('formspec', formspec) meta:set_string('formspec', formspec)
@ -343,7 +347,11 @@ minetest.register_node('x_enchanting:table', {
) )
local item_stack = inv:get_stack('item', 1) local item_stack = inv:get_stack('item', 1)
local data = XEnchanting:get_enchantment_data(#bookshelfs, minetest.registered_tools[item_stack:get_name()]) local data = XEnchanting:get_enchantment_data(
player,
#bookshelfs,
minetest.registered_tools[item_stack:get_name()]
)
local formspec = XEnchanting:get_formspec(pos, p_name, data) local formspec = XEnchanting:get_formspec(pos, p_name, data)
meta:set_string('formspec', formspec) meta:set_string('formspec', formspec)
@ -372,7 +380,11 @@ minetest.register_node('x_enchanting:table', {
) )
local item_stack = inv:get_stack('item', 1) local item_stack = inv:get_stack('item', 1)
local data = XEnchanting:get_enchantment_data(#bookshelfs, minetest.registered_tools[item_stack:get_name()]) local data = XEnchanting:get_enchantment_data(
player,
#bookshelfs,
minetest.registered_tools[item_stack:get_name()]
)
local formspec = XEnchanting:get_formspec(pos, p_name, data) local formspec = XEnchanting:get_formspec(pos, p_name, data)
meta:set_string('formspec', formspec) meta:set_string('formspec', formspec)
@ -416,9 +428,8 @@ minetest.register_node('x_enchanting:table', {
end end
local trade_stack = inv:get_stack('trade', 1) local trade_stack = inv:get_stack('trade', 1)
local data = XEnchanting.form_context[p_name].data
if trade_stack:get_count() < selected_slot or not data then if trade_stack:get_count() < selected_slot then
return return
end end

View File

@ -15,6 +15,7 @@
---@field add_entity fun(pos: Vector, name: string, staticdata?: string): ObjectRef|nil Spawn Lua-defined entity at position. Returns `ObjectRef`, or `nil` if failed. ---@field add_entity fun(pos: Vector, name: string, staticdata?: string): ObjectRef|nil Spawn Lua-defined entity at position. Returns `ObjectRef`, or `nil` if failed.
---@field get_node fun(pos: Vector): NodeDef Returns the node at the given position as table in the format `{name="node_name", param1=0, param2=0}`, returns `{name="ignore", param1=0, param2=0}` for unloaded areas. ---@field get_node fun(pos: Vector): NodeDef Returns the node at the given position as table in the format `{name="node_name", param1=0, param2=0}`, returns `{name="ignore", param1=0, param2=0}` for unloaded areas.
---@field registered_nodes table<string, NodeDef|ItemDef> Map of registered node definitions, indexed by name ---@field registered_nodes table<string, NodeDef|ItemDef> Map of registered node definitions, indexed by name
---@field registered_ores table<string, table> Map of registered ore definitions, indexed by name
---@field after fun(time: number|integer, func: fun(...), ...): JobTable Call the function `func` after `time` seconds, may be fractional. Optional: Variable number of arguments that are passed to `func`. ---@field after fun(time: number|integer, func: fun(...), ...): JobTable Call the function `func` after `time` seconds, may be fractional. Optional: Variable number of arguments that are passed to `func`.
---@field sound_play fun(spec: SimpleSoundSpec|string, parameters: SoundParamDef, ephemeral?: boolean): any Returns a `handle`. Ephemeral sounds will not return a handle and can't be stopped or faded. It is recommend to use this for short sounds that happen in response to player actions (e.g. door closing). ---@field sound_play fun(spec: SimpleSoundSpec|string, parameters: SoundParamDef, ephemeral?: boolean): any Returns a `handle`. Ephemeral sounds will not return a handle and can't be stopped or faded. It is recommend to use this for short sounds that happen in response to player actions (e.g. door closing).
---@field add_particlespawner fun(particlespawner_definition: ParticlespawnerDef): number|integer Add a `ParticleSpawner`, an object that spawns an amount of particles over `time` seconds. Returns an `id`, and -1 if adding didn't succeed. ---@field add_particlespawner fun(particlespawner_definition: ParticlespawnerDef): number|integer Add a `ParticleSpawner`, an object that spawns an amount of particles over `time` seconds. Returns an `id`, and -1 if adding didn't succeed.
@ -87,8 +88,11 @@
---@field place_node fun(pos: Vector, node: SetNodeTable): nil Place node with the same effects that a player would cause ---@field place_node fun(pos: Vector, node: SetNodeTable): nil Place node with the same effects that a player would cause
---@field add_particle fun(def: ParticleDef): nil ---@field add_particle fun(def: ParticleDef): nil
---@field registered_tools table<string, ItemDef> Map of registered tool definitions, indexed by name ---@field registered_tools table<string, ItemDef> Map of registered tool definitions, indexed by name
---@field registered_entities table<string, ObjectRef> Map of registered entity definitions, indexed by name
---@field has_feature fun(args: table<string, boolean> | string): boolean | table returns `boolean, missing_features`, `arg`: string or table in format `{foo=true, bar=true}`, `missing_features`: `{foo=true, bar=true}` ---@field has_feature fun(args: table<string, boolean> | string): boolean | table returns `boolean, missing_features`, `arg`: string or table in format `{foo=true, bar=true}`, `missing_features`: `{foo=true, bar=true}`
---@field handle_node_drops fun(pos: Vector, drops: string[], digger: ObjectRef) `drops`: list of itemstrings. Handles drops from nodes after digging: Default action is to put them into digger's inventory. Can be overridden to get different functionality (e.g. dropping items on ground) ---@field handle_node_drops fun(pos: Vector, drops: string[], digger: ObjectRef) `drops`: list of itemstrings. Handles drops from nodes after digging: Default action is to put them into digger's inventory. Can be overridden to get different functionality (e.g. dropping items on ground)
---@field register_on_dieplayer fun(func: fun(player: ObjectRef, reason?: string)): nil Called when a player dies. `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange
---@field register_on_player_hpchange fun(func: fun(player, hp_change, reason), modifier): number Called when the player gets damaged or healed. `player`: ObjectRef of the player. `hp_change`: the amount of change. Negative when it is damage.. `reason`: a PlayerHPChangeReason table.. The `type` field will have one of the following values: `set_hp`: A mod or the engine called `set_hp` without giving a type - use this for custom damage types.. `punch`: Was punched. `reason.object` will hold the puncher, or nil if none. `fall`, `node_damage`: `damage_per_second` from a neighbouring node. `reason.node` will hold the node name or nil. `drown` `respawn`. Any of the above types may have additional fields from mods. `reason.from` will be `mod` or `engine`. `modifier`: when true, the function should return the actual `hp_change`. Note: modifiers only get a temporary `hp_change` that can be modified by later modifiers. Modifiers can return true as a second argument to stop the execution of further functions. Non-modifiers receive the final HP change calculated by the modifiers.
---Minetest settings ---Minetest settings
---@class MinetestSettings ---@class MinetestSettings

View File

@ -5,6 +5,8 @@
---@class MtgFarming ---@class MtgFarming
---@field hoe_on_use fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef, uses: number): ItemStack | nil ---@field hoe_on_use fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef, uses: number): ItemStack | nil
---@field place_seed fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, plantname: string): ItemStack Seed placement ---@field place_seed fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, plantname: string): ItemStack Seed placement
---@field grow_plant fun(pos: Vector, elapsed: number): nil
---@field register_plant fun(name: string, def: table): nil
----Node definition. Used by `minetest.register_node`. ----Node definition. Used by `minetest.register_node`.
---@class NodeDefMtgFarming ---@class NodeDefMtgFarming

View File

@ -27,6 +27,7 @@
---@field on_destruct fun(pos: Vector) Node destructor; called before removing node. Not called for bulk node placement. default: nil ---@field on_destruct fun(pos: Vector) Node destructor; called before removing node. Not called for bulk node placement. default: nil
---@field on_blast fun(pos: Vector, intensity?: number): nil intensity: 1.0 = mid range of regular TNT. If defined, called when an explosion touches the node, instead of removing the node. ---@field on_blast fun(pos: Vector, intensity?: number): nil intensity: 1.0 = mid range of regular TNT. If defined, called when an explosion touches the node, instead of removing the node.
---@field on_timer fun(pos: Vector, elapsed: number): boolean | nil default: nil, called by NodeTimers, see minetest.get_node_timer and NodeTimerRef. elapsed is the total time passed since the timer was started. return true to run the timer for another cycle with the same timeout value. ---@field on_timer fun(pos: Vector, elapsed: number): boolean | nil default: nil, called by NodeTimers, see minetest.get_node_timer and NodeTimerRef. elapsed is the total time passed since the timer was started. return true to run the timer for another cycle with the same timeout value.
---@field _next_state string Only for x_farming composter
---Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length. ---Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length.
---@class NodeTilesDef ---@class NodeTilesDef

View File

@ -6,3 +6,4 @@
---Table helpers ---Table helpers
---@class TableAbstract ---@class TableAbstract
---@field copy fun(table: table): table returns a deep copy of `table` ---@field copy fun(table: table): table returns a deep copy of `table`
---@field indexof fun(list: table, value: any): number returns the smallest numerical index containing the value `val` in the table `list`. Non-numerical indices are ignored. If `val` could not be found, `-1` is returned. `list` must not have negative indices.

View File

@ -1,8 +0,0 @@
---@diagnostic disable: codestyle-check
---Base class Unified Inventory
---@class UnifiedInventory
---@field set_inventory_formspec fun(player: ObjectRef, formspecname: string): nil
---@field register_button fun(name: string, def: table): nil
---@field single_slot fun(x: number, y: number): nil
---@field register_page fun(name: string, def: table): nil
---@field style_full table

View File

@ -4,55 +4,50 @@
---@class XEnchanting ---@class XEnchanting
---@field tools_enchantability table<string, number> Add enchantability to default tools. key/value = tool_name/enchantibility value ---@field tools_enchantability table<string, number> Add enchantability to default tools. key/value = tool_name/enchantibility value
---@field roman_numbers table<number, string> Convert Arabic numbers to Roman numbers ---@field roman_numbers table<number, string> Convert Arabic numbers to Roman numbers
---@field enchantment_defs table<'sharpness' | 'fortune' | 'unbreaking' | 'efficiency' | 'silk_touch', EnchantmentDef> ---@field enchantment_defs table<'sharpness' | 'fortune' | 'unbreaking' | 'efficiency' | 'silk_touch' | 'curse_of_vanishing' | 'knockback', EnchantmentDef>
---@field has_tool_group fun(self: XEnchanting, name: string): string | boolean Check if tool has one of the known tool groups, returns `false` otherwise. ---@field has_tool_group fun(self: XEnchanting, name: string): string | boolean Check if tool has one of the known tool groups, returns `false` otherwise.
---@field set_tool_enchantability fun(self: XEnchanting, tool_def: ItemDef): nil Sets `enchantibility` group and values from base table (`XEnchanting.tools_enchantability`) to all registered tools (atching known group names). If tool has already `enchantibility` group defined it will take the defined value insted. ---@field set_tool_enchantability fun(self: XEnchanting, tool_def: ItemDef): nil Sets `enchantibility` group and values from base table (`XEnchanting.tools_enchantability`) to all registered tools (atching known group names). If tool has already `enchantibility` group defined it will take the defined value insted.
---@field get_enchanted_tool_capabilities fun(self: XEnchanting, tool_def: ItemDef, enchantments: Enchantments[]): GetEnchantedToolCapabilitiesReturn Applies enchantments to item tool capabilities. ---@field get_enchanted_tool_capabilities fun(self: XEnchanting, tool_def: ItemDef, enchantments: Enchantment[]): ToolCapabilitiesDef Applies enchantments to item tool capabilities.
---@field set_enchanted_tool fun(self: XEnchanting, pos: Vector, itemstack: ItemStack, level: number, player_name: string): nil Set choosen enchantment and its modified tool capabilities to itemstack and `item` inventory. This will also get new `randomseed`. ---@field set_enchanted_tool fun(self: XEnchanting, pos: Vector, itemstack: ItemStack, level: number, player_name: string): nil Set choosen enchantment and its modified tool capabilities to itemstack and `item` inventory. This will also get new `randomseed`.
---@field get_enchantment_data fun(self: XEnchanting, nr_of_bookshelfs: number, tool_def: ItemDef): EnchantmentData Algoritm to get aplicable random enchantments. ---@field get_enchantment_data fun(self: XEnchanting, player: ObjectRef, nr_of_bookshelfs: number, tool_def: ItemDef): EnchantmentData Algoritm to get aplicable random enchantments.
---@field get_formspec fun(self: XEnchanting, pos: Vector, player_name: string, data?: EnchantmentData): string Builds and returns `formspec` string ---@field get_formspec fun(self: XEnchanting, pos: Vector, player_name: string, data: EnchantmentData | nil): string Builds and returns `formspec` string
---@field form_context table<string, FormContextDef> Additional form data for player.
---@field scroll_animations table<string, table> Parameters for `ObjectRef` `set_animation` method
---@field player_seeds table<string, number | integer>
---@field registered_ores table<string, boolean> Table with registered ores, `key` ore name
---Enchantment definition ---Enchantment definition
---@class EnchantmentDef ---@class EnchantmentDef
---@field name string Readable name of the enchantment ---@field name string Readable name of the enchantment
---@field final_level_range table<number, number[]> Level range ---@field final_level_range table<number, number[]> Level range
---@field level_def table<number, number> Level bonus ---@field level_def table<number, number> Level bonus. Value will be set in item meta as float.
---@field weight number Enchantment weight ---@field weight number Enchantment weight
---@field randomseed number Math random seed. Will change after item was enchanted. ---@field secondary boolean Will not appear in masked description and can be applied only as additional enchantment by probability chance.
---@field form_context table<string, FormContextDef> Additional form data for player. ---@field groups string[] | nil List of groups for items what are compatible with this enchantment. If `nil` then all groups are compatible.
---@field scroll_animations table<string, table> Parameters for `ObjectRef` `set_animation` method ---@field incompatible string[] | nil List of incompatible enchantments
---Form context ---Form context
---@class FormContextDef ---@class FormContextDef
---@field pos Vector Formspec/node form position ---@field pos Vector Formspec/node form position
---@field data EnchantmentDataSlot Enchantment data ---@field data EnchantmentData | nil Enchantment data
---@class GetEnchantedToolCapabilitiesReturn
---@field tool_capabilities ToolCapabilitiesDef
---@field enchantments_desc string
---@field enchantments_desc_masked string
---@class EnchantmentData ---@class EnchantmentData
---@field slots EnchantmentDataSlot[] ---@field slots EnchantmentDataSlot[]
---@class Enchantments ---@class Enchantment
---@field id string ---@field id string Unique ID of the enchantment
---@field value number ---@field value number Value of the enchantment based on level
---@field level number ---@field level number Level of the enchantment
---@field secondary boolean | nil Will not appear in masked description and can be applied only as additional enchantment by probability chance.
---@field incompatible string[] | nil List of incompatible enchantments
---@class ToolCapData
---@field enchantments_desc string Masket description before enchanting
---@field enchantments_desc_masked string Description added to item description definition after enchanting
---@field tool_capabilities ToolCapabilitiesDef Modified tool capabilities with applied enchantment
---@class EnchantmentDataSlot ---@class EnchantmentDataSlot
---@field final_enchantments Enchantments[]
---@field tool_cap_data ToolCapData
---@field level number ---@field level number
---@field final_enchantments Enchantment[]
---@field tool_cap_data ToolCapabilitiesDef
---@field descriptions {["enchantments_desc"]: string, ["enchantments_desc_masked"]: string }