1
0
mirror of https://bitbucket.org/minetest_gamers/x_enchanting.git synced 2025-06-29 14:21:07 +02:00

Compare commits

..

9 Commits

24 changed files with 472 additions and 57 deletions

106
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,30 +51,125 @@ 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 #### Curse of Vanishing
Causes the item to disappear on death. Causes the item to disappear on death.
### Knockback groups: any
#### Knockback
Increases knockback (players only). 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.
Supported: mobs_monster, mobs_animal
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
@ -130,7 +226,11 @@ GNU Lesser General Public License v2.1 or later (see included LICENSE file)
- x_enchanting_scroll_mesh.png -- Derived from a texture TumeniNodes (CC BY-SA 3.0) - x_enchanting_scroll_mesh.png -- Derived from a texture TumeniNodes (CC BY-SA 3.0)
- x_enchanting_scroll_particle.png - x_enchanting_scroll_particle.png
- x_enchanting_table.png - x_enchanting_table.png
- x_enchanting_trade_slot.png - x_enchanting_gui_slot_bg.png -- Derived from texture Pixel Perfection by XSSheep (CC BY-SA 4.0)
- x_enchanting_gui_cloth_bg.png -- Derived from texture Pixel Perfection by XSSheep (CC BY-SA 4.0)
- x_enchanting_gui_cloth_trade_bg.png -- Derived from texture Pixel Perfection by XSSheep (CC BY-SA 4.0)
- x_enchanting_gui_formbg.png -- Derived from texture Pixel Perfection by XSSheep (CC BY-SA 4.0)
- x_enchanting_gui_hb_bg.png -- Derived from texture Pixel Perfection by XSSheep (CC BY-SA 4.0)
### Models ### Models

256
api.lua
View File

