Add quiver

This commit is contained in:
Juraj Vajda 2022-10-17 00:02:58 +00:00
commit 6eff91575d
44 changed files with 2426 additions and 959 deletions

View File

@ -13,7 +13,7 @@
"license": "LGPL-2.1-or-later", "license": "LGPL-2.1-or-later",
"media_license": "CC-BY-SA-4.0", "media_license": "CC-BY-SA-4.0",
"repo": "https://bitbucket.org/minetest_gamers/x_bows/src/master/", "repo": "https://bitbucket.org/minetest_gamers/x_bows/src/master/",
"issue_tracker": "https://bitbucket.org/minetest_gamers/x_bows/issues", "issue_tracker": "https://bitbucket.org/minetest_gamers/x_bows/issues?status=new&status=open",
"forums": 26466, "forums": 26466,
"video_url": "https://youtu.be/pItpltmUoa8" "video_url": "https://youtu.be/pItpltmUoa8"
} }

View File

@ -23,6 +23,7 @@ read_globals = {
"PcgRandom", "PcgRandom",
"ItemStack", "ItemStack",
"AreaStore", "AreaStore",
"unpack",
"vector", "vector",

View File

@ -2,7 +2,7 @@
Adds bow and arrows to Minetest. Adds bow and arrows to Minetest.
![screenshot](screenshot.png) ![screenshot](screenshot.1.png)
Video: https://youtu.be/pItpltmUoa8 Video: https://youtu.be/pItpltmUoa8
@ -11,6 +11,7 @@ Video: https://youtu.be/pItpltmUoa8
* bow will force you sneak when loaded (optional dep. playerphysics) * bow will force you sneak when loaded (optional dep. playerphysics)
* loaded bow will slightly adjust the player FOV * loaded bow will slightly adjust the player FOV
* bow uses minetest tool capabilities - if the bow is not loaded for long enough (time from last puch) the arrow will fly shorter range * bow uses minetest tool capabilities - if the bow is not loaded for long enough (time from last puch) the arrow will fly shorter range
* charged bow in inventory will discharge and give back the arrow when not selected
* arrow uses raycast * arrow uses raycast
* arrow has chance of critical shots/hits (only on full punch interval) * arrow has chance of critical shots/hits (only on full punch interval)
* arrow uses minetest damage calculation (including 3d_armor) for making damage (no hardcoded values) * arrow uses minetest damage calculation (including 3d_armor) for making damage (no hardcoded values)
@ -24,9 +25,15 @@ Video: https://youtu.be/pItpltmUoa8
* registers only one entity reused for all arrows * registers only one entity reused for all arrows
* (experimental) poison arrow - dealing damage for 5s but will not kill the target * (experimental) poison arrow - dealing damage for 5s but will not kill the target
* target block reduces fall damage by -30 * target block reduces fall damage by -30
* quiver for more arrow storage (can hold only arrows)
* quiver perks when in inventory (faster arrows, more arrow damage...)
* quiver shows temporarily its inventory in HUD overlay when loading or shooting (quickview)
* quiver item shows its content in infotext (hover over the item)
## How To ## How To
### Bow
With the bow selected in hotbar and in your hand, press right click on mouse (PC) or the same action as when placing blocks, to load the bow. With the bow selected in hotbar and in your hand, press right click on mouse (PC) or the same action as when placing blocks, to load the bow.
For bow to be loaded you have to have arrows in the main invetory. Charging bow will have slight sound effect and can be fired at any time with left click (PC) For bow to be loaded you have to have arrows in the main invetory. Charging bow will have slight sound effect and can be fired at any time with left click (PC)
or the same action as when you are digging a block. Waiting for full charge of the bow is recommended or the same action as when you are digging a block. Waiting for full charge of the bow is recommended
@ -47,6 +54,17 @@ If you shoot the arrow before the bow is fully charged the speed/distance will b
Changing the selection in hotbar will unload the bow and give you back arrow from the unloaded bow - this applies also when login in to the game (bow will be discharged and arrow will be returned to inventory) and also when you drop the charged arrow (discharged bow will be dropped with arrow item). Changing the selection in hotbar will unload the bow and give you back arrow from the unloaded bow - this applies also when login in to the game (bow will be discharged and arrow will be returned to inventory) and also when you drop the charged arrow (discharged bow will be dropped with arrow item).
If you have `playerphysics` or `player_monoids` mod installed, charged bow will slow you down until you release the arrow. If you have `playerphysics` or `player_monoids` mod installed, charged bow will slow you down until you release the arrow.
### Quiver
Quiver item can hold inventory of arrows. When player has quiver in his/hers inventory, bow can take arrows from quiver, otherwise arrows outside of the quiver are used to load the bow.
Though, if arrow from quivers are used to laod the bow, the arrows have additional speed and damage.
If we are loading/shooting arrows from quiver, there is temporary quickview HUD overlay shown, peeking in to the quivers inventory from which the arrow was taken. Arrows used from quiver will be faster only when the bow is fully charged - see "How To - Bow" for more information on how to know when bow is fully charged.
There are few indications on how to know when the bow shot arrow from quiver:
* there is temporary HUD overview shown peeking in to the quiver inventory
* after shooting, arrow will have blue/purple particle trail (if bow was fully charged)
## Dependencies ## Dependencies
- none - none
@ -73,11 +91,6 @@ GNU Lesser General Public License v2.1 or later (see included LICENSE file)
- x_bows_bow_wood.png - x_bows_bow_wood.png
- x_bows_bow_wood_charged.png - x_bows_bow_wood_charged.png
- x_bows_arrow_wood.png - x_bows_arrow_wood.png
- x_bows_arrow_tile_point_top.png
- x_bows_arrow_tile_point_right.png
- x_bows_arrow_tile_point_bottom.png
- x_bows_arrow_tile_point_left.png
- x_bows_arrow_tile_tail.png
- x_bows_arrow_particle.png - x_bows_arrow_particle.png
- x_bows_arrow_tipped_particle.png - x_bows_arrow_tipped_particle.png
- x_bows_bubble.png - x_bows_bubble.png
@ -92,6 +105,18 @@ Modified by SaKeL:
- x_bows_arrow_diamond.png - x_bows_arrow_diamond.png
- x_bows_arrow_diamond_poison.png - x_bows_arrow_diamond_poison.png
**CC-BY-SA-3.0, by paramat**
- x_bows_quiver_hotbar_selected.png
- x_bows_quiver_hotbar.png
**LGPL-2.1-or-later, by SaKeL**
- x_bows_quiver.png
- x_bows_quiver_open.png
- x_bows_arrow_slot.png
- x_bows_arrow_mesh.png
### Sounds ### Sounds
**Creative Commons License, EminYILDIRIM**, https://freesound.org **Creative Commons License, EminYILDIRIM**, https://freesound.org
@ -122,6 +147,24 @@ Modified by SaKeL:
- x_bows_arrow_successful_hit.ogg - x_bows_arrow_successful_hit.ogg
**Creative Commons License, Shamewap**, https://freesound.org
- x_bows_quiver.1.ogg
- x_bows_quiver.2.ogg
- x_bows_quiver.3.ogg
- x_bows_quiver.4.ogg
- x_bows_quiver.5.ogg
- x_bows_quiver.6.ogg
- x_bows_quiver.7.ogg
- x_bows_quiver.8.ogg
- x_bows_quiver.9.ogg
### Models
****LGPL-2.1-or-later, by SaKeL**
- x_bows_arrow.obj
## Installation ## Installation
see: http://wiki.minetest.com/wiki/Installing_Mods see: http://wiki.minetest.com/wiki/Installing_Mods

958
arrow.lua

File diff suppressed because it is too large Load Diff

874
init.lua
View File

@ -6,430 +6,629 @@ ItemStack = ItemStack--[[@as ItemStack]]
vector = vector--[[@as Vector]] vector = vector--[[@as Vector]]
default = default--[[@as MtgDefault]] default = default--[[@as MtgDefault]]
math.randomseed(tonumber(tostring(os.time()):reverse():sub(1, 9))--[[@as number]])
local mod_start_time = minetest.get_us_time() local mod_start_time = minetest.get_us_time()
local bow_charged_timer = 0 local bow_charged_timer = 0
-- main class ---x_bows main class
---@class XBows
x_bows = { x_bows = {
pvp = minetest.settings:get_bool('enable_pvp') or false, pvp = minetest.settings:get_bool('enable_pvp') or false,
creative = minetest.settings:get_bool('creative_mode') or false, creative = minetest.settings:get_bool('creative_mode') or false,
mesecons = minetest.get_modpath('mesecons'), mesecons = minetest.get_modpath('mesecons'),
hbhunger = minetest.get_modpath('hbhunger'), hbhunger = minetest.get_modpath('hbhunger'),
playerphysics = minetest.get_modpath('playerphysics'), playerphysics = minetest.get_modpath('playerphysics'),
player_monoids = minetest.get_modpath('player_monoids'), player_monoids = minetest.get_modpath('player_monoids'),
registered_arrows = {}, registered_arrows = {},
registered_bows = {}, registered_bows = {},
player_bow_sneak = {}, registered_quivers = {},
settings = { player_bow_sneak = {},
x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false) settings = {
} x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false)
},
quiver = {
hud_item_ids = {},
after_job = {}
},
charge_sound_after_job = {}
} }
---Shorthand for checking creative priv
---@param name string
---@return boolean
function x_bows.is_creative(name) function x_bows.is_creative(name)
return x_bows.creative or minetest.check_player_privs(name, {creative = true}) return x_bows.creative or minetest.check_player_privs(name, {creative = true})
end end
---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also ---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
---@param player ObjectRef Player Ref ---@param player ObjectRef Player Ref
---@param includeWielded? boolean Will include reset for wielded bow also ---@param includeWielded? boolean Will include reset for wielded bow also. default: `false`
---@return nil ---@return nil
local function reset_charged_bow(player, includeWielded) local function reset_charged_bow(player, includeWielded)
local _includeWielded = includeWielded or false local _includeWielded = includeWielded or false
local inv = player:get_inventory() local inv = player:get_inventory()
---@cast inv InvRef if inv and inv:contains_item('main', 'x_bows:bow_wood_charged') then
if inv:contains_item('main', 'x_bows:bow_wood_charged') then local inv_list = inv:get_list('main')
local inv_list = inv:get_list('main')
for i, st in ipairs(inv_list) do for i, st in ipairs(inv_list) do
local reset = _includeWielded or player:get_wield_index() ~= i local reset = _includeWielded or player:get_wield_index() ~= i
if not st:is_empty() and x_bows.registered_bows[st:get_name()] and reset then if not st:is_empty() and x_bows.registered_bows[st:get_name()] and reset then
local item_meta = st:get_meta() local item_meta = st:get_meta()
local arrow = item_meta:get_string('arrow') local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string')))
-- return arrow -- return arrow
if arrow ~= '' and not x_bows.is_creative(player:get_player_name()) then if arrow_itemstack and not x_bows.is_creative(player:get_player_name()) then
if inv:room_for_item('main', {name=arrow}) then if inv:room_for_item('main', {name=arrow_itemstack:get_name()}) then
inv:add_item('main', arrow) inv:add_item('main', arrow_itemstack:get_name())
else else
minetest.item_drop(ItemStack({name=arrow, count=1}), player, player:get_pos()) minetest.item_drop(ItemStack({name=arrow_itemstack:get_name(), count=1}), player, player:get_pos())
end end
end end
-- reset bow to uncharged bow -- reset bow to uncharged bow
inv:set_stack('main', i, ItemStack({ inv:set_stack('main', i, ItemStack({
name=x_bows.registered_bows[st:get_name()].name, name=x_bows.registered_bows[st:get_name()].name,
count=st:get_count(), count=st:get_count(),
wear=st:get_wear() wear=st:get_wear()
})) }))
end end
end end
end end
end end
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
reset_charged_bow(player, true) reset_charged_bow(player, true)
x_bows.quiver.close_quiver(player)
end) end)
---Register bows
---@param name string
---@param def table
---@return boolean|nil
function x_bows.register_bow(name, def) function x_bows.register_bow(name, def)
if name == nil or name == '' then if name == nil or name == '' then
return false return false
end end
def.name = 'x_bows:' .. name def.name = 'x_bows:' .. name
def.name_charged = 'x_bows:' .. name .. '_charged' def.name_charged = 'x_bows:' .. name .. '_charged'
def.description = def.description or name def.description = def.description or name
def.uses = def.uses or 150 def.uses = def.uses or 150
x_bows.registered_bows[def.name_charged] = def x_bows.registered_bows[def.name_charged] = def
-- not charged bow -- not charged bow
minetest.register_tool(def.name, { minetest.register_tool(def.name, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: '
.. (1 / def.crit_chance) * 100 .. '%'), .. (1 / def.crit_chance) * 100 .. '%'),
inventory_image = def.inventory_image or 'x_bows_bow_wood.png', inventory_image = def.inventory_image or 'x_bows_bow_wood.png',
-- on_use = function(itemstack, user, pointed_thing) on_place = x_bows.load,
-- end, on_secondary_use = x_bows.load,
on_place = x_bows.load, groups = {bow = 1, flammable = 1}
on_secondary_use = x_bows.load, })
groups = {bow = 1, flammable = 1},
-- range = 0
})
-- charged bow -- charged bow
minetest.register_tool(def.name_charged, { minetest.register_tool(def.name_charged, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: '
.. (1 / def.crit_chance) * 100 .. '%'), .. (1 / def.crit_chance) * 100 .. '%'),
inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png', inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png',
on_use = x_bows.shoot, on_use = x_bows.shoot,
groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1}, groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1},
on_drop = function(itemstack, dropper, pos) on_drop = function(itemstack, dropper, pos)
local item_meta = itemstack:get_meta() local item_meta = itemstack:get_meta()
local arrow = item_meta:get_string('arrow') local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string')))
-- return arrow -- return arrow
if arrow ~= '' and not x_bows.is_creative(dropper:get_player_name()) then if arrow_itemstack and not x_bows.is_creative(dropper:get_player_name()) then
minetest.item_drop(ItemStack({name=arrow, count=1}), dropper, {x=pos.x + 0.5, y=pos.y + 0.5, z=pos.z + 0.5}) minetest.item_drop(ItemStack({name=arrow_itemstack:get_name(), count=1}), dropper, {x=pos.x + 0.5, y=pos.y + 0.5, z=pos.z + 0.5})
end end
itemstack:set_name(def.name) itemstack:set_name(def.name)
-- returns leftover itemstack -- returns leftover itemstack
return minetest.item_drop(itemstack, dropper, pos) return minetest.item_drop(itemstack, dropper, pos)
end end
}) })
-- recipes -- recipes
if def.recipe then if def.recipe then
minetest.register_craft({ minetest.register_craft({
output = def.name, output = def.name,
recipe = def.recipe recipe = def.recipe
}) })
end end
end end
---Register arrows
---@param name string
---@param def table
---@return boolean|nil
function x_bows.register_arrow(name, def) function x_bows.register_arrow(name, def)
if name == nil or name == '' then if name == nil or name == '' then
return false return false
end end
def.name = 'x_bows:' .. name def.name = 'x_bows:' .. name
def.description = def.description or name def.description = def.description or name
x_bows.registered_arrows[def.name] = def x_bows.registered_arrows[def.name] = def
minetest.register_craftitem('x_bows:' .. name, { minetest.register_craftitem('x_bows:' .. name, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: ' description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: '
.. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: ' .. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: '
.. def.tool_capabilities.full_punch_interval .. 's'), .. def.tool_capabilities.full_punch_interval .. 's'),
inventory_image = def.inventory_image, short_description = def.description,
groups = {arrow = 1, flammable = 1} inventory_image = def.inventory_image,
}) groups = {arrow = 1, flammable = 1}
})
-- recipes -- recipes
if def.craft then if def.craft then
minetest.register_craft({ minetest.register_craft({
output = def.name ..' ' .. (def.craft_count or 4), output = def.name ..' ' .. (def.craft_count or 4),
recipe = def.craft recipe = def.craft
}) })
end end
end end
---Register quivers
---@param name string
---@param def table
---@return boolean|nil
function x_bows.register_quiver(name, def)
if name == nil or name == '' then
return false
end
def.name = 'x_bows:' .. name
def.name_open = 'x_bows:' .. name .. '_open'
def.description = def.description or name
def.uses = def.uses or 150
x_bows.registered_quivers[def.name] = def
---closed quiver
minetest.register_tool(def.name, {
description = def.description
.. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.faster_arrows) * 100 .. '%')
.. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.add_damage),
short_description = def.short_description
.. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.faster_arrows) * 100 .. '%')
.. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.add_damage),
inventory_image = def.inventory_image or 'x_bows_quiver.png',
wield_image = def.wield_image or 'x_bows_quiver.png',
groups = {quiver = 1, flammable = 1},
on_secondary_use = function(itemstack, user, pointed_thing)
return x_bows.open_quiver(itemstack, user)
end,
---@param itemstack ItemStack
---@param placer ObjectRef
---@param pointed_thing PointedThingDef
---@return ItemStack|nil
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.under then
local node = minetest.get_node(pointed_thing.under)
local node_def = minetest.registered_nodes[node.name]
if node_def and node_def.on_rightclick then
return node_def.on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
end
end
return x_bows.open_quiver(itemstack, placer)
end
})
---open quiver
minetest.register_tool(def.name_open, {
description = def.description
.. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.faster_arrows) * 100 .. '%')
.. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.add_damage),
short_description = def.short_description
.. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.faster_arrows) * 100 .. '%')
.. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.add_damage),
inventory_image = def.inventory_image_open or 'x_bows_quiver_open.png',
wield_image = def.wield_image_open or 'x_bows_quiver_open.png',
groups = {quiver = 1, flammable = 1, not_in_creative_inventory = 1},
---@param itemstack ItemStack
---@param dropper ObjectRef|nil
---@param pos Vector
---@return ItemStack
on_drop = function (itemstack, dropper, pos)
local replace_item = x_bows.quiver.get_replacement_item(itemstack, 'x_bows:quiver')
return minetest.item_drop(replace_item, dropper, pos)
end
})
-- recipes
if def.recipe then
minetest.register_craft({
output = def.name,
recipe = def.recipe
})
end
end
---Loads bow
---@param itemstack ItemStack
---@param user ObjectRef
---@param pointed_thing PointedThingDef
---@return ItemStack
function x_bows.load(itemstack, user, pointed_thing) function x_bows.load(itemstack, user, pointed_thing)
local time_load = minetest.get_us_time() local player_name = user:get_player_name()
local inv = user:get_inventory() local inv = user:get_inventory()--[[@as InvRef]]
local inv_list = inv:get_list('main') local inv_list = inv:get_list('main')
local bow_name = itemstack:get_name() local bow_name = itemstack:get_name()
local bow_def = x_bows.registered_bows[bow_name .. '_charged'] local bow_def = x_bows.registered_bows[bow_name .. '_charged']
local itemstack_arrows = {} ---@alias ItemStackArrows {["stack"]: ItemStack, ["idx"]: number|integer}[]
---@type ItemStackArrows
local itemstack_arrows = {}
if pointed_thing.under then ---trigger right click event if pointed item has one
local node = minetest.get_node(pointed_thing.under) if pointed_thing.under then
local node_def = minetest.registered_nodes[node.name] local node = minetest.get_node(pointed_thing.under)
local node_def = minetest.registered_nodes[node.name]
if node_def and node_def.on_rightclick then if node_def and node_def.on_rightclick then
return node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing) return node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing)
end end
end end
for _, st in ipairs(inv_list) do ---find itemstack arrow in quiver
if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then local quiver_result = x_bows.quiver.get_itemstack_arrow_from_quiver(user)
table.insert(itemstack_arrows, st) local itemstack_arrow = quiver_result.found_arrow_stack
end
end
-- take 1st found arrow in the list if itemstack_arrow then
local itemstack_arrow = itemstack_arrows[1] local itemstack_arrow_meta = itemstack_arrow:get_meta()
if itemstack_arrow and bow_def then itemstack_arrow_meta:set_int('is_arrow_from_quiver', 1)
local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities itemstack_arrow_meta:set_string('quiver_name', quiver_result.quiver_name)
itemstack_arrow_meta:set_string('quiver_id', quiver_result.quiver_id)
else
x_bows.quiver.remove_hud(user)
minetest.after(0, function(v_user, v_bow_name, v_time_load) ---find itemstack arrow in players inventory
local wielded_item = v_user:get_wielded_item() for i, st in ipairs(inv_list) do
local wielded_item_name = wielded_item:get_name() if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then
table.insert(itemstack_arrows, {stack = st, idx = i})
end
end
if wielded_item_name == v_bow_name then -- take 1st found arrow in the list
local meta = wielded_item:get_meta() itemstack_arrow = #itemstack_arrows > 0 and itemstack_arrows[1].stack or nil
end
meta:set_string('arrow', itemstack_arrow:get_name()) if itemstack_arrow and bow_def then
meta:set_string('time_load', tostring(v_time_load)) local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities
wielded_item:set_name(v_bow_name .. '_charged')
v_user:set_wielded_item(wielded_item)
if not x_bows.is_creative(user:get_player_name()) then ---@param v_user ObjectRef
inv:remove_item('main', itemstack_arrow:get_name()) ---@param v_bow_name string
end ---@param v_itemstack_arrow ItemStack
end ---@param v_inv InvRef
end, user, bow_name, time_load) ---@param v_itemstack_arrows ItemStackArrows
minetest.after(0, function(v_user, v_bow_name, v_itemstack_arrow, v_inv, v_itemstack_arrows)
local wielded_item = v_user:get_wielded_item()
-- sound plays when charge time reaches full punch interval time if wielded_item:get_name() == v_bow_name then
-- @TODO: find a way to prevent this from playing when not fully charged local wielded_item_meta = wielded_item:get_meta()
minetest.after(_tool_capabilities.full_punch_interval, function(v_user, v_bow_name) local v_itemstack_arrow_meta = v_itemstack_arrow:get_meta()
local wielded_item = v_user:get_wielded_item()
local wielded_item_name = wielded_item:get_name()
if wielded_item_name == v_bow_name .. '_charged' then wielded_item_meta:set_string('arrow_itemstack_string', minetest.serialize(v_itemstack_arrow:to_table()))
minetest.sound_play('x_bows_bow_loaded', { wielded_item_meta:set_string('time_load', tostring(minetest.get_us_time()))
to_player = user:get_player_name(),
gain = 0.6
})
end
end, user, bow_name)
minetest.sound_play('x_bows_bow_load', { wielded_item:set_name(v_bow_name .. '_charged')
to_player = user:get_player_name(), v_user:set_wielded_item(wielded_item)
gain = 0.6
})
return itemstack if not x_bows.is_creative(v_user:get_player_name()) and v_itemstack_arrow_meta:get_int('is_arrow_from_quiver') ~= 1 then
end v_itemstack_arrow:take_item()
v_inv:set_stack('main', v_itemstack_arrows[1].idx, v_itemstack_arrow)
end
end
end, user, bow_name, itemstack_arrow, inv, itemstack_arrows)
---stop previous charged sound after job
if x_bows.charge_sound_after_job[player_name] then
for _, v in pairs(x_bows.charge_sound_after_job[player_name]) do
v:cancel()
end
x_bows.charge_sound_after_job[player_name] = {}
else
x_bows.charge_sound_after_job[player_name] = {}
end
---sound plays when charge time reaches full punch interval time
table.insert(x_bows.charge_sound_after_job[player_name], minetest.after(_tool_capabilities.full_punch_interval, function(v_user, v_bow_name)
local wielded_item = v_user:get_wielded_item()
local wielded_item_name = wielded_item:get_name()
if wielded_item_name == v_bow_name .. '_charged' then
minetest.sound_play('x_bows_bow_loaded', {
to_player = v_user:get_player_name(),
gain = 0.6
})
end
end, user, bow_name))
minetest.sound_play('x_bows_bow_load', {
to_player = player_name,
gain = 0.6
})
return itemstack
end
return itemstack
end end
---Shoots the bow
---@param itemstack ItemStack
---@param user ObjectRef
---@param pointed_thing? PointedThingDef
---@return ItemStack
function x_bows.shoot(itemstack, user, pointed_thing) function x_bows.shoot(itemstack, user, pointed_thing)
local time_shoot = minetest.get_us_time(); local time_shoot = minetest.get_us_time();
local meta = itemstack:get_meta() local meta = itemstack:get_meta()
local meta_arrow = meta:get_string('arrow') local time_load = tonumber(meta:get_string('time_load'))
local time_load = tonumber(meta:get_string('time_load')) local tflp = (time_shoot - time_load) / 1000000
local tflp = (time_shoot - time_load) / 1000000 ---@type ItemStack
local arrow_itemstack = ItemStack(minetest.deserialize(meta:get_string('arrow_itemstack_string')))
local arrow_itemstack_meta = arrow_itemstack:get_meta()
local arrow_name = arrow_itemstack:get_name()
local is_arrow_from_quiver = arrow_itemstack_meta:get_int('is_arrow_from_quiver')
local quiver_name = arrow_itemstack_meta:get_string('quiver_name')
local quiver_id = arrow_itemstack_meta:get_string('quiver_id')
local detached_inv = x_bows.quiver.get_or_create_detached_inv(
quiver_id,
user:get_player_name()
)
if not x_bows.registered_arrows[meta_arrow] then if is_arrow_from_quiver == 1 then
return itemstack x_bows.quiver.udate_or_create_hud(user, detached_inv:get_list('main'))
end else
x_bows.quiver.remove_hud(user)
end
local bow_name_charged = itemstack:get_name() if not x_bows.registered_arrows[arrow_name] then
local bow_name = x_bows.registered_bows[bow_name_charged].name return itemstack
local uses = x_bows.registered_bows[bow_name_charged].uses end
local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance
local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities
local staticdata = { local bow_name_charged = itemstack:get_name()
arrow = meta_arrow, local bow_name = x_bows.registered_bows[bow_name_charged].name
user_name = user:get_player_name(), local uses = x_bows.registered_bows[bow_name_charged].uses
is_critical_hit = false, local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance
_tool_capabilities = _tool_capabilities, local _tool_capabilities = x_bows.registered_arrows[arrow_name].tool_capabilities
_tflp = tflp, local quiver_xbows_def = x_bows.registered_quivers[quiver_name]
}
-- crits, only on full punch interval local staticdata = {
if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then arrow = arrow_name,
if math.random(1, crit_chance) == 1 then user_name = user:get_player_name(),
staticdata.is_critical_hit = true is_critical_hit = false,
end _tool_capabilities = _tool_capabilities,
end _tflp = tflp,
}
local sound_name = 'x_bows_bow_shoot' ---crits, only on full punch interval
if staticdata.is_critical_hit then if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then
sound_name = 'x_bows_bow_shoot_crit' if math.random(1, crit_chance) == 1 then
end staticdata.is_critical_hit = true
end
end
meta:set_string('arrow', '') ---speed multiply
itemstack:set_name(bow_name) if quiver_xbows_def and quiver_xbows_def.faster_arrows and quiver_xbows_def.faster_arrows > 1 then
staticdata.faster_arrows_multiplier = quiver_xbows_def.faster_arrows
end
local pos = user:get_pos() ---add damage
local dir = user:get_look_dir() if quiver_xbows_def and quiver_xbows_def.add_damage and quiver_xbows_def.add_damage > 1 then
local obj = minetest.add_entity( staticdata.add_damage = quiver_xbows_def.add_damage
{ end
x = pos.x,
y = pos.y + 1.5,
z = pos.z
},
'x_bows:arrow_entity',
minetest.serialize(staticdata)
)
if not obj then ---sound
return itemstack local sound_name = 'x_bows_bow_shoot'
end if staticdata.is_critical_hit then
sound_name = 'x_bows_bow_shoot_crit'
end
local strength_multiplier = tflp meta:set_string('arrow_itemstack_string', '')
itemstack:set_name(bow_name)
if strength_multiplier > _tool_capabilities.full_punch_interval then local pos = user:get_pos()
strength_multiplier = 1 local dir = user:get_look_dir()
end local obj = minetest.add_entity(
{
x = pos.x,
y = pos.y + 1.5,
z = pos.z
},
'x_bows:arrow_entity',
minetest.serialize(staticdata)
)
local strength = 30 * strength_multiplier if not obj then
return itemstack
end
obj:set_velocity(vector.multiply(dir, strength)) local strength_multiplier = tflp
obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3})
obj:set_yaw(minetest.dir_to_yaw(dir))
if not x_bows.is_creative(user:get_player_name()) then if strength_multiplier > _tool_capabilities.full_punch_interval then
itemstack:add_wear(65535 / uses) strength_multiplier = 1
end
minetest.sound_play(sound_name, { ---faster arrow, only on full punch interval
gain = 0.3, if staticdata.faster_arrows_multiplier then
pos = user:get_pos(), strength_multiplier = strength_multiplier + (strength_multiplier / staticdata.faster_arrows_multiplier)
max_hear_distance = 10 end
}) end
return itemstack local strength = 30 * strength_multiplier
obj:set_velocity(vector.multiply(dir, strength))
obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3})
obj:set_yaw(minetest.dir_to_yaw(dir))
if not x_bows.is_creative(user:get_player_name()) then
itemstack:add_wear(65535 / uses)
end
minetest.sound_play(sound_name, {
gain = 0.3,
pos = user:get_pos(),
max_hear_distance = 10
})
return itemstack
end end
---Arrow particle
---@param pos Vector
---@param type 'arrow' | 'arrow_crit' | 'bubble' | 'arrow_tipped'
---@return number|nil
function x_bows.particle_effect(pos, type) function x_bows.particle_effect(pos, type)
if type == 'arrow' then if type == 'arrow' then
return minetest.add_particlespawner({ return minetest.add_particlespawner({
amount = 1, amount = 1,
time = 0.1, time = 0.1,
minpos = pos, minpos = pos,
maxpos = pos, maxpos = pos,
minexptime = 1, minexptime = 1,
maxexptime = 1, maxexptime = 1,
minsize = 2, minsize = 2,
maxsize = 2, maxsize = 2,
texture = 'x_bows_arrow_particle.png', texture = 'x_bows_arrow_particle.png',
animation = { animation = {
type = 'vertical_frames', type = 'vertical_frames',
aspect_w = 8, aspect_w = 8,
aspect_h = 8, aspect_h = 8,
length = 1, length = 1,
}, },
glow = 1 glow = 1
}) })
elseif type == 'arrow_crit' then elseif type == 'arrow_crit' then
return minetest.add_particlespawner({ return minetest.add_particlespawner({
amount = 3, amount = 3,
time = 0.1, time = 0.1,
minpos = pos, minpos = pos,
maxpos = pos, maxpos = pos,
minexptime = 0.5, minexptime = 0.5,
maxexptime = 0.5, maxexptime = 0.5,
minsize = 2, minsize = 2,
maxsize = 2, maxsize = 2,
texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127', texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127',
animation = { animation = {
type = 'vertical_frames', type = 'vertical_frames',
aspect_w = 8, aspect_w = 8,
aspect_h = 8, aspect_h = 8,
length = 1, length = 1,
}, },
glow = 1 glow = 1
}) })
elseif type == 'bubble' then elseif type == 'arrow_fast' then
return minetest.add_particlespawner({ return minetest.add_particlespawner({
amount = 1, amount = 3,
time = 1, time = 0.1,
minpos = pos, minpos = pos,
maxpos = pos, maxpos = pos,
minvel = {x=1, y=1, z=0}, minexptime = 0.5,
maxvel = {x=1, y=1, z=0}, maxexptime = 0.5,
minacc = {x=1, y=1, z=1}, minsize = 2,
maxacc = {x=1, y=1, z=1}, maxsize = 2,
minexptime = 0.2, texture = 'x_bows_arrow_particle.png^[colorize:#0000FF:64',
maxexptime = 0.5, animation = {
minsize = 0.5, type = 'vertical_frames',
maxsize = 1, aspect_w = 8,
texture = 'x_bows_bubble.png' aspect_h = 8,
}) length = 1,
elseif type == 'arrow_tipped' then },
return minetest.add_particlespawner({ glow = 1
amount = 5, })
time = 1, elseif type == 'bubble' then
minpos = vector.subtract(pos, 0.5), return minetest.add_particlespawner({
maxpos = vector.add(pos, 0.5), amount = 1,
minexptime = 0.4, time = 1,
maxexptime = 0.8, minpos = pos,
minvel = {x=-0.4, y=0.4, z=-0.4}, maxpos = pos,
maxvel = {x=0.4, y=0.6, z=0.4}, minvel = {x=1, y=1, z=0},
minacc = {x=0.2, y=0.4, z=0.2}, maxvel = {x=1, y=1, z=0},
maxacc = {x=0.4, y=0.6, z=0.4}, minacc = {x=1, y=1, z=1},
minsize = 4, maxacc = {x=1, y=1, z=1},
maxsize = 6, minexptime = 0.2,
texture = 'x_bows_arrow_tipped_particle.png^[colorize:#008000:127', maxexptime = 0.5,
animation = { minsize = 0.5,
type = 'vertical_frames', maxsize = 1,
aspect_w = 8, texture = 'x_bows_bubble.png'
aspect_h = 8, })
length = 1, elseif type == 'arrow_tipped' then
}, return minetest.add_particlespawner({
glow = 1 amount = 5,
}) time = 1,
end minpos = vector.subtract(pos, 0.5),
maxpos = vector.add(pos, 0.5),
minexptime = 0.4,
maxexptime = 0.8,
minvel = {x=-0.4, y=0.4, z=-0.4},
maxvel = {x=0.4, y=0.6, z=0.4},
minacc = {x=0.2, y=0.4, z=0.2},
maxacc = {x=0.4, y=0.6, z=0.4},
minsize = 4,
maxsize = 6,
texture = 'x_bows_arrow_tipped_particle.png^[colorize:#008000:127',
animation = {
type = 'vertical_frames',
aspect_w = 8,
aspect_h = 8,
length = 1,
},
glow = 1
})
end
end end
-- sneak, fov adjustments when bow is charged -- sneak, fov adjustments when bow is charged
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
bow_charged_timer = bow_charged_timer + dtime bow_charged_timer = bow_charged_timer + dtime
if bow_charged_timer > 0.5 then if bow_charged_timer > 0.5 then
for _, player in ipairs(minetest.get_connected_players()) do for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name() local name = player:get_player_name()
local stack = player:get_wielded_item() local stack = player:get_wielded_item()
local item = stack:get_name() local item = stack:get_name()
if not item then if not item then
return return
end end
if not x_bows.player_bow_sneak[name] then if not x_bows.player_bow_sneak[name] then
x_bows.player_bow_sneak[name] = {} x_bows.player_bow_sneak[name] = {}
end end
if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then
if x_bows.playerphysics then if x_bows.playerphysics then
playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25) playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25)
elseif x_bows.player_monoids then elseif x_bows.player_monoids then
player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_wood_charged') player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_wood_charged')
end end
x_bows.player_bow_sneak[name].sneak = true x_bows.player_bow_sneak[name].sneak = true
player:set_fov(0.9, true, 0.4) player:set_fov(0.9, true, 0.4)
elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then
if x_bows.playerphysics then if x_bows.playerphysics then
playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged') playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged')
elseif x_bows.player_monoids then elseif x_bows.player_monoids then
player_monoids.speed:del_change(player, 'x_bows:bow_wood_charged') player_monoids.speed:del_change(player, 'x_bows:bow_wood_charged')
end end
x_bows.player_bow_sneak[name].sneak = false x_bows.player_bow_sneak[name].sneak = false
player:set_fov(0, true, 0.4) player:set_fov(0, true, 0.4)
end end
reset_charged_bow(player) reset_charged_bow(player)
end end
bow_charged_timer = 0 bow_charged_timer = 0
end end
end) end)
local path = minetest.get_modpath('x_bows') local path = minetest.get_modpath('x_bows')
@ -437,6 +636,7 @@ local path = minetest.get_modpath('x_bows')
dofile(path .. '/nodes.lua') dofile(path .. '/nodes.lua')
dofile(path .. '/arrow.lua') dofile(path .. '/arrow.lua')
dofile(path .. '/items.lua') dofile(path .. '/items.lua')
dofile(path .. '/quiver.lua')
local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000 local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000