@ -1,5 +1,3 @@
default = minetest.global_exists('default') and default --[[@as MtgDefault]]
local S = minetest.get_translator(minetest.get_current_modname()) local S = minetest.get_translator(minetest.get_current_modname())
---@type XEnchanting ---@type XEnchanting
@ -62,7 +60,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 +98,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 +120,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 +142,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'),
@ -125,7 +158,13 @@ XEnchanting = {
[1] = 1 [1] = 1
}, },
weight = 1, weight = 1,
secondary = true secondary = true,
groups = {
'pickaxe',
'shovel',
'axe'
},
incompatible = { 'fortune' }
}, },
curse_of_vanishing = { curse_of_vanishing = {
name = S('Curse of Vanishing'), name = S('Curse of Vanishing'),
@ -136,7 +175,9 @@ XEnchanting = {
[1] = 1 [1] = 1
}, },
weight = 1, weight = 1,
secondary = true secondary = true,
-- all applicable
groups = nil
}, },
knockback = { knockback = {
name = S('Knockback'), name = S('Knockback'),
@ -149,7 +190,70 @@ XEnchanting = {
[1] = 105, [1] = 105,
[2] = 190 [2] = 190
}, },
weight = 5 weight = 5,
groups = {
'sword'
}
},
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 = {},
@ -159,7 +263,8 @@ XEnchanting = {
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
@ -192,17 +297,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
@ -318,7 +428,7 @@ function XEnchanting.get_enchanted_tool_capabilities(self, tool_def, enchantment
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
@ -390,16 +500,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()
---@type table<string, {["value"]: number}>
local final_enchantments_meta = {}
for i, enchantment in ipairs(final_enchantments) do for i, enchantment in ipairs(final_enchantments) do
stack_meta:set_float('is_' .. enchantment.id, enchantment.value) stack_meta:set_float('is_' .. enchantment.id, enchantment.value)
-- store only necessary data, keeping the meta optimized
final_enchantments_meta[enchantment.id] = {
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))
inv:set_stack('item', 1, itemstack) inv:set_stack('item', 1, itemstack)
@ -433,6 +549,25 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
_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
---- ----
@ -474,7 +609,7 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
-- 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 -- find matching levels
@ -495,7 +630,8 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
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 secondary = enchantment_def.secondary,
incompatible = enchantment_def.incompatible
}) })
end end
end end
@ -513,10 +649,11 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
total_weight = total_weight + self.enchantment_defs[enchantment.id].weight total_weight = total_weight + self.enchantment_defs[enchantment.id].weight
end end
-- Pick a random integer in the half range [0; total_weight] as a number `rand_weight` -- 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 = (final_level + 1) / 50
local probability_level = final_level local probability_level = final_level
---@type Enchantment[]
local possible_enchantments_excl_secodnary = {} local possible_enchantments_excl_secodnary = {}
for _, enchantment in pairs(possible_enchantments) do for _, enchantment in pairs(possible_enchantments) do
@ -533,22 +670,40 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
local rand_ench = possible_enchantments[rand_ench_idx] local rand_ench = possible_enchantments[rand_ench_idx]
if j == 1 then if j == 1 then
-- First pick
-- Dont add cursed/secondary enchantment as first pick -- Dont add cursed/secondary enchantment as first pick
rand_ench_idx = math.random(1, #possible_enchantments_excl_secodnary) rand_ench_idx = math.random(1, #possible_enchantments_excl_secodnary)
rand_ench = possible_enchantments_excl_secodnary[rand_ench_idx] rand_ench = possible_enchantments_excl_secodnary[rand_ench_idx]
table.insert(final_enchantments, rand_ench)
for idx, value in pairs(possible_enchantments) do for idx, value in pairs(possible_enchantments) do
if rand_ench.id == value.id then if rand_ench.id == value.id then
table.remove(possible_enchantments, idx) table.remove(possible_enchantments, idx)
end 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 end
table.insert(final_enchantments, rand_ench)
else else
local probability = (probability_level + 1) / 50 local probability = (probability_level + 1) / 50
table.remove(possible_enchantments, rand_ench_idx)
table.insert(final_enchantments, rand_ench) 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. -- With probability (`final_level` + 1) / 50, keep going. Otherwise, stop picking bonus enchantments.
local rand_probability = math.random() local rand_probability = math.random()
@ -586,6 +741,39 @@ function XEnchanting.get_enchantment_data(self, player, nr_of_bookshelfs, tool_d
return data return data
end end
local function get_hotbar_bg(x, y)
local out = ''
for i = 0, 7, 1 do
out = out .. 'image[' .. x + i .. ',' .. y .. ';1,1;x_enchanting_gui_hb_bg.png]'
end
return out
end
local function get_list_bg(x, y)
local out = ''
for row = 0, 2, 1 do
for i = 0, 7, 1 do
out = out .. 'image[' .. x + i .. ',' .. y + row .. ';1,1;x_enchanting_gui_slot_bg.png]'
end
end
return out
end
local function get_formspec_bg(player_name)
local info = minetest.get_player_information(player_name)
local bg = 'background[5,5;1,1;x_enchanting_gui_formbg.png;true]'
if info.formspec_version > 1 then
bg = 'background9[5,5;1,1;x_enchanting_gui_formbg.png;true;10]'
end
return bg
end
function XEnchanting.get_formspec(self, pos, player_name, data) function XEnchanting.get_formspec(self, pos, player_name, data)
local spos = pos.x .. ',' .. pos.y .. ',' .. pos.z local spos = pos.x .. ',' .. pos.y .. ',' .. pos.z
local inv = minetest.get_meta(pos):get_inventory() local inv = minetest.get_meta(pos):get_inventory()
@ -597,21 +785,29 @@ function XEnchanting.get_formspec(self, pos, player_name, data)
local formspec = { local formspec = {
'size[8,9]', 'size[8,9]',
'bgcolor[#080808BB;true]',
'listcolors[#FFFFFF00;#FFFFFF1A;#FFFFFF00;#30434C;#FFF]',
get_formspec_bg(player_name),
'style_type[label;font=mono,bold]',
'style[slot_1,slot_2,slot_3;font=mono,bold;textcolor=#4D413A]',
'label[0, 0;' .. S('Enchant') .. ']', 'label[0, 0;' .. S('Enchant') .. ']',
-- item
'list[nodemeta:' .. spos .. ';item;0, 2.5;1, 1;]', 'list[nodemeta:' .. spos .. ';item;0, 2.5;1, 1;]',
'image[1,2.5;1,1;x_enchanting_trade_slot.png;]', 'image[0, 2.5;1,1;x_enchanting_gui_cloth_bg.png]',
-- trade
'list[nodemeta:' .. spos .. ';trade;1, 2.5;1, 1;]', 'list[nodemeta:' .. spos .. ';trade;1, 2.5;1, 1;]',
'image[1, 2.5;1,1;x_enchanting_gui_cloth_trade_bg.png]',
-- inventories
'list[current_player;main;0, 4.85;8, 1;]', 'list[current_player;main;0, 4.85;8, 1;]',
'list[current_player;main;0, 6.08;8, 3;8]', 'list[current_player;main;0, 6.08;8, 3;8]',
'listring[nodemeta:' .. spos .. ';trade]', 'listring[nodemeta:' .. spos .. ';trade]',
'listring[current_player;main]', 'listring[current_player;main]',
'listring[nodemeta:' .. spos .. ';item]', 'listring[nodemeta:' .. spos .. ';item]',
'listring[current_player;main]' 'listring[current_player;main]',
} }
if default then formspec[#formspec + 1] = get_hotbar_bg(0, 4.85)
formspec[#formspec + 1] = default.get_hotbar_bg(0, 4.85) formspec[#formspec + 1] = get_list_bg(0, 6.08)
end
-- data -- data
if data then if data then
@ -621,17 +817,17 @@ 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.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']' formspec[#formspec + 1] = 'image_button[2.75,' .. -0.5 + i .. ';5,1;x_enchanting_image_button.png;slot_' .. i .. ';' .. slot.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#594E47', 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.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#FFFF00', S('level') .. ': ' .. slot.level) .. ']' formspec[#formspec + 1] = 'image_button[2.75,' .. -0.5 + i .. ';5,1;x_enchanting_image_button_disabled.png;slot_' .. i .. ';' .. slot.descriptions.enchantments_desc_masked .. ' ' .. minetest.colorize('#594E47', 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.75,' .. -0.5 + i .. ';1,1;x_enchanting_image_trade_' .. i .. '.png;]'
else else
-- disabled buttons -- disabled buttons
---@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 .. ';]' formspec[#formspec + 1] = 'image_button[2.75,' .. -0.5 + i .. ';5,1;x_enchanting_image_button_disabled.png;slot_' .. i .. ';]'
end end
end end
@ -640,7 +836,7 @@ function XEnchanting.get_formspec(self, pos, player_name, data)
for i = 1, 3, 1 do for i = 1, 3, 1 do
-- disabled buttons -- disabled buttons
---@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 .. ';]' formspec[#formspec + 1] = 'image_button[2.75,' .. -0.5 + i .. ';5,1;x_enchanting_image_button_disabled.png;slot_' .. i .. ';]'
end end
model_scroll_is_open = false model_scroll_is_open = false

Binary file not shown.

Binary file not shown.

137
init.lua
View File

@ -7,12 +7,96 @@ local mod_start_time = minetest.get_us_time()
dofile(path .. '/api.lua') dofile(path .. '/api.lua')
dofile(path .. '/table.lua') dofile(path .. '/table.lua')
---Check if string X starts with string Y
---@param str string
---@param start string
---@return boolean
local function starts_with(str, start)
return str:sub(1, #start) == start
end
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
-- Tools override
for name, tool_def in pairs(minetest.registered_tools) do for name, tool_def in pairs(minetest.registered_tools) do
if XEnchanting:has_tool_group(name) then if XEnchanting:has_tool_group(name) then
XEnchanting:set_tool_enchantability(tool_def) XEnchanting:set_tool_enchantability(tool_def)
end end
end end
-- Ores override - Fortune
for _, def in pairs(minetest.registered_ores) do
if not XEnchanting.registered_ores[def.ore] then
XEnchanting.registered_ores[def.ore] = true
end
end
-- Entities override - Looting
for name, def in pairs(minetest.registered_entities) do
if starts_with(name, 'mobs_animal:')
or starts_with(name, 'mobs_monster:')
then
if def.on_punch and def.drops then
local prev_on_punch = def.on_punch
---@param self table
---@param puncher ObjectRef|nil
---@param time_from_last_punch number|integer|nil
---@param tool_capabilities ToolCapabilitiesDef|nil
---@param dir Vector
---@param damage number|integer
---@return boolean|nil
def.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
if not self
or not self.object
or not self.object:get_luaentity()
or not puncher
or not tool_capabilities
then
return prev_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
end
local wield_stack = puncher:get_wielded_item()
local wield_stack_meta = wield_stack:get_meta()
local looting = wield_stack_meta:get_float('is_looting')
if looting == 0 then
return prev_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
end
local pos = self.object:get_pos()
prev_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
if self.health and self.health <= 0 then
local death_by_player = self.cause_of_death
and self.cause_of_death.puncher
and self.cause_of_death.puncher:is_player()
if death_by_player and pos then
for _, drop in ipairs(def.drops) do
if math.random(10, 100) / 100 < looting / (looting + 1) then
local drop_min = drop.min or 0
local drop_max = drop.max or 0
local count = math.random(drop_min, drop_max)
local stack = ItemStack({
name = drop.name,
count = count
})
local chance = math.random(1, tool_capabilities.max_drop_level)
stack:set_count(stack:get_count() * chance)
if stack:get_count() > 0 then
minetest.item_drop(stack, puncher, pos)
end
end
end
end
end
end
end
end
end
end) end)
minetest.register_on_joinplayer(function(player, last_login) minetest.register_on_joinplayer(function(player, last_login)
@ -27,7 +111,6 @@ 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)
@ -37,29 +120,63 @@ 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_float('is_silk_touch') == 0 then -- Fortune
local fortune = wield_stack_meta:get_float('is_fortune')
if fortune > 0 then
local new_drops = {}
for _, itemstring in ipairs(drops) do
if XEnchanting.registered_ores[node.name]
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
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
local node = minetest.get_node(pos) -- Silk Touch
local silk_touch = wield_stack_meta:get_float('is_silk_touch')
if minetest.get_item_group(node.name, 'no_silktouch') == 1 then
return old_handle_node_drops(pos, drops, digger)
end
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 end
minetest.register_on_player_hpchange(function(player, hp_change, reason) minetest.register_on_player_hpchange(function(player, hp_change, reason)
-- Curse of Vanishing
if (player:get_hp() + hp_change) <= 0 then if (player:get_hp() + hp_change) <= 0 then
-- Going to die -- Going to die
local player_inv = player:get_inventory() --[[@as InvRef]] local player_inv = player:get_inventory() --[[@as InvRef]]
-- Curse of Vanishing
local player_inventory_lists = { 'main', 'craft' } local player_inventory_lists = { 'main', 'craft' }
for _, list_name in ipairs(player_inventory_lists) do for _, list_name in ipairs(player_inventory_lists) do
@ -72,8 +189,6 @@ minetest.register_on_player_hpchange(function(player, hp_change, reason)
player_inv:set_stack(list_name, i, ItemStack('')) player_inv:set_stack(list_name, i, ItemStack(''))
end end
end end
player_inv:set_list(list_name, {})
end end
end end
end end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

View File

@ -14,3 +14,4 @@
---@field on_detach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as `self`. `child`: an `ObjectRef` of the child that detaches ---@field on_detach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as `self`. `child`: an `ObjectRef` of the child that detaches
---@field on_detach fun(self: table, parent: ObjectRef|nil): nil Function receive a "luaentity" table as `self`. `parent`: an `ObjectRef` (can be `nil`) from where it got detached. This happens before the parent object is removed from the world. ---@field on_detach fun(self: table, parent: ObjectRef|nil): nil Function receive a "luaentity" table as `self`. `parent`: an `ObjectRef` (can be `nil`) from where it got detached. This happens before the parent object is removed from the world.
---@field get_staticdata fun(self: table) Function receive a "luaentity" table as `self`. Should return a string that will be passed to `on_activate` when the object is instantiated the next time. ---@field get_staticdata fun(self: table) Function receive a "luaentity" table as `self`. Should return a string that will be passed to `on_activate` when the object is instantiated the next time.
---@field drops table Custom for mob drops

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,11 +88,12 @@
---@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 registered_entities table<string, EntityDef> 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_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. ---@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.
---@field get_player_information fun(player_name: string): table Table containing information about a player
---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

@ -14,6 +14,8 @@
---@field form_context table<string, FormContextDef> Additional form data for player. ---@field form_context table<string, FormContextDef> Additional form data for player.
---@field scroll_animations table<string, table> Parameters for `ObjectRef` `set_animation` method ---@field scroll_animations table<string, table> Parameters for `ObjectRef` `set_animation` method
---@field player_seeds table<string, number | integer> ---@field player_seeds table<string, number | integer>
---@field registered_ores table<string, boolean> Table with registered ores, `key` ore name
---@field settings {["x_enchanting_small_formspec"]: boolean}
---Enchantment definition ---Enchantment definition
@ -23,6 +25,8 @@
---@field level_def table<number, number> Level bonus. Value will be set in item meta as float. ---@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 secondary boolean Will not appear in masked description and can be applied only as additional enchantment by probability chance. ---@field secondary boolean Will not appear in masked description and can be applied only as additional enchantment by probability chance.
---@field groups string[] | nil List of groups for items what are compatible with this enchantment. If `nil` then all groups are compatible.
---@field incompatible string[] | nil List of incompatible enchantments
---Form context ---Form context
@ -36,10 +40,11 @@
---@class Enchantment ---@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 Will not appear in masked description and can be applied only as additional enchantment by probability chance. ---@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 EnchantmentDataSlot ---@class EnchantmentDataSlot