221
items.lua
View File

@ -1,130 +1,149 @@
x_bows.register_bow('bow_wood', { x_bows.register_bow('bow_wood', {
description = 'Wooden Bow', description = 'Wooden Bow',
uses = 385, uses = 385,
-- `crit_chance` 10% chance, 5 is 20% chance -- `crit_chance` 10% chance, 5 is 20% chance
-- (1 / crit_chance) * 100 = % chance -- (1 / crit_chance) * 100 = % chance
crit_chance = 10, crit_chance = 10,
recipe = { recipe = {
{'', 'default:stick', 'farming:string'}, {'', 'default:stick', 'farming:string'},
{'default:stick', '', 'farming:string'}, {'default:stick', '', 'farming:string'},
{'', 'default:stick', 'farming:string'}, {'', 'default:stick', 'farming:string'},
} }
}) })
x_bows.register_arrow('arrow_wood', { x_bows.register_arrow('arrow_wood', {
description = 'Arrow Wood', description = 'Arrow Wood',
inventory_image = 'x_bows_arrow_wood.png', inventory_image = 'x_bows_arrow_wood.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'group:stick'}, {'group:stick'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 1, full_punch_interval = 1,
max_drop_level = 0, max_drop_level = 0,
damage_groups = {fleshy=2} damage_groups = {fleshy=2}
} }
}) })
x_bows.register_arrow('arrow_stone', { x_bows.register_arrow('arrow_stone', {
description = 'Arrow Stone', description = 'Arrow Stone',
inventory_image = 'x_bows_arrow_stone.png', inventory_image = 'x_bows_arrow_stone.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'group:stone'}, {'group:stone'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 1.2, full_punch_interval = 1.2,
max_drop_level = 0, max_drop_level = 0,
damage_groups = {fleshy=4} damage_groups = {fleshy=4}
} }
}) })
x_bows.register_arrow('arrow_bronze', { x_bows.register_arrow('arrow_bronze', {
description = 'Arrow Bronze', description = 'Arrow Bronze',
inventory_image = 'x_bows_arrow_bronze.png', inventory_image = 'x_bows_arrow_bronze.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'default:bronze_ingot'}, {'default:bronze_ingot'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 0.8, full_punch_interval = 0.8,
max_drop_level = 1, max_drop_level = 1,
damage_groups = {fleshy=6} damage_groups = {fleshy=6}
} }
}) })
x_bows.register_arrow('arrow_steel', { x_bows.register_arrow('arrow_steel', {
description = 'Arrow Steel', description = 'Arrow Steel',
inventory_image = 'x_bows_arrow_steel.png', inventory_image = 'x_bows_arrow_steel.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'default:steel_ingot'}, {'default:steel_ingot'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 0.7, full_punch_interval = 0.7,
max_drop_level = 1, max_drop_level = 1,
damage_groups = {fleshy=6} damage_groups = {fleshy=6}
} }
}) })
x_bows.register_arrow('arrow_mese', { x_bows.register_arrow('arrow_mese', {
description = 'Arrow Mese', description = 'Arrow Mese',
inventory_image = 'x_bows_arrow_mese.png', inventory_image = 'x_bows_arrow_mese.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'default:mese_crystal'}, {'default:mese_crystal'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 0.7, full_punch_interval = 0.7,
max_drop_level = 1, max_drop_level = 1,
damage_groups = {fleshy=7} damage_groups = {fleshy=7}
} }
}) })
x_bows.register_arrow('arrow_diamond', { x_bows.register_arrow('arrow_diamond', {
description = 'Arrow Diamond', description = 'Arrow Diamond',
inventory_image = 'x_bows_arrow_diamond.png', inventory_image = 'x_bows_arrow_diamond.png',
craft = { craft = {
{'default:flint'}, {'default:flint'},
{'default:diamond'}, {'default:diamond'},
{'group:wool'} {'group:wool'}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 0.7, full_punch_interval = 0.7,
max_drop_level = 1, max_drop_level = 1,
damage_groups = {fleshy=8} damage_groups = {fleshy=8}
} }
}) })
x_bows.register_arrow('arrow_diamond_tipped_poison', { x_bows.register_arrow('arrow_diamond_tipped_poison', {
description = 'Arrow Diamond Tipped Poison (0:05)', description = 'Arrow Diamond Tipped Poison (0:05)',
inventory_image = 'x_bows_arrow_diamond_poison.png', inventory_image = 'x_bows_arrow_diamond_poison.png',
craft = { craft = {
{'', '', ''}, {'', '', ''},
{'', 'default:marram_grass_1', ''}, {'', 'default:marram_grass_1', ''},
{'', 'x_bows:arrow_diamond', ''} {'', 'x_bows:arrow_diamond', ''}
}, },
tool_capabilities = { tool_capabilities = {
full_punch_interval = 0.7, full_punch_interval = 0.7,
max_drop_level = 1, max_drop_level = 1,
damage_groups = {fleshy=8} damage_groups = {fleshy=8}
}, },
craft_count = 1 craft_count = 1
})
x_bows.register_quiver('quiver', {
description = 'Quiver \n\n Empty\n',
short_description = 'Quiver',
recipe = {
{'group:arrow', 'group:arrow', 'group:arrow'},
{'group:arrow', 'wool:brown', 'group:arrow'},
{'group:arrow', 'group:arrow', 'group:arrow'}
},
craft_count = 1,
faster_arrows = 5,
add_damage = 2
}) })
minetest.register_craft({ minetest.register_craft({
type = 'fuel', type = 'fuel',
recipe = 'x_bows:bow_wood', recipe = 'x_bows:bow_wood',
burntime = 3, burntime = 3,
}) })
minetest.register_craft({ minetest.register_craft({
type = 'fuel', type = 'fuel',
recipe = 'x_bows:arrow_wood', recipe = 'x_bows:arrow_wood',
burntime = 1, burntime = 1,
})
minetest.register_craft({
type = 'fuel',
recipe = 'x_bows:quiver',
burntime = 3,
}) })

View File

@ -1,6 +1,6 @@
name = x_bows name = x_bows
description = Adds bow and arrows to Minetest. description = Adds bow and arrows to Minetest.
depends = depends =
optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics, player_monoids optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics, player_monoids, wool
supported_games = minetest_game supported_games = minetest_game
min_minetest_version = 5.4 min_minetest_version = 5.4

582
models/x_bows_arrow.obj Normal file
View File

@ -0,0 +1,582 @@
# Blender v3.3.0 OBJ File: 'arrow2.blend'
# www.blender.org
mtllib arrow2.mtl
o arrow2
v -0.007500 -0.030000 -0.112500
v -0.007500 -0.015000 -0.180000
v -0.007500 -0.015000 -0.157500
v -0.007500 0.015000 0.180000
v -0.007500 0.015000 0.090000
v -0.022500 -0.000000 -0.112500
v -0.037500 -0.000000 -0.112500
v -0.037500 0.015000 -0.180000
v -0.037500 -0.000000 -0.180000
v 0.022500 0.015000 -0.157500
v -0.007500 0.045000 -0.112500
v 0.007500 -0.015000 -0.157500
v 0.007500 -0.015000 0.090000
v 0.007500 -0.015000 -0.112500
v 0.007500 -0.000000 0.180000
v 0.007500 -0.000000 0.157500
v 0.022500 -0.000000 0.157500
v 0.022500 -0.000000 0.090000
v 0.022500 -0.000000 -0.090000
v 0.022500 0.015000 -0.090000
v 0.037500 -0.000000 -0.112500
v 0.037500 0.015000 -0.112500
v -0.022500 0.015000 -0.157500
v 0.007500 -0.000000 -0.180000
v 0.007500 0.015000 -0.180000
v 0.037500 0.015000 -0.180000
v 0.007500 0.030000 0.157500
v 0.007500 0.030000 -0.157500
v 0.007500 -0.030000 -0.112500
v -0.007500 -0.015000 0.157500
v 0.007500 -0.015000 0.157500
v -0.007500 -0.015000 -0.090000
v 0.007500 -0.015000 -0.090000
v -0.022500 0.015000 0.157500
v 0.022500 0.015000 0.157500
v -0.022500 -0.000000 -0.090000
v -0.007500 -0.000000 -0.090000
v 0.007500 -0.000000 -0.090000
v -0.037500 0.015000 -0.112500
v 0.022500 0.015000 -0.112500
v -0.007500 0.015000 0.157500
v -0.007500 0.030000 0.157500
v -0.007500 0.030000 -0.090000
v -0.007500 0.030000 -0.112500
v 0.007500 0.030000 -0.112500
v 0.007500 -0.015000 -0.180000
v -0.007500 -0.000000 -0.157500
v 0.007500 -0.000000 -0.157500
v 0.007500 -0.000000 0.090000
v 0.022500 0.015000 0.090000
v -0.022500 -0.000000 -0.180000
v 0.022500 -0.000000 -0.180000
v 0.022500 0.015000 -0.180000
v 0.007500 0.030000 0.090000
v 0.007500 0.015000 0.090000
v -0.007500 0.015000 -0.157500
v 0.007500 0.045000 -0.180000
v -0.007500 -0.030000 -0.180000
v 0.007500 -0.030000 -0.180000
v -0.007500 -0.015000 0.090000
v -0.007500 -0.015000 -0.112500
v -0.007500 -0.000000 0.180000
v -0.007500 -0.000000 0.157500
v -0.022500 -0.000000 0.157500
v -0.022500 -0.000000 0.090000
v -0.007500 -0.000000 0.090000
v 0.022500 -0.000000 -0.112500
v -0.022500 -0.000000 -0.157500
v -0.007500 -0.000000 -0.180000
v 0.022500 -0.000000 -0.157500
v 0.037500 -0.000000 -0.180000
v -0.007500 0.030000 -0.157500
v -0.007500 0.030000 -0.180000
v 0.007500 0.030000 -0.180000
v 0.007500 0.015000 0.180000
v 0.007500 0.015000 0.157500
v -0.022500 0.015000 0.090000
v 0.007500 0.015000 -0.090000
v -0.007500 0.015000 -0.090000
v -0.022500 0.015000 -0.090000
v -0.022500 0.015000 -0.112500
v -0.022500 0.015000 -0.180000
v 0.007500 0.015000 -0.157500
v -0.007500 0.015000 -0.180000
v -0.007500 0.030000 0.090000
v 0.007500 0.030000 -0.090000
v 0.007500 0.045000 -0.112500
v -0.007500 0.045000 -0.180000
vt 0.533333 0.533333
vt 0.000000 0.600000
vt 0.533333 0.600000
vt 0.466667 0.866667
vt 0.533333 0.800000
vt 0.466667 0.800000
vt 0.533333 0.866667
vt 0.600000 0.800000
vt 0.533333 0.800000
vt 0.733333 0.400000
vt 0.800000 0.200000
vt 0.733333 0.200000
vt 0.400000 0.733333
vt 0.200000 0.800000
vt 0.400000 0.800000
vt 0.466667 0.666667
vt 0.400000 0.733333
vt 0.400000 0.666667
vt 0.733333 0.866667
vt 0.800000 0.800000
vt 0.733333 0.800000
vt 0.866667 0.333333
vt 0.933333 0.266667
vt 0.866667 0.266667
vt 0.066667 0.533333
vt 0.133333 0.000000
vt 0.066667 0.000000
vt 0.200000 0.466667
vt 0.200000 0.533333
vt 0.133333 0.533333
vt 0.666667 0.866667
vt 0.600000 0.933333
vt 0.600000 0.866667
vt 0.800000 0.333333
vt 0.866667 0.266667
vt 0.800000 0.266667
vt 0.200000 0.866667
vt 0.000000 0.800000
vt 0.000000 0.866667
vt 0.866667 0.800000
vt 0.800000 0.866667
vt 0.800000 0.800000
vt 0.200000 0.733333
vt 0.000000 0.800000
vt 0.200000 0.800000
vt 0.600000 0.733333
vt 0.400000 0.800000
vt 0.400000 0.733333
vt 0.666667 0.866667
vt 0.733333 0.800000
vt 0.666667 0.800000
vt 0.733333 0.200000
vt 0.666667 0.266667
vt 0.666667 0.200000
vt 0.733333 0.200000
vt 0.666667 0.000000
vt 0.733333 0.000000
vt 0.733333 0.666667
vt 0.666667 0.733333
vt 0.666667 0.666667
vt 0.466667 0.333333
vt 0.466667 0.400000
vt 0.333333 0.333333
vt 0.733333 0.200000
vt 0.800000 0.000000
vt 0.800000 0.200000
vt 0.533333 0.733333
vt 0.600000 0.533333
vt 0.533333 0.533333
vt 0.466667 0.866667
vt 0.400000 0.933333
vt 0.400000 0.866667
vt 0.666667 0.266667
vt 0.600000 0.266667
vt 0.600000 0.200000
vt 0.333333 0.066667
vt 0.400000 0.066667
vt 0.400000 0.133333
vt 0.200000 0.866667
vt 0.133333 0.933333
vt 0.133333 0.866667
vt 0.000000 0.533333
vt 0.066667 0.000000
vt 0.066667 0.533333
vt 0.400000 0.866667
vt 0.466667 0.800000
vt 0.400000 0.800000
vt 0.466667 0.200000
vt 0.533333 0.200000
vt 0.466667 0.066667
vt 0.266667 0.866667
vt 0.200000 0.933333
vt 0.200000 0.866667
vt 0.866667 0.400000
vt 0.933333 0.333333
vt 0.866667 0.333333
vt 0.400000 0.866667
vt 0.200000 0.800000
vt 0.400000 0.800000
vt 0.333333 0.933333
vt 0.400000 0.866667
vt 0.333333 0.866667
vt 0.066667 0.866667
vt 0.000000 0.933333
vt 0.000000 0.866667
vt 0.800000 0.400000
vt 0.733333 0.466667
vt 0.733333 0.400000
vt 0.733333 0.466667
vt 0.666667 0.266667
vt 0.733333 0.266667
vt 0.200000 0.733333
vt 0.000000 0.666667
vt 0.200000 0.666667
vt 0.866667 0.333333
vt 0.800000 0.400000
vt 0.800000 0.333333
vt 0.333333 0.866667
vt 0.266667 0.933333
vt 0.266667 0.866667
vt 0.533333 0.600000
vt 0.000000 0.666667
vt 0.000000 0.600000
vt 0.866667 0.533333
vt 0.800000 0.600000
vt 0.800000 0.533333
vt 0.600000 0.466667
vt 0.600000 0.533333
vt 0.666667 0.533333
vt 0.466667 0.933333
vt 0.533333 0.866667
vt 0.466667 0.866667
vt 0.666667 0.666667
vt 0.733333 0.466667
vt 0.666667 0.466667
vt 0.866667 0.200000
vt 0.933333 0.133333
vt 0.866667 0.133333
vt 0.800000 0.666667
vt 0.733333 0.733333
vt 0.733333 0.666667
vt 0.733333 0.666667
vt 0.800000 0.466667
vt 0.800000 0.666667
vt 0.866667 0.000000
vt 0.800000 0.066667
vt 0.800000 0.000000
vt 0.800000 0.733333
vt 0.600000 0.800000
vt 0.800000 0.800000
vt 0.933333 0.000000
vt 0.866667 0.066667
vt 0.866667 0.000000
vt 0.866667 0.200000
vt 0.800000 0.200000
vt 0.400000 0.733333
vt 0.200000 0.666667
vt 0.400000 0.666667
vt 0.800000 0.200000
vt 0.866667 0.133333
vt 0.800000 0.133333
vt 0.133333 0.866667
vt 0.066667 0.933333
vt 0.066667 0.866667
vt 0.800000 0.133333
vt 0.866667 0.066667
vt 0.800000 0.066667
vt 0.800000 0.466667
vt 0.866667 0.400000
vt 0.800000 0.400000
vt 0.133333 0.133333
vt 0.133333 0.200000
vt 0.200000 0.200000
vt 0.466667 0.466667
vt 0.533333 0.466667
vt 0.533333 0.400000
vt 0.666667 0.733333
vt 0.600000 0.533333
vt 0.600000 0.733333
vt 0.800000 0.533333
vt 0.866667 0.466667
vt 0.800000 0.466667
vt 0.866667 0.733333
vt 0.800000 0.800000
vt 0.800000 0.733333
vt 0.933333 0.066667
vt 0.866667 0.133333
vt 0.866667 0.066667
vt 0.533333 0.933333
vt 0.600000 0.866667
vt 0.533333 0.866667
vt 0.800000 0.733333
vt 0.866667 0.666667
vt 0.800000 0.666667
vt 0.800000 0.666667
vt 0.866667 0.600000
vt 0.800000 0.600000
vt 0.600000 0.866667
vt 0.666667 0.800000
vt 0.600000 0.800000
vt 0.866667 0.266667
vt 0.933333 0.200000
vt 0.866667 0.200000
vt 0.933333 0.400000
vt 0.866667 0.466667
vt 0.866667 0.400000
vt 0.533333 0.666667
vt 0.466667 0.733333
vt 0.466667 0.666667
vt 0.000000 0.533333
vt 0.533333 0.866667
vt 0.600000 0.866667
vt 0.800000 0.400000
vt 0.200000 0.733333
vt 0.466667 0.733333
vt 0.800000 0.866667
vt 0.933333 0.333333
vt 0.133333 0.533333
vt 0.133333 0.333333
vt 0.200000 0.333333
vt 0.200000 0.266667
vt 0.266667 0.266667
vt 0.266667 0.466667
vt 0.666667 0.933333
vt 0.866667 0.333333
vt 0.200000 0.800000
vt 0.866667 0.866667
vt 0.000000 0.733333
vt 0.600000 0.800000
vt 0.733333 0.866667
vt 0.733333 0.266667
vt 0.666667 0.200000
vt 0.733333 0.733333
vt 0.266667 0.400000
vt 0.266667 0.333333
vt 0.333333 0.266667
vt 0.533333 0.266667
vt 0.533333 0.333333
vt 0.733333 0.000000
vt 0.600000 0.733333
vt 0.466667 0.933333
vt 0.533333 0.200000
vt 0.600000 0.066667
vt 0.533333 0.000000
vt 0.600000 0.000000
vt 0.666667 0.066667
vt 0.200000 0.133333
vt 0.200000 0.066667
vt 0.133333 0.066667
vt 0.133333 0.000000
vt 0.333333 0.000000
vt 0.200000 0.933333
vt 0.000000 0.000000
vt 0.466667 0.866667
vt 0.533333 0.000000
vt 0.466667 0.000000
vt 0.400000 0.066667
vt 0.400000 0.266667
vt 0.466667 0.266667
vt 0.266667 0.933333
vt 0.933333 0.400000
vt 0.200000 0.866667
vt 0.400000 0.933333
vt 0.066667 0.933333
vt 0.800000 0.466667
vt 0.666667 0.466667
vt 0.000000 0.733333
vt 0.866667 0.400000
vt 0.333333 0.933333
vt 0.533333 0.666667
vt 0.866667 0.600000
vt 0.666667 0.333333
vt 0.600000 0.333333
vt 0.600000 0.266667
vt 0.533333 0.266667
vt 0.533333 0.466667
vt 0.533333 0.933333
vt 0.733333 0.666667
vt 0.933333 0.200000
vt 0.800000 0.733333
vt 0.733333 0.466667
vt 0.866667 0.066667
vt 0.600000 0.733333
vt 0.933333 0.066667
vt 0.200000 0.733333
vt 0.866667 0.200000
vt 0.133333 0.933333
vt 0.866667 0.133333
vt 0.866667 0.466667
vt 0.200000 0.266667
vt 0.333333 0.200000
vt 0.400000 0.266667
vt 0.400000 0.200000
vt 0.333333 0.133333
vt 0.333333 0.400000
vt 0.333333 0.466667
vt 0.266667 0.466667
vt 0.266667 0.533333
vt 0.466667 0.533333
vt 0.666667 0.533333
vt 0.866667 0.533333
vt 0.866667 0.800000
vt 0.933333 0.133333
vt 0.600000 0.933333
vt 0.866667 0.733333
vt 0.866667 0.666667
vt 0.666667 0.866667
vt 0.933333 0.266667
vt 0.933333 0.466667
vt 0.533333 0.733333
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 0.0000 -1.0000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn 1.0000 0.0000 0.0000
usemtl arrow2_Material
s off
f 37/1/1 5/2/1 79/3/1
f 63/4/2 15/5/2 62/6/2
f 85/7/3 55/8/3 5/9/3
f 49/10/2 17/11/2 16/12/2
f 60/13/1 63/14/1 66/15/1
f 86/16/4 44/17/4 43/18/4
f 56/19/1 69/20/1 47/21/1
f 53/22/3 71/23/3 52/24/3
f 37/25/2 49/26/2 66/27/2
f 23/28/4 82/29/4 8/30/4
f 17/31/5 76/32/5 16/33/5
f 69/34/2 48/35/2 47/36/2
f 8/37/1 7/38/1 39/39/1
f 67/40/6 20/41/6 19/42/6
f 5/43/1 42/44/1 85/45/1
f 18/46/6 35/47/6 17/48/6
f 80/49/1 6/50/1 36/51/1
f 33/52/5 37/53/5 32/54/5
f 54/55/4 42/56/4 27/57/4
f 12/58/4 2/59/4 3/60/4
f 12/61/6 48/62/6 14/63/6
f 88/64/4 87/65/4 57/66/4
f 60/67/2 31/68/2 30/69/2
f 6/70/5 39/71/5 7/72/5
f 26/73/4 53/74/4 10/75/4
f 28/76/6 74/77/6 57/78/6
f 29/79/5 61/80/5 1/81/5
f 79/82/4 55/83/4 78/84/4
f 72/85/3 83/86/3 56/87/3
f 68/88/2 47/89/2 6/90/2
f 63/91/5 34/92/5 64/93/5
f 8/94/3 51/95/3 9/96/3
f 26/97/6 21/98/6 71/99/6
f 23/100/3 47/101/3 68/102/3
f 51/103/6 23/104/6 68/105/6
f 76/106/5 42/107/5 41/108/5
f 5/109/4 34/110/4 41/111/4
f 49/112/6 31/113/6 13/114/6
f 83/115/4 84/116/4 56/117/4
f 21/118/5 40/119/5 67/120/5
f 38/121/6 55/122/6 49/123/6
f 31/124/5 63/125/5 30/126/5
f 70/127/2 52/128/2 71/129/2
f 83/130/3 70/131/3 48/132/3
f 65/133/2 63/134/2 64/135/2
f 2/136/3 59/137/3 58/138/3
f 78/139/5 43/140/5 79/141/5
f 55/142/4 35/143/4 50/144/4
f 45/145/5 11/146/5 44/147/5
f 65/148/1 34/149/1 77/150/1
f 15/151/5 4/152/5 62/153/5
f 47/36/3 12/154/3 3/155/3
f 54/156/6 76/157/6 55/158/6
f 66/159/3 13/160/3 60/161/3
f 24/162/6 83/163/6 48/164/6
f 73/165/2 28/166/2 72/167/2
f 61/168/2 33/169/2 32/170/2
f 79/171/1 43/172/1 44/173/1
f 3/174/1 2/175/1 58/176/1
f 59/177/2 1/178/2 58/179/2
f 88/180/3 74/181/3 73/182/3
f 16/183/6 75/184/6 15/185/6
f 37/186/5 80/187/5 36/188/5
f 55/189/3 18/190/3 49/191/3
f 10/192/1 52/193/1 70/194/1
f 4/195/1 63/196/1 62/197/1
f 84/198/3 24/199/3 69/200/3
f 77/201/3 66/202/3 65/203/3
f 19/204/5 78/205/5 38/206/5
f 75/207/4 41/208/4 4/209/4
f 37/1/1 66/210/1 5/2/1
f 63/4/2 16/211/2 15/5/2
f 85/7/3 54/212/3 55/8/3
f 49/10/2 18/213/2 17/11/2
f 60/13/1 30/214/1 63/14/1
f 86/16/4 45/215/4 44/17/4
f 56/19/1 84/216/1 69/20/1
f 53/22/3 26/217/3 71/23/3
f 37/25/2 38/218/2 49/26/2
f 8/30/4 39/219/4 23/28/4
f 39/219/4 81/220/4 23/28/4
f 81/220/4 80/221/4 79/222/4
f 79/222/4 56/223/4 81/220/4
f 56/223/4 23/28/4 81/220/4
f 17/31/5 35/224/5 76/32/5
f 69/34/2 24/225/2 48/35/2
f 8/37/1 9/226/1 7/38/1
f 67/40/6 40/227/6 20/41/6
f 5/43/1 41/228/1 42/44/1
f 18/46/6 50/229/6 35/47/6
f 80/49/1 81/230/1 6/50/1
f 33/52/5 38/231/5 37/53/5
f 54/55/4 85/232/4 42/56/4
f 12/58/4 46/233/4 2/59/4
f 48/62/6 38/234/6 14/63/6
f 38/234/6 33/235/6 14/63/6
f 14/63/6 29/236/6 12/61/6
f 29/236/6 59/237/6 12/61/6
f 59/237/6 46/238/6 12/61/6
f 88/64/4 11/239/4 87/65/4
f 60/67/2 13/240/2 31/68/2
f 6/70/5 81/241/5 39/71/5
f 10/75/4 83/242/4 40/243/4
f 83/242/4 78/244/4 40/243/4
f 78/244/4 20/245/4 40/243/4
f 40/243/4 22/246/4 10/75/4
f 22/246/4 26/73/4 10/75/4
f 57/78/6 87/247/6 28/76/6
f 87/247/6 45/248/6 28/76/6
f 45/248/6 86/249/6 78/250/6
f 78/250/6 83/251/6 45/248/6
f 83/251/6 28/76/6 45/248/6
f 29/79/5 14/252/5 61/80/5
f 79/82/4 5/253/4 55/83/4
f 72/85/3 28/254/3 83/86/3
f 47/89/2 37/255/2 6/90/2
f 37/255/2 36/256/2 6/90/2
f 6/90/2 7/257/2 68/88/2
f 7/257/2 9/258/2 68/88/2
f 9/258/2 51/259/2 68/88/2
f 63/91/5 41/260/5 34/92/5
f 8/94/3 82/261/3 51/95/3
f 26/97/6 22/262/6 21/98/6
f 23/100/3 56/263/3 47/101/3
f 51/103/6 82/264/6 23/104/6
f 76/106/5 27/265/5 42/107/5
f 5/109/4 77/266/4 34/110/4
f 49/112/6 16/267/6 31/113/6
f 83/115/4 25/268/4 84/116/4
f 21/118/5 22/269/5 40/119/5
f 38/121/6 78/270/6 55/122/6
f 31/124/5 16/271/5 63/125/5
f 71/129/2 21/272/2 70/127/2
f 21/272/2 67/273/2 70/127/2
f 67/273/2 19/274/2 38/275/2
f 38/275/2 48/276/2 67/273/2
f 48/276/2 70/127/2 67/273/2
f 83/130/3 10/277/3 70/131/3
f 65/133/2 66/278/2 63/134/2
f 2/136/3 46/279/3 59/137/3
f 78/139/5 86/280/5 43/140/5
f 55/142/4 76/281/4 35/143/4
f 45/145/5 87/282/5 11/146/5
f 65/148/1 64/283/1 34/149/1
f 15/151/5 75/284/5 4/152/5
f 47/36/3 48/35/3 12/154/3
f 54/156/6 27/285/6 76/157/6
f 66/159/3 49/286/3 13/160/3
f 24/162/6 25/287/6 83/163/6
f 73/165/2 74/288/2 28/166/2
f 61/168/2 14/289/2 33/169/2
f 44/173/1 11/290/1 72/291/1
f 11/290/1 88/292/1 72/291/1
f 88/292/1 73/293/1 72/291/1
f 72/291/1 56/294/1 44/173/1
f 56/294/1 79/171/1 44/173/1
f 58/176/1 1/295/1 3/174/1
f 1/295/1 61/296/1 3/174/1
f 61/296/1 32/297/1 37/298/1
f 37/298/1 47/299/1 61/296/1
f 47/299/1 3/174/1 61/296/1
f 59/177/2 29/300/2 1/178/2
f 88/180/3 57/301/3 74/181/3
f 16/183/6 76/302/6 75/184/6
f 37/186/5 79/303/5 80/187/5
f 55/189/3 50/304/3 18/190/3
f 10/192/1 53/305/1 52/193/1
f 4/195/1 41/306/1 63/196/1
f 84/198/3 25/307/3 24/199/3
f 77/201/3 5/308/3 66/202/3
f 19/204/5 20/309/5 78/205/5
f 75/207/4 76/310/4 41/208/4

View File

@ -1,54 +1,38 @@
minetest.register_node('x_bows:arrow_node', { minetest.register_node('x_bows:arrow_node', {
drawtype = 'nodebox', drawtype = 'mesh',
node_box = { mesh = 'x_bows_arrow.obj',
type = 'fixed', tiles = {'x_bows_arrow_mesh.png'},
fixed = { groups = {not_in_creative_inventory=1},
{-0.1875, 0, -0.5, 0.1875, 0, 0.5}, sunlight_propagates = true,
{0, -0.1875, -0.5, 0, 0.1875, 0.5}, paramtype = 'light',
{-0.5, -0.5, -0.5, 0.5, 0.5, -0.5}, collision_box = {0, 0, 0, 0, 0, 0},
} selection_box = {0, 0, 0, 0, 0, 0}
},
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z
-- Textures of node; top, bottom, right, left, front, back
tiles = {
'x_bows_arrow_tile_point_top.png',
'x_bows_arrow_tile_point_bottom.png',
'x_bows_arrow_tile_point_right.png',
'x_bows_arrow_tile_point_left.png',
'x_bows_arrow_tile_tail.png',
'x_bows_arrow_tile_tail.png'
},
groups = {not_in_creative_inventory=1},
sunlight_propagates = true,
paramtype = 'light',
collision_box = {0, 0, 0, 0, 0, 0},
selection_box = {0, 0, 0, 0, 0, 0}
}) })
minetest.register_node('x_bows:target', { minetest.register_node('x_bows:target', {
description = 'Straw', description = 'Straw',
tiles = {'x_bows_target.png'}, tiles = {'x_bows_target.png'},
is_ground_content = false, is_ground_content = false,
groups = {snappy=3, flammable=4, fall_damage_add_percent=-30}, groups = {snappy=3, flammable=4, fall_damage_add_percent=-30},
sounds = default.node_sound_leaves_defaults(), sounds = default.node_sound_leaves_defaults(),
mesecons = {receptor = {state = 'off'}}, mesecons = {receptor = {state = 'off'}},
on_timer = function (pos, elapsed) on_timer = function (pos, elapsed)
mesecon.receptor_off(pos) mesecon.receptor_off(pos)
return false return false
end, end,
}) })
minetest.register_craft({ minetest.register_craft({
type = 'fuel', type = 'fuel',
recipe = 'x_bows:target', recipe = 'x_bows:target',
burntime = 3, burntime = 3,
}) })
minetest.register_craft({ minetest.register_craft({
output = 'x_bows:target', output = 'x_bows:target',
recipe = { recipe = {
{'', 'default:mese_crystal', ''}, {'', 'default:mese_crystal', ''},
{'default:mese_crystal', 'farming:straw', 'default:mese_crystal'}, {'default:mese_crystal', 'farming:straw', 'default:mese_crystal'},
{'', 'default:mese_crystal', ''}, {'', 'default:mese_crystal', ''},
} }
}) })

515
quiver.lua Normal file
View File

@ -0,0 +1,515 @@
---Close one or all open quivers in players inventory
---@param player ObjectRef
---@param quiver_id? string If `nil` then all open quivers will be closed
---@returns nil
function x_bows.quiver.close_quiver(player, quiver_id)
local player_inv = player:get_inventory()
---find matching quiver item in players inventory with the open formspec name
if player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then
local inv_list = player_inv:get_list('main')
for i, st in ipairs(inv_list) do
local st_meta = st:get_meta()
if not st:is_empty() and st:get_name() == 'x_bows:quiver_open' then
if quiver_id and st_meta:get_string('quiver_id') == quiver_id then
local replace_item = x_bows.quiver.get_replacement_item(st, 'x_bows:quiver')
player_inv:set_stack('main', i, replace_item)
break
else
local replace_item = x_bows.quiver.get_replacement_item(st, 'x_bows:quiver')
player_inv:set_stack('main', i, replace_item)
end
end
end
end
end
---Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta.
---@param from_stack ItemStack transfer data from this item
---@param to_item_name string transfer data to this item
---@return ItemStack ItemStack replacement item
function x_bows.quiver.get_replacement_item(from_stack, to_item_name)
---@type ItemStack
local replace_item = ItemStack({
name = to_item_name,
count = from_stack:get_count(),
wear = from_stack:get_wear()
})
local replace_item_meta = replace_item:get_meta()
local from_stack_meta = from_stack:get_meta()
replace_item_meta:set_string('quiver_items', from_stack_meta:get_string('quiver_items'))
replace_item_meta:set_string('quiver_id', from_stack_meta:get_string('quiver_id'))
replace_item_meta:set_string('description', from_stack_meta:get_string('description'))
return replace_item
end
---Gets arrow from quiver
---@param player ObjectRef
---@return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil}
function x_bows.quiver.get_itemstack_arrow_from_quiver(player)
local player_inv = player:get_inventory()
---@type ItemStack|nil
local found_arrow_stack
local prev_detached_inv_list = {}
local quiver_id
local quiver_name
---find matching quiver item in players inventory with the open formspec name
if player_inv and player_inv:contains_item('main', 'x_bows:quiver') then
local inv_list = player_inv:get_list('main')
for i, st in ipairs(inv_list) do
if not st:is_empty() and st:get_name() == 'x_bows:quiver' then
local st_meta = st:get_meta()
local player_name = player:get_player_name()
quiver_id = st_meta:get_string('quiver_id')
local detached_inv = x_bows.quiver.get_or_create_detached_inv(
quiver_id,
player_name,
st_meta:get_string('quiver_items')
)
if not detached_inv:is_empty('main') then
local detached_inv_list = detached_inv:get_list('main')
---find arrows inside quiver inventory
for j, qst in ipairs(detached_inv_list) do
---save copy of inv list before we take the item
table.insert(prev_detached_inv_list, detached_inv:get_stack('main', j))
if not qst:is_empty() and not found_arrow_stack then
quiver_name = st:get_name()
found_arrow_stack = qst:take_item()
if not x_bows.is_creative(player_name) then
detached_inv:set_list('main', detached_inv_list)
x_bows.quiver.save(detached_inv, player, true)
end
end
end
end
end
if found_arrow_stack then
---show HUD - quiver inventory
x_bows.quiver.udate_or_create_hud(player, prev_detached_inv_list)
break
end
end
end
return {
found_arrow_stack = found_arrow_stack,
quiver_id = quiver_id,
quiver_name = quiver_name
}
end
---Remove all added HUDs
---@param player ObjectRef
function x_bows.quiver.remove_hud(player)
local player_name = player:get_player_name()
if x_bows.quiver.hud_item_ids[player_name] then
for _, v in pairs(x_bows.quiver.hud_item_ids[player_name]) do
if type(v) == 'table' then
for _, v2 in pairs(v) do
player:hud_remove(v2)
end
else
player:hud_remove(v)
end
end
x_bows.quiver.hud_item_ids[player_name] = {
arrow_inv_img = {},
stack_count = {}
}
else
x_bows.quiver.hud_item_ids[player_name] = {
arrow_inv_img = {},
stack_count = {}
}
end
end
---Update or create HUD
---@todo implement hud_change?
---@param player ObjectRef
---@param inv_list ItemStack[]
---@return nil
function x_bows.quiver.udate_or_create_hud(player, inv_list)
local player_name = player:get_player_name()
local selected_bg_added = false
if x_bows.quiver.after_job[player_name] then
for _, v in pairs(x_bows.quiver.after_job[player_name]) do
v:cancel()
end
x_bows.quiver.after_job[player_name] = {}
else
x_bows.quiver.after_job[player_name] = {}
end
x_bows.quiver.remove_hud(player)
---title image
x_bows.quiver.hud_item_ids[player_name].title_image = player:hud_add({
hud_elem_type = 'image',
position = {x = 1, y = 0.5},
offset = {x = -120, y = -140},
text = 'x_bows_quiver.png',
scale = {x = 4, y = 4},
alignment = 0,
})
---title copy
local quiver_def = minetest.registered_items['x_bows:quiver']
x_bows.quiver.hud_item_ids[player_name].title_copy = player:hud_add({
hud_elem_type = 'text',
position = {x = 1, y = 0.5},
offset = {x = -120, y = -75},
text = quiver_def.short_description,
alignment = 0,
scale = {x = 100, y = 30},
number = 0xFFFFFF,
})
---hotbar bg
x_bows.quiver.hud_item_ids[player_name].hotbar_bg = player:hud_add({
hud_elem_type = 'image',
position = {x = 1, y = 0.5},
offset = {x = -238, y = 0},
text = 'x_bows_quiver_hotbar.png',
scale = {x = 1, y = 1},
alignment = {x = 1, y = 0 },
})
for j, qst in ipairs(inv_list) do
if not qst:is_empty() then
local found_arrow_stack_def = minetest.registered_items[qst:get_name()]
if not selected_bg_added then
selected_bg_added = true
---ui selected bg
x_bows.quiver.hud_item_ids[player_name].hotbar_selected = player:hud_add({
hud_elem_type = 'image',
position = {x = 1, y = 0.5},
offset = {x = -308 + (j * 74), y = 2},
text = 'x_bows_quiver_hotbar_selected.png',
scale = {x = 1, y = 1},
alignment = {x = 1, y = 0 },
})
end
if found_arrow_stack_def then
---arrow inventory image
table.insert(x_bows.quiver.hud_item_ids[player_name].arrow_inv_img, player:hud_add({
hud_elem_type = 'image',
position = {x = 1, y = 0.5},
offset = {x = -300 + (j * 74), y = 0},
text = found_arrow_stack_def.inventory_image,
scale = {x = 4, y = 4},
alignment = {x = 1, y = 0 },
}))
---stack count
table.insert(x_bows.quiver.hud_item_ids[player_name].stack_count, player:hud_add({
hud_elem_type = 'text',
position = {x = 1, y = 0.5},
offset = {x = -244 + (j * 74), y = 23},
text = qst:get_count(),
alignment = -1,
scale = {x = 50, y = 10},
number = 0xFFFFFF,
}))
end
end
end
---@param v_player ObjectRef
table.insert(x_bows.quiver.after_job[player_name], minetest.after(10, function(v_player)
x_bows.quiver.remove_hud(v_player)
end, player))
end
---Get existing detached inventory or create new one
---@param quiver_id string
---@param player_name string
---@param quiver_items? string
---@return InvRef
function x_bows.quiver.get_or_create_detached_inv(quiver_id, player_name, quiver_items)
local detached_inv
if quiver_id ~= '' then
detached_inv = minetest.get_inventory({type='detached', name=quiver_id})
end
if not detached_inv then
detached_inv = minetest.create_detached_inventory(quiver_id, {
---@param inv InvRef detached inventory
---@param from_list string
---@param from_index number
---@param to_list string
---@param to_index number
---@param count number
---@param player ObjectRef
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
if x_bows.quiver.quiver_can_allow(inv, player) then
return count
else
return 0
end
end,
---@param inv InvRef detached inventory
---@param listname string listname of the inventory, e.g. `'main'`
---@param index number
---@param stack ItemStack
---@param player ObjectRef
allow_put = function(inv, listname, index, stack, player)
if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 and x_bows.quiver.quiver_can_allow(inv, player) then
return stack:get_count()
else
return 0
end
end,
---@param inv InvRef detached inventory
---@param listname string listname of the inventory, e.g. `'main'`
---@param index number
---@param stack ItemStack
---@param player ObjectRef
allow_take = function(inv, listname, index, stack, player)
if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 and x_bows.quiver.quiver_can_allow(inv, player) then
return stack:get_count()
else
return 0
end
end,
---@param inv InvRef detached inventory
---@param from_list string
---@param from_index number
---@param to_list string
---@param to_index number
---@param count number
---@param player ObjectRef
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
x_bows.quiver.save(inv, player)
end,
---@param inv InvRef detached inventory
---@param listname string listname of the inventory, e.g. `'main'`
---@param index number index where was item put
---@param stack ItemStack stack of item what was put
---@param player ObjectRef
on_put = function(inv, listname, index, stack, player)
x_bows.quiver.save(inv, player)
end,
---@param inv InvRef detached inventory
---@param listname string listname of the inventory, e.g. `'main'`
---@param index number
---@param stack ItemStack
---@param player ObjectRef
on_take = function(inv, listname, index, stack, player)
x_bows.quiver.save(inv, player)
end,
}, player_name)
detached_inv:set_size('main', 3 * 1)
end
---populate items in inventory
if quiver_items and quiver_items ~= '' then
x_bows.quiver.set_string_to_inv(detached_inv, quiver_items)
end
return detached_inv
end
---create UUID
---@return string
local function uuid()
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
---@diagnostic disable-next-line: redundant-return-value
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
return string.format('%x', v)
end)
end
---create formspec
---@param name string name of the form
---@return string
local function get_formspec(name)
local width = 3
local height = 1
local list_w = 8
local list_pos_x = (list_w - width) / 2
local formspec =
'size['..list_w..',6]' ..
'list[detached:'..name..';main;'..list_pos_x..',0.3;'..width..',1;]'..
'list[current_player;main;0,'..(height + 0.85)..';'..list_w..',1;]'..
'list[current_player;main;0,'..(height + 2.08)..';'..list_w..',3;8]'..
'listring[detached:'..name..';main]'..
'listring[current_player;main]'..
default.get_hotbar_bg(0, height + 0.85)
--update formspec
local inv = minetest.get_inventory({type='detached', name=name})
local invlist = inv:get_list(name)
---inventory slots overlay
local px, py = list_pos_x, 0.3
for i = 1, 3 do
if not invlist or invlist[i]:is_empty() then
formspec = formspec ..
'image[' .. px .. ',' .. py .. ';1,1;x_bows_arrow_slot.png]'
end
px = px + 1
end
return formspec
end
---convert inventory of itemstacks to serialized string
---@param inv InvRef
---@return {['inv_string']: string, ['content_description']: string}
local function get_string_from_inv(inv)
local inv_list = inv:get_list('main')
local t = {}
local content_description = ''
for i, st in ipairs(inv_list) do
if not st:is_empty() then
table.insert(t, st:to_table())
content_description = content_description .. '\n' ..st:get_short_description()..' '..st:get_count()
else
table.insert(t, {is_empty = true})
end
end
return {
inv_string = minetest.serialize(t),
content_description = content_description == '' and '\nEmpty' or content_description
}
end
---set items from serialized string to inventory
---@param inv InvRef inventory to add items to
---@param str string previously stringified inventory of itemstacks
function x_bows.quiver.set_string_to_inv(inv, str)
local t = minetest.deserialize(str)
for i, item in ipairs(t) do
if not item.is_empty then
inv:set_stack('main', i, ItemStack(item))
end
end
end
---save quiver inventory to itemstack meta
---@param inv InvRef
---@param player ObjectRef
---@param quiver_is_closed? boolean default `false`
function x_bows.quiver.save(inv, player, quiver_is_closed)
local player_inv = player:get_inventory()
local inv_loc = inv:get_location()
local quiver_item_name = quiver_is_closed and 'x_bows:quiver' or 'x_bows:quiver_open'
---find matching quiver item in players inventory with the open formspec name
if player_inv and player_inv:contains_item('main', quiver_item_name) then
local inv_list = player_inv:get_list('main')
for i, st in ipairs(inv_list) do
local st_meta = st:get_meta()
if not st:is_empty() and st:get_name() == quiver_item_name and st_meta:get_string('quiver_id') == inv_loc.name then
---save inventory items in quiver item meta
local string_from_inventory_result = get_string_from_inv(inv)
st_meta:set_string('quiver_items', string_from_inventory_result.inv_string)
---update description
local new_description = st:get_short_description()..'\n'..string_from_inventory_result.content_description..'\n'
st_meta:set_string('description', new_description)
player_inv:set_stack('main', i, st)
break
end
end
end
end
---check if we are allowing actions in the correct quiver inventory
---@param inv InvRef
---@param player ObjectRef
---@return boolean
function x_bows.quiver.quiver_can_allow(inv, player)
local player_inv = player:get_inventory()
local inv_loc = inv:get_location()
---find matching quiver item in players inventory with the open formspec name
if player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then
local inv_list = player_inv:get_list('main')
for i, st in ipairs(inv_list) do
local st_meta = st:get_meta()
if not st:is_empty() and st:get_name() == 'x_bows:quiver_open' and st_meta:get_string('quiver_id') == inv_loc.name then
return true
end
end
end
return false
end
---Open quiver
---@param itemstack ItemStack
---@param user ObjectRef
---@return ItemStack
function x_bows.open_quiver(itemstack, user)
local itemstack_meta = itemstack:get_meta()
local pname = user:get_player_name()
local quiver_id = itemstack_meta:get_string('quiver_id')
---create inventory id and save it
if quiver_id == '' then
quiver_id = itemstack:get_name()..'_'..uuid()
itemstack_meta:set_string('quiver_id', quiver_id)
end
local quiver_items = itemstack_meta:get_string('quiver_items')
x_bows.quiver.get_or_create_detached_inv(quiver_id, pname, quiver_items)
---show open variation of quiver
local replace_item = x_bows.quiver.get_replacement_item(itemstack, 'x_bows:quiver_open')
itemstack:replace(replace_item)
minetest.sound_play('x_bows_quiver', {
to_player = user:get_player_name(),
gain = 0.1
})
minetest.show_formspec(pname, quiver_id, get_formspec(quiver_id))
return itemstack
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if player and fields.quit then
x_bows.quiver.close_quiver(player, formname)
end
end)

BIN
screenshot.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

BIN
screenshot.2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

BIN
screenshot.3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 KiB

BIN
sounds/x_bows_quiver.1.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.2.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.3.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.4.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.5.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.6.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.7.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.8.ogg Normal file

Binary file not shown.

BIN
sounds/x_bows_quiver.9.ogg Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

BIN
textures/x_bows_quiver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

View File

@ -13,6 +13,3 @@
---@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) 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) 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.
---Entity definition
---@class ObjectProperties

View File

@ -1,7 +1,3 @@
---https://github.com/sumneko/lua-language-server/wiki ---https://github.com/sumneko/lua-language-server/wiki
---Undefined location
---@class LocationUndefined
---@field type string|'undefined'
---@alias Dump fun(obj: any, dumped?: any): string returns a string which makes `obj` human-readable, `obj`: arbitrary variable, `dumped`: table, default: `{}` ---@alias Dump fun(obj: any, dumped?: any): string returns a string which makes `obj` human-readable, `obj`: arbitrary variable, `dumped`: table, default: `{}`

View File

@ -17,4 +17,4 @@
---@field get_lists fun(): table Returns table that maps listnames to inventory lists ---@field get_lists fun(): table Returns table that maps listnames to inventory lists
---@field set_lists fun(self: InvRef, lists: table): nil Sets inventory lists, size will not change ---@field set_lists fun(self: InvRef, lists: table): nil Sets inventory lists, size will not change
---@field remove_item fun(self: InvRef, listname: string, stack: string|ItemStack): nil Take as many items as specified from the list, returns the items that were actually removed, as an `ItemStack`, note that any item metadata is ignored, so attempting to remove a specific unique item this way will likely remove the wrong one, to do that use `set_stack` with an empty `ItemStack`. ---@field remove_item fun(self: InvRef, listname: string, stack: string|ItemStack): nil Take as many items as specified from the list, returns the items that were actually removed, as an `ItemStack`, note that any item metadata is ignored, so attempting to remove a specific unique item this way will likely remove the wrong one, to do that use `set_stack` with an empty `ItemStack`.
---@field get_location fun(): InvRef|LocationUndefined returns a location compatible to `minetest.get_inventory(location)`. returns `{type="undefined"}` in case location is not known ---@field get_location fun(self: InvRef): {['type']: 'player'|'node'|'detached'|'undefined', ['name']: string|nil, ['pos']: Vector|nil} returns a location compatible to `minetest.get_inventory(location)`. returns `{type="undefined"}` in case location is not known

View File

@ -27,7 +27,7 @@
---@field add_wear_by_uses fun(self: ItemStack, max_uses: integer|number): nil Increases wear in such a way that, if only this function is called, the item breaks after `max_uses` times. Valid `max_uses` range is [0,65536] Does nothing if item is not a tool or if `max_uses` is 0 ---@field add_wear_by_uses fun(self: ItemStack, max_uses: integer|number): nil Increases wear in such a way that, if only this function is called, the item breaks after `max_uses` times. Valid `max_uses` range is [0,65536] Does nothing if item is not a tool or if `max_uses` is 0
---@field add_item fun(self: ItemStack, item: string|table): ItemStack Returns leftover `ItemStack` Put some item or stack onto this stack ---@field add_item fun(self: ItemStack, item: string|table): ItemStack Returns leftover `ItemStack` Put some item or stack onto this stack
---@field item_fits fun(self: ItemStack, item: string|table): boolean Returns `true` if item or stack can be fully added to this one. ---@field item_fits fun(self: ItemStack, item: string|table): boolean Returns `true` if item or stack can be fully added to this one.
---@field take_item fun(self: ItemStack, n: integer|number): ItemStack Returns taken `ItemStack` Take (and remove) up to `n` items from this stack `n`: number, default: `1` ---@field take_item fun(self: ItemStack, n?: integer|number): ItemStack Returns taken `ItemStack` Take (and remove) up to `n` items from this stack `n`: number, default: `1`
---@field peek_item fun(self: ItemStack, n: integer|number): ItemStack Returns taken `ItemStack` Copy (don't remove) up to `n` items from this stack `n`: number, default: `1` ---@field peek_item fun(self: ItemStack, n: integer|number): ItemStack Returns taken `ItemStack` Copy (don't remove) up to `n` items from this stack `n`: number, default: `1`
---@field name string ---@field name string
---@field count integer ---@field count integer

View File

@ -12,7 +12,7 @@
---@field set_string fun(self: MetaDataRef, key: string, value: string): string Value of `""` will delete the key. ---@field set_string fun(self: MetaDataRef, key: string, value: string): string Value of `""` will delete the key.
---@field get_string fun(self: MetaDataRef, key: string): string Returns `""` if key not present. ---@field get_string fun(self: MetaDataRef, key: string): string Returns `""` if key not present.
---@field set_int fun(self: MetaDataRef, key: string, value: integer): nil ---@field set_int fun(self: MetaDataRef, key: string, value: integer): nil
---@field get_int fun(self: MetaDataRef, key: string): string|integer Returns `0` if key not present. ---@field get_int fun(self: MetaDataRef, key: string): integer|number Returns `0` if key not present.
---@field set_float fun(self: MetaDataRef, key: string, value: number): nil ---@field set_float fun(self: MetaDataRef, key: string, value: number): nil
---@field get_float fun(self: MetaDataRef, key): integer|number Returns `0` if key not present. ---@field get_float fun(self: MetaDataRef, key): integer|number Returns `0` if key not present.
---@field to_table fun(): nil Returns `nil` or a table with keys: `fields`: key-value storage `inventory`: `{list1 = {}, ...}}` (NodeMetaRef only) ---@field to_table fun(): nil Returns `nil` or a table with keys: `fields`: key-value storage `inventory`: `{list1 = {}, ...}}` (NodeMetaRef only)

View File

@ -62,6 +62,11 @@
---@field add_node fun(pos: Vector, node: SetNodeTable): nil alias to `minetest.set_node`, Set node at position `pos`, `node`: table `{name=string, param1=number, param2=number}`, If param1 or param2 is omitted, it's set to `0`. e.g. `minetest.set_node({x=0, y=10, z=0}, {name="default:wood"})` ---@field add_node fun(pos: Vector, node: SetNodeTable): nil alias to `minetest.set_node`, Set node at position `pos`, `node`: table `{name=string, param1=number, param2=number}`, If param1 or param2 is omitted, it's set to `0`. e.g. `minetest.set_node({x=0, y=10, z=0}, {name="default:wood"})`
---@field string_to_pos fun(string: string): Vector|nil If the string can't be parsed to a position, nothing is returned. ---@field string_to_pos fun(string: string): Vector|nil If the string can't be parsed to a position, nothing is returned.
---@field chat_send_player fun(name: string, text: string): nil ---@field chat_send_player fun(name: string, text: string): nil
---@field create_detached_inventory fun(name: string, callbacks: DetachedInventoryCallbacks, player_name?: string): InvRef Creates a detached inventory. If it already exists, it is cleared. `callbacks`: See [Detached inventory callbacks], `player_name`: Make detached inventory available to one player exclusively, by default they will be sent to every player (even if not used). Note that this parameter is mostly just a workaround and will be removed in future releases.
---@field get_mod_storage fun(): StorageRef Mod metadata: per mod metadata, saved automatically. Can be obtained via `minetest.get_mod_storage()` during load time.
---@field show_formspec fun(playername: string, formname: string, formspec: string): nil `playername`: name of player to show formspec, `formname`: name passed to `on_player_receive_fields` callbacks. It should follow the `"modname:<whatever>"` naming convention. `formspec`: formspec to display
---@field register_on_player_receive_fields fun(func: fun(player: ObjectRef, formname: string, fields: table)): nil Called when the server received input from `player` in a formspec with the given `formname`. Specifically, this is called on any of the following events: a button was pressed, Enter was pressed while the focus was on a text field, a checkbox was toggled, something was selected in a dropdown list, a different tab was selected, selection was changed in a textlist or table, an entry was double-clicked in a textlist or table, a scrollbar was moved, or the form was actively closed by the player.
---@field get_inventory fun(location: {['"type"']: 'player'|'node'|'detached', ['"name"']: string|nil, ['"pos"']: Vector|nil}): InvRef
---Minetest settings ---Minetest settings
---@class MinetestSettings ---@class MinetestSettings
@ -83,6 +88,15 @@
---@field param1 number ---@field param1 number
---@field param2 number ---@field param2 number
--- Detached inventory callbacks
---@class DetachedInventoryCallbacks
---@field allow_move fun(inv: InvRef, from_list: string, from_index: number, to_list: string, to_index: number, count: number, player: ObjectRef): number Called when a player wants to move items inside the inventory. Return value: number of items allowed to move.
---@field allow_put fun(inv: InvRef, listname: string, index: number, stack: ItemStack, player: ObjectRef): number Called when a player wants to put something into the inventory. Return value: number of items allowed to put. Return value -1: Allow and don't modify item count in inventory.
---@field allow_take fun(inv: InvRef, listname: string, index: number, stack: ItemStack, player: ObjectRef): number Called when a player wants to take something out of the inventory. Return value: number of items allowed to take. Return value -1: Allow and don't modify item count in inventory.
---@field on_move fun(inv: InvRef, from_list: string, from_index: number, to_list: string, to_index: number, count: number, player: ObjectRef): nil
---@field on_put fun(inv: InvRef, listname: string, index: number, stack: ItemStack, player: ObjectRef): nil
---@field on_take fun(inv: InvRef, listname: string, index: number, stack: ItemStack, player: ObjectRef): nil Called after the actual action has happened, according to what was allowed. No return value.
--- Job table --- Job table
---@class JobTable ---@class JobTable
---@field cancel fun(self: JobTable) Cancels the job function from being called ---@field cancel fun(self: JobTable) Cancels the job function from being called

View File

@ -29,6 +29,7 @@
---@field node_sound_glass_defaults fun(table?: NodeSoundDef): NodeSoundDef ---@field node_sound_glass_defaults fun(table?: NodeSoundDef): NodeSoundDef
---@field node_sound_metal_defaults fun(table?: NodeSoundDef): NodeSoundDef ---@field node_sound_metal_defaults fun(table?: NodeSoundDef): NodeSoundDef
---@field node_sound_ice_defaults fun(table?: NodeSoundDef): NodeSoundDef ---@field node_sound_ice_defaults fun(table?: NodeSoundDef): NodeSoundDef
---@field get_hotbar_bg fun(x: number, y: number): nil Get the hotbar background as string, containing the formspec elements. x: Horizontal position in the formspec, y: Vertical position in the formspec.
--- Leaf decay definition --- Leaf decay definition
---@class RegisterLeafdecayDef ---@class RegisterLeafdecayDef

View File

@ -13,6 +13,10 @@
---@field buildable_to boolean If true, placed nodes can replace this node. default: `false` ---@field buildable_to boolean If true, placed nodes can replace this node. default: `false`
---@field tiles string|NodeTilesDef Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length. ---@field tiles string|NodeTilesDef Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length.
---@field sound NodeSoundDef Definition of node sounds to be played at various events. ---@field sound NodeSoundDef Definition of node sounds to be played at various events.
---@field drawtype NodeDrawTypes
---@field liquid_viscosity number|integer Controls speed at which the liquid spreads/flows (max. 7).
-- 0 is fastest, 7 is slowest. By default, this also slows down movement of players inside the node (can be overridden using `move_resistance`)
---@field walkable boolean If true, objects collide with node.
---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
@ -22,3 +26,24 @@
---@field align_style 'node'|'world'|'user' align style determines whether the texture will be rotated with the node or kept aligned with its surroundings. "user" means that client setting will be used, similar to `glasslike_framed_optional`. Note: supported by solid nodes and nodeboxes only. ---@field align_style 'node'|'world'|'user' align style determines whether the texture will be rotated with the node or kept aligned with its surroundings. "user" means that client setting will be used, similar to `glasslike_framed_optional`. Note: supported by solid nodes and nodeboxes only.
---@field scale number|integer scale is used to make texture span several (exactly `scale`) nodes, instead of just one, in each direction. Works for world-aligned textures only. Note that as the effect is applied on per-mapblock basis, `16` should be equally divisible by `scale` or you may get wrong results. ---@field scale number|integer scale is used to make texture span several (exactly `scale`) nodes, instead of just one, in each direction. Works for world-aligned textures only. Note that as the effect is applied on per-mapblock basis, `16` should be equally divisible by `scale` or you may get wrong results.
---@field color ColorSpec the texture's color will be multiplied with this color. the tile's color overrides the owning node's color in all cases. ---@field color ColorSpec the texture's color will be multiplied with this color. the tile's color overrides the owning node's color in all cases.
---There are a bunch of different looking node types. `*_optional` drawtypes need less rendering time if deactivated (always client-side).
---@alias NodeDrawTypes
---| '"normal"' # A node-sized cube.
---| '"airlike"' # Invisible, uses no texture.
---| '"liquid"' # The cubic source node for a liquid. Faces bordering to the same node are never rendered. Connects to node specified in `liquid_alternative_flowing`. Use `backface_culling = false` for the tiles you want to make visible when inside the node.
---| '"flowingliquid"' # The flowing version of a liquid, appears with various heights and slopes. Faces bordering to the same node are never rendered. Connects to node specified in `liquid_alternative_source`. Node textures are defined with `special_tiles` where the first tile is for the top and bottom faces and the second tile is for the side faces. `tiles` is used for the item/inventory/wield image rendering. Use `backface_culling = false` for the special tiles you want to make visible when inside the node
---| '"glasslike"' # Often used for partially-transparent nodes. Only external sides of textures are visible.
---| '"glasslike_framed"' # All face-connected nodes are drawn as one volume within a surrounding frame. The frame appearance is generated from the edges of the first texture specified in `tiles`. The width of the edges used are 1/16th of texture size: 1 pixel for 16x16, 2 pixels for 32x32 etc. The glass 'shine' (or other desired detail) on each node face is supplied by the second texture specified in `tiles`.
---| '"glasslike_framed_optional"' # This switches between the above 2 drawtypes according to the menu setting 'Connected Glass'.
---| '"allfaces"' # Often used for partially-transparent nodes. External and internal sides of textures are visible.
---| '"allfaces_optional"' # Often used for leaves nodes. This switches between `normal`, `glasslike` and `allfaces` according to the menu setting: Opaque Leaves / Simple Leaves / Fancy Leaves. With 'Simple Leaves' selected, the texture specified in `special_tiles` is used instead, if present. This allows a visually thicker texture to be used to compensate for how `glasslike` reduces visual thickness.
---| '"torchlike"' # A single vertical texture. If `paramtype2="[color]wallmounted"`: If placed on top of a node, uses the first texture specified in `tiles`. If placed against the underside of a node, uses the second texture specified in `tiles`. If placed on the side of a node, uses the third texture specified in `tiles` and is perpendicular to that node. If `paramtype2="none"`: Will be rendered as if placed on top of a node (see above) and only the first texture is used.
---| '"signlike"' # A single texture parallel to, and mounted against, the top, underside or side of a node. If `paramtype2="[color]wallmounted"`, it rotates according to `param2` If `par
---| '"plantlike"' # Two vertical and diagonal textures at right-angles to each other. See `paramtype2 = "meshoptions"` above for other options.
---| '"firelike"' # When above a flat surface, appears as 6 textures, the central 2 as `plantlike` plus 4 more surrounding those. If not above a surface the central 2 do not appear, but the texture appears against the faces of surrounding nodes if they are present.
---| '"fencelike"' # A 3D model suitable for a wooden fence. One placed node appears as a single vertical post. Adjacently-placed nodes cause horizontal bars to appear between them.
---| '"raillike"' # Often used for tracks for mining carts. Requires 4 textures to be specified in `tiles`, in order: Straight, curved, t-junction, crossing. Each placed node automatically switches to a suitable rotated texture determined by the adjacent `raillike` nodes, in order to create a continuous track network. Becomes a sloping node if placed against stepped nodes.
---| '"nodebox"' # Often used for stairs and slabs. Allows defining nodes consisting of an arbitrary number of boxes. See [Node boxes] below for more information.
---| '"mesh"' # Uses models for nodes. Tiles should hold model materials textures. Only static meshes are implemented. For supported model formats see Irrlicht engine documentation.
---| '"plantlike_rooted"' # Enables underwater `plantlike` without air bubbles around the nodes. Consists of a base cube at the co-ordinates of the node plus a `plantlike` extension above If `paramtype2="leveled", the `plantlike` extension has a height of `param2 / 16` nodes, otherwise it's the height of 1 node If `paramtype2="wallmounted"`, the `plantlike` extension will be at one of the corresponding 6 sides of the base cube. Also, the base cube rotates like a `normal` cube would The `plantlike` extension visually passes through any nodes above the base cube without affecting them. The base cube texture tiles are defined as normal, the `plantlike` extension uses the defined special tile, for example: `special_tiles = {{name = "default_papyrus.png"}},`

View File

@ -1,6 +1,6 @@
---https://github.com/sumneko/lua-language-server/wiki ---https://github.com/sumneko/lua-language-server/wiki
---@alias ObjectRef ObjectRefAbstract|ObjectRefLuaEntityRef ---@alias ObjectRef ObjectRefAbstract | ObjectRefLuaEntityRef
---Moving things in the game are generally these. ---Moving things in the game are generally these.
---This is basically a reference to a C++ `ServerActiveObject`. ---This is basically a reference to a C++ `ServerActiveObject`.
@ -21,6 +21,13 @@
---@field add_velocity fun(self: ObjectRef, vel: Vector): nil `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}`. In comparison to using get_velocity, adding the velocity and then using set_velocity, add_velocity is supposed to avoid synchronization problems. Additionally, players also do not support set_velocity. If a player: Does not apply during free_move. Note that since the player speed is normalized at each move step, increasing e.g. Y velocity beyond what would usually be achieved (see: physics overrides) will cause existing X/Z velocity to be reduced. Example: `add_velocity({x=0, y=6.5, z=0})` is equivalent to pressing the jump key (assuming default settings) ---@field add_velocity fun(self: ObjectRef, vel: Vector): nil `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}`. In comparison to using get_velocity, adding the velocity and then using set_velocity, add_velocity is supposed to avoid synchronization problems. Additionally, players also do not support set_velocity. If a player: Does not apply during free_move. Note that since the player speed is normalized at each move step, increasing e.g. Y velocity beyond what would usually be achieved (see: physics overrides) will cause existing X/Z velocity to be reduced. Example: `add_velocity({x=0, y=6.5, z=0})` is equivalent to pressing the jump key (assuming default settings)
---@field get_properties fun(self: ObjectRef): table Returns object property table ---@field get_properties fun(self: ObjectRef): table Returns object property table
---@field get_children fun(self: ObjectRef): ObjectRef[] Returns a list of ObjectRefs that are attached to the object. ---@field get_children fun(self: ObjectRef): ObjectRef[] Returns a list of ObjectRefs that are attached to the object.
---@field set_properties fun(self: ObjectRef, object_properties: ObjectProperties): nil For entities; disables the regular damage mechanism for players punching it by hand or a non-tool item, so that it can do something else than take damage.
---@field get_look_dir fun(self: ObjectRef): Vector get camera direction as a unit vector
---@field get_meta fun(self: ObjectRef): MetaDataRef returns a PlayerMetaRef.
---@field hud_add fun(self: ObjectRef, hud_definition: table): number|integer|nil add a HUD element described by HUD def, returns ID number on success
---@field hud_remove fun(self: ObjectRef, id: number|integer): nil remove the HUD element of the specified id
---@field hud_change fun(self: ObjectRef, id: number|integer, stat: string, value: any): nil change a value of a previously added HUD element. `stat` supports the same keys as in the hud definition table except for `"hud_elem_type"`.
---@field set_wielded_item fun(self: ObjectRef, item: ItemStack): boolean replaces the wielded item, returns `true` if successful.
---Moving things in the game are generally these. ---Moving things in the game are generally these.
---This is basically a reference to a C++ `ServerActiveObject`. ---This is basically a reference to a C++ `ServerActiveObject`.
@ -33,3 +40,39 @@
---@field immortal number|integer Skips all damage and breath handling for an object. This group will also hide the integrated HUD status bars for players. It is automatically set to all players when damage is disabled on the server and cannot be reset (subject to change). ---@field immortal number|integer Skips all damage and breath handling for an object. This group will also hide the integrated HUD status bars for players. It is automatically set to all players when damage is disabled on the server and cannot be reset (subject to change).
---@field fall_damage_add_percent number|integer Modifies the fall damage suffered by players when they hit the ground. It is analog to the node group with the same name. See the node group above for the exact calculation. ---@field fall_damage_add_percent number|integer Modifies the fall damage suffered by players when they hit the ground. It is analog to the node group with the same name. See the node group above for the exact calculation.
---@field punch_operable number|integer For entities; disables the regular damage mechanism for players punching it by hand or a non-tool item, so that it can do something else than take damage. ---@field punch_operable number|integer For entities; disables the regular damage mechanism for players punching it by hand or a non-tool item, so that it can do something else than take damage.
---Used by `ObjectRef` methods. Part of an Entity definition. These properties are not persistent, but are applied automatically to the corresponding Lua entity using the given registration fields. Player properties need to be saved manually.
---@class ObjectProperties
---@field hp_max integer Defines the maximum and default HP of the entity. For Lua entities the maximum is not enforced. For players this defaults to `minetest.PLAYER_MAX_HP_DEFAULT`.
---@field breath_max integer For players only. Defaults to `minetest.PLAYER_MAX_BREATH_DEFAULT`.
---@field zoom_fov number For players only. Zoom FOV in degrees. Note that zoom loads and/or generates world beyond the server's maximum send and generate distances, so acts like a telescope. Smaller zoom_fov values increase the distance loaded/generated. Defaults to 15 in creative mode, 0 in survival mode. zoom_fov = 0 disables zooming for the player.
---@field eye_height number For players only. Camera height above feet position in nodes.
---@field physical boolean Collide with `walkable` nodes.
---@field collide_with_objects boolean Collide with other objects if physical = true
---@field collisionbox number[]|integer[]
---@field selectionbox number[]|integer[] Selection box uses collision box dimensions when not set. For both boxes: {xmin, ymin, zmin, xmax, ymax, zmax} in nodes from object position.
---@field pointable boolean Whether the object can be pointed at
---@field visual 'cube'|'sprite'|'upright_sprite'|'mesh'|'wielditem'|'item' "cube" is a node-sized cube. "sprite" is a flat texture always facing the player. "upright_sprite" is a vertical flat texture. "mesh" uses the defined mesh model. "wielditem" is used for dropped items. (see builtin/game/item_entity.lua). For this use 'wield_item = itemname' (Deprecated: 'textures = {itemname}'). If the item has a 'wield_image' the object will be an extrusion of that, otherwise: If 'itemname' is a cubic node or nodebox the object will appear identical to 'itemname'. If 'itemname' is a plantlike node the object will be an extrusion of its texture. Otherwise for non-node items, the object will be an extrusion of 'inventory_image'. If 'itemname' contains a ColorString or palette index (e.g. from `minetest.itemstring_with_palette()`), the entity will inherit the color. "item" is similar to "wielditem" but ignores the 'wield_image' parameter.
---@field visual_size {['x']: integer|number, ['y']: integer|number, ['z']: integer|number} Multipliers for the visual size. If `z` is not specified, `x` will be used to scale the entity along both horizontal axes.
---@field mesh string File name of mesh when using "mesh" visual
---@field textures table Number of required textures depends on visual. "cube" uses 6 textures just like a node, but all 6 must be defined. "sprite" uses 1 texture. "upright_sprite" uses 2 textures: {front, back}. "wielditem" expects 'textures = {itemname}'. "mesh" requires one texture for each mesh buffer/material (in order)
---@field colors table Number of required colors depends on visual
---@field use_texture_alpha boolean Use texture's alpha channel. Excludes "upright_sprite" and "wielditem". Note: currently causes visual issues when viewed through other semi-transparent materials such as water.
---@field spritediv {['x']: integer|number, ['y']: integer|number} Used with spritesheet textures for animation and/or frame selection according to position relative to player. Defines the number of columns and rows in the spritesheet: {columns, rows}.
---@field initial_sprite_basepos {['x']: integer|number, ['y']: integer|number} Used with spritesheet textures. Defines the {column, row} position of the initially used frame in the spritesheet.
---@field is_visible boolean If false, object is invisible and can't be pointed.
---@field makes_footstep_sound boolean If true, is able to make footstep sounds of nodes
---@field automatic_rotate number|integer Set constant rotation in radians per second, positive or negative. Object rotates along the local Y-axis, and works with set_rotation. Set to 0 to disable constant rotation.
---@field stepheight number|integer If positive number, object will climb upwards when it moves horizontally against a `walkable` node, if the height difference is within `stepheight`.
---@field automatic_face_movement_dir number|integer Automatically set yaw to movement direction, offset in degrees. 'false' to disable.
---@field automatic_face_movement_max_rotation_per_sec number|integer Limit automatic rotation to this value in degrees per second. No limit if value <= 0.
---@field backface_culling boolean Set to false to disable backface_culling for model
---@field glow number|integer Add this much extra lighting when calculating texture color. Value < 0 disables light's effect on texture color. For faking self-lighting, UI style entities, or programmatic coloring in mods.
---@field nametag string The name to display on the head of the object. By default empty. If the object is a player, a nil or empty nametag is replaced by the player's name. For all other objects, a nil or empty string removes the nametag. To hide a nametag, set its color alpha to zero. That will disable it entirely.
---@field nametag_color ColorSpec Sets text color of nametag
---@field nametag_bgcolor ColorSpec Sets background color of nametag `false` will cause the background to be set automatically based on user settings. Default: false
---@field infotext string Same as infotext for nodes. Empty by default
---@field static_save boolean If false, never save this object statically. It will simply be deleted when the block gets unloaded. The get_staticdata() callback is never called then. Defaults to 'true'.
---@field damage_texture_modifier string Texture modifier to be applied for a short duration when object is hit
---@field shaded boolean Setting this to 'false' disables diffuse lighting of entity
---@field show_on_minimap boolean Defaults to true for players, false for other entities. If set to true the entity will show as a marker on the minimap.

View File

@ -8,6 +8,7 @@
--- Definition of node sounds to be played at various events. --- Definition of node sounds to be played at various events.
---@class NodeSoundDef ---@class NodeSoundDef
---@field name string Sound name.
---@field footstep SimpleSoundSpec If walkable, played when object walks on it. If node is climbable or a liquid, played when object moves through it ---@field footstep SimpleSoundSpec If walkable, played when object walks on it. If node is climbable or a liquid, played when object moves through it
---@field dig SimpleSoundSpec|'__group' While digging node. If `"__group"`, then the sound will be `default_dig_<groupname>`, where `<groupname>` is the name of the item's digging group with the fastest digging time. In case of a tie, one of the sounds will be played (but we cannot predict which one) Default value: `"__group"` ---@field dig SimpleSoundSpec|'__group' While digging node. If `"__group"`, then the sound will be `default_dig_<groupname>`, where `<groupname>` is the name of the item's digging group with the fastest digging time. In case of a tie, one of the sounds will be played (but we cannot predict which one) Default value: `"__group"`
---@field dug SimpleSoundSpec Node was dug ---@field dug SimpleSoundSpec Node was dug