diff --git a/.cdb.json b/.cdb.json index c637dfa..dad2a35 100644 --- a/.cdb.json +++ b/.cdb.json @@ -13,7 +13,7 @@ "license": "LGPL-2.1-or-later", "media_license": "CC-BY-SA-4.0", "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, "video_url": "https://youtu.be/pItpltmUoa8" } diff --git a/.luacheckrc b/.luacheckrc index 067c620..2d4f4c2 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -23,6 +23,7 @@ read_globals = { "PcgRandom", "ItemStack", "AreaStore", + "unpack", "vector", @@ -57,4 +58,4 @@ read_globals = { "mesecon", "armor", "default" -} \ No newline at end of file +} diff --git a/README.md b/README.md index 20a7329..ee4c469 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Adds bow and arrows to Minetest. -![screenshot](screenshot.png) +![screenshot](screenshot.1.png) Video: https://youtu.be/pItpltmUoa8 @@ -11,6 +11,7 @@ Video: https://youtu.be/pItpltmUoa8 * bow will force you sneak when loaded (optional dep. playerphysics) * 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 +* charged bow in inventory will discharge and give back the arrow when not selected * arrow uses raycast * 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) @@ -24,9 +25,15 @@ Video: https://youtu.be/pItpltmUoa8 * registers only one entity reused for all arrows * (experimental) poison arrow - dealing damage for 5s but will not kill the target * 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 +### 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) 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). 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 - 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_charged.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_tipped_particle.png - x_bows_bubble.png @@ -92,6 +105,18 @@ Modified by SaKeL: - x_bows_arrow_diamond.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 **Creative Commons License, EminYILDIRIM**, https://freesound.org @@ -122,6 +147,24 @@ Modified by SaKeL: - 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 see: http://wiki.minetest.com/wiki/Installing_Mods diff --git a/arrow.lua b/arrow.lua index a4d081c..ac60a5f 100644 --- a/arrow.lua +++ b/arrow.lua @@ -1,551 +1,597 @@ --- Gets total armor level from 3d armor +---Gets total armor level from 3d armor +---@param player ObjectRef +---@return integer local function get_3d_armor_armor(player) - local armor_total = 0 + local armor_total = 0 - if not player:is_player() or not minetest.get_modpath('3d_armor') or not armor.def[player:get_player_name()] then - return armor_total - end + if not player:is_player() or not minetest.get_modpath('3d_armor') or not armor.def[player:get_player_name()] then + return armor_total + end - armor_total = armor.def[player:get_player_name()].level + armor_total = armor.def[player:get_player_name()].level - return armor_total + return armor_total end --- Limits number `x` between `min` and `max` values +---Limits number `x` between `min` and `max` values +---@param x integer +---@param min integer +---@param max integer +---@return integer local function limit(x, min, max) - return math.min(math.max(x, min), max) + return math.min(math.max(x, min), max) end --- Gets `ObjectRef` collision box +---Gets collision box +---@param obj ObjectRef +---@return number[] local function get_obj_box(obj) - local box + local box - if obj:is_player() then - box = obj:get_properties().collisionbox or {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5} - else - box = obj:get_luaentity().collisionbox or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5} - end + if obj:is_player() then + box = obj:get_properties().collisionbox or {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5} + else + box = obj:get_luaentity().collisionbox or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5} + end - return box + return box end --- Poison Arrow Effects +---Poison Arrow Effects +---@param tick integer|number +---@param time integer|number +---@param time_left integer|number +---@param arrow_obj ObjectRef +---@param target_obj ObjectRef +---@param old_damage_texture_modifier string +---@param punch_def table function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_damage_texture_modifier, punch_def) - if not arrow_obj or target_obj:get_hp() <= 0 then - return - end + if not arrow_obj or target_obj:get_hp() <= 0 then + return + end - target_obj:set_properties({damage_texture_modifier = '^[colorize:#00FF0050'}) + target_obj:set_properties({damage_texture_modifier = '^[colorize:#00FF0050'}) - time_left = time_left + tick + time_left = time_left + tick - if time_left <= time then - minetest.after( - tick, - x_bows.poison_effect, - tick, - time, - time_left, - arrow_obj, - target_obj, - old_damage_texture_modifier, - punch_def - ) - elseif target_obj:is_player() then - if x_bows.hbhunger then - -- Reset HUD bar color - hb.change_hudbar(target_obj, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png') - end + if time_left <= time then + minetest.after( + tick, + x_bows.poison_effect, + tick, + time, + time_left, + arrow_obj, + target_obj, + old_damage_texture_modifier, + punch_def + ) + elseif target_obj:is_player() then + if x_bows.hbhunger then + -- Reset HUD bar color + hb.change_hudbar(target_obj, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png') + end - if old_damage_texture_modifier then - target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier}) - end + if old_damage_texture_modifier then + target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier}) + end - -- return - else - -- local lua_ent = target_obj:get_luaentity() + -- return + else + -- local lua_ent = target_obj:get_luaentity() - -- if not lua_ent then - -- return - -- end + -- if not lua_ent then + -- return + -- end - -- lua_ent[arrow_obj.arrow .. '_active'] = false + -- lua_ent[arrow_obj.arrow .. '_active'] = false - if old_damage_texture_modifier then - target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier}) - end - -- return - end + if old_damage_texture_modifier then + target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier}) + end + -- return + end - local _damage = punch_def.tool_capabilities.damage_groups.fleshy - if target_obj:get_hp() - _damage > 0 then - target_obj:punch( - punch_def.puncher, - punch_def.time_from_last_punch, - punch_def.tool_capabilities - ) + local _damage = punch_def.tool_capabilities.damage_groups.fleshy + if target_obj:get_hp() - _damage > 0 then + target_obj:punch( + punch_def.puncher, + punch_def.time_from_last_punch, + punch_def.tool_capabilities + ) - local target_obj_pos = target_obj:get_pos() + local target_obj_pos = target_obj:get_pos() - if target_obj_pos then - x_bows.particle_effect(target_obj_pos, 'arrow_tipped') - end - end + if target_obj_pos then + x_bows.particle_effect(target_obj_pos, 'arrow_tipped') + end + end end -- Main Arrow Entity minetest.register_entity('x_bows:arrow_entity', { - initial_properties = { - visual = 'wielditem', - visual_size = {x = 0.2, y = 0.2, z = 0.3}, - collisionbox = {0, 0, 0, 0, 0, 0}, - selectionbox = {0, 0, 0, 0, 0, 0}, - physical = false, - textures = {'air'}, - hp_max = 1 - }, + initial_properties = { + visual = 'wielditem', + collisionbox = {0, 0, 0, 0, 0, 0}, + selectionbox = {0, 0, 0, 0, 0, 0}, + physical = false, + textures = {'air'}, + hp_max = 1 + }, - on_activate = function(self, staticdata) - if not self or not staticdata or staticdata == '' then - self.object:remove() - return - end + ---@param self table + ---@param staticdata string + on_activate = function(self, staticdata) + if not self or not staticdata or staticdata == '' then + self.object:remove() + return + end - local _staticdata = minetest.deserialize(staticdata) + local _staticdata = minetest.deserialize(staticdata) - -- set/reset - do not inherit from previous entity table - self._velocity = {x = 0, y = 0, z = 0} - self._old_pos = nil - self._attached = false - self._attached_to = { - type = '', - pos = nil - } - self._has_particles = false - self._lifetimer = 60 - self._nodechecktimer = 0.5 - self._is_drowning = false - self._in_liquid = false - self._poison_arrow = false - self._shot_from_pos = self.object:get_pos() - self.arrow = _staticdata.arrow - self.user = minetest.get_player_by_name(_staticdata.user_name) - self._tflp = _staticdata._tflp - self._tool_capabilities = _staticdata._tool_capabilities - self._is_critical_hit = _staticdata.is_critical_hit + -- set/reset - do not inherit from previous entity table + self._velocity = {x = 0, y = 0, z = 0} + self._old_pos = nil + self._attached = false + self._attached_to = { + type = '', + pos = nil + } + self._has_particles = false + self._lifetimer = 60 + self._nodechecktimer = 0.5 + self._is_drowning = false + self._in_liquid = false + self._poison_arrow = false + self._shot_from_pos = self.object:get_pos() + self.arrow = _staticdata.arrow + self.user = minetest.get_player_by_name(_staticdata.user_name) + self._tflp = _staticdata._tflp + self._tool_capabilities = _staticdata._tool_capabilities + self._is_critical_hit = _staticdata.is_critical_hit + self._faster_arrows_multiplier = _staticdata.faster_arrows_multiplier + self._add_damage = _staticdata.add_damage - if self.arrow == 'x_bows:arrow_diamond_tipped_poison' then - self._poison_arrow = true - end + if self.arrow == 'x_bows:arrow_diamond_tipped_poison' then + self._poison_arrow = true + end - self.object:set_properties({ - textures = {'x_bows:arrow_node'}, - infotext = self.arrow - }) - end, + self.object:set_properties({ + textures = {'x_bows:arrow_node'}, + infotext = self.arrow + }) + end, - on_death = function(self, killer) - if not self._old_pos then - self.object:remove() - return - end + ---@param self table + ---@param killer ObjectRef|nil + on_death = function(self, killer) + if not self._old_pos then + self.object:remove() + return + end - minetest.item_drop(ItemStack(self.arrow), nil, vector.round(self._old_pos)) - end, + minetest.item_drop(ItemStack(self.arrow), nil, vector.round(self._old_pos)) + end, - on_step = function(self, dtime) - local pos = self.object:get_pos() - self._old_pos = self._old_pos or pos - local ray = minetest.raycast(self._old_pos, pos, true, true) - local pointed_thing = ray:next() + ---@param self table + ---@param dtime integer|number + on_step = function(self, dtime) + local pos = self.object:get_pos() + self._old_pos = self._old_pos or pos + local ray = minetest.raycast(self._old_pos, pos, true, true) + local pointed_thing = ray:next() - self._lifetimer = self._lifetimer - dtime - self._nodechecktimer = self._nodechecktimer - dtime + self._lifetimer = self._lifetimer - dtime + self._nodechecktimer = self._nodechecktimer - dtime - -- adjust pitch when flying - if not self._attached then - local velocity = self.object:get_velocity() - local v_rotation = self.object:get_rotation() - local pitch = math.atan2(velocity.y, math.sqrt(velocity.x^2 + velocity.z^2)) + -- adjust pitch when flying + if not self._attached then + local velocity = self.object:get_velocity() + local v_rotation = self.object:get_rotation() + local pitch = math.atan2(velocity.y, math.sqrt(velocity.x^2 + velocity.z^2)) - self.object:set_rotation({ - x = pitch, - y = v_rotation.y, - z = v_rotation.z - }) - end + self.object:set_rotation({ + x = pitch, + y = v_rotation.y, + z = v_rotation.z + }) + end - -- remove attached arrows after lifetime - if self._lifetimer <= 0 then - self.object:remove() - return - end + -- remove attached arrows after lifetime + if self._lifetimer <= 0 then + self.object:remove() + return + end - -- add particles only when not attached - if not self._attached and not self._in_liquid then - self._has_particles = true + -- add particles only when not attached + if not self._attached and not self._in_liquid then + self._has_particles = true - if self._tflp >= self._tool_capabilities.full_punch_interval then - if self._is_critical_hit then - x_bows.particle_effect(self._old_pos, 'arrow_crit') - else - x_bows.particle_effect(self._old_pos, 'arrow') - end - end - end + if self._tflp >= self._tool_capabilities.full_punch_interval then + if self._is_critical_hit then + x_bows.particle_effect(self._old_pos, 'arrow_crit') + elseif self._faster_arrows_multiplier then + x_bows.particle_effect(self._old_pos, 'arrow_fast') + else + x_bows.particle_effect(self._old_pos, 'arrow') + end + end + end - -- remove attached arrows after object dies - if not self.object:get_attach() and self._attached_to.type == 'object' then - self.object:remove() - return - end + -- remove attached arrows after object dies + if not self.object:get_attach() and self._attached_to.type == 'object' then + self.object:remove() + return + end - -- arrow falls down when not attached to node any more - if self._attached_to.type == 'node' and self._attached and self._nodechecktimer <= 0 then - local node = minetest.get_node(self._attached_to.pos) - self._nodechecktimer = 0.5 + -- arrow falls down when not attached to node any more + if self._attached_to.type == 'node' and self._attached and self._nodechecktimer <= 0 then + local node = minetest.get_node(self._attached_to.pos) + self._nodechecktimer = 0.5 - if not node then - return - end + if not node then + return + end - if node.name == 'air' then - self.object:set_velocity({x = 0, y = -3, z = 0}) - self.object:set_acceleration({x = 0, y = -3, z = 0}) - -- reset values - self._attached = false - self._attached_to.type = '' - self._attached_to.pos = nil - self.object:set_properties({collisionbox = {0, 0, 0, 0, 0, 0}}) + if node.name == 'air' then + self.object:set_velocity({x = 0, y = -3, z = 0}) + self.object:set_acceleration({x = 0, y = -3, z = 0}) + -- reset values + self._attached = false + self._attached_to.type = '' + self._attached_to.pos = nil + self.object:set_properties({collisionbox = {0, 0, 0, 0, 0, 0}}) - return - end - end + return + end + end - while pointed_thing do - local ip_pos = pointed_thing.intersection_point - local in_pos = pointed_thing.intersection_normal - self.pointed_thing = pointed_thing + while pointed_thing do + local ip_pos = pointed_thing.intersection_point + local in_pos = pointed_thing.intersection_normal + self.pointed_thing = pointed_thing - if pointed_thing.type == 'object' - and pointed_thing.ref ~= self.object - and pointed_thing.ref:get_hp() > 0 - and ( - (pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= self.user:get_player_name()) - or ( - pointed_thing.ref:get_luaentity() - and pointed_thing.ref:get_luaentity().physical - and pointed_thing.ref:get_luaentity().name ~= '__builtin:item' - ) - ) - and self.object:get_attach() == nil - then - if pointed_thing.ref:is_player() then - minetest.sound_play('x_bows_arrow_successful_hit', { - to_player = self.user:get_player_name(), - gain = 0.3 - }) - else - minetest.sound_play('x_bows_arrow_hit', { - to_player = self.user:get_player_name(), - gain = 0.6 - }) - end + if pointed_thing.type == 'object' + and pointed_thing.ref ~= self.object + and pointed_thing.ref:get_hp() > 0 + and ( + (pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= self.user:get_player_name()) + or ( + pointed_thing.ref:get_luaentity() + and pointed_thing.ref:get_luaentity().physical + and pointed_thing.ref:get_luaentity().name ~= '__builtin:item' + ) + ) + and self.object:get_attach() == nil + then + if pointed_thing.ref:is_player() then + minetest.sound_play('x_bows_arrow_successful_hit', { + to_player = self.user:get_player_name(), + gain = 0.3 + }) + else + minetest.sound_play('x_bows_arrow_hit', { + to_player = self.user:get_player_name(), + gain = 0.6 + }) + end - -- store these here before punching in case pointed_thing.ref dies - local collisionbox = get_obj_box(pointed_thing.ref) - local xmin = collisionbox[1] * 100 - local ymin = collisionbox[2] * 100 - local zmin = collisionbox[3] * 100 - local xmax = collisionbox[4] * 100 - local ymax = collisionbox[5] * 100 - local zmax = collisionbox[6] * 100 + -- store these here before punching in case pointed_thing.ref dies + local collisionbox = get_obj_box(pointed_thing.ref) + local xmin = collisionbox[1] * 100 + local ymin = collisionbox[2] * 100 + local zmin = collisionbox[3] * 100 + local xmax = collisionbox[4] * 100 + local ymax = collisionbox[5] * 100 + local zmax = collisionbox[6] * 100 - self.object:set_velocity({x = 0, y = 0, z = 0}) - self.object:set_acceleration({x = 0, y = 0, z = 0}) + self.object:set_velocity({x = 0, y = 0, z = 0}) + self.object:set_acceleration({x = 0, y = 0, z = 0}) - -- calculate damage - local target_armor_groups = pointed_thing.ref:get_armor_groups() - local _damage = 0 - for group, base_damage in pairs(self._tool_capabilities.damage_groups) do - _damage = _damage - + base_damage - * limit(self._tflp / self._tool_capabilities.full_punch_interval, 0.0, 1.0) - * ((target_armor_groups[group] or 0) + get_3d_armor_armor(pointed_thing.ref)) / 100.0 - end + -- calculate damage + local target_armor_groups = pointed_thing.ref:get_armor_groups() + local _damage = 0 - -- crits - if self._is_critical_hit then - _damage = _damage * 2 - end + if self._add_damage then + _damage = _damage + self._add_damage + end - -- knockback - local dir = vector.normalize(vector.subtract(self._shot_from_pos, ip_pos)) - local distance = vector.distance(self._shot_from_pos, ip_pos) - local knockback = minetest.calculate_knockback( - pointed_thing.ref, - self.object, - self._tflp, - { - full_punch_interval = self._tool_capabilities.full_punch_interval, - damage_groups = {fleshy = _damage}, - }, - dir, - distance, - _damage - ) + for group, base_damage in pairs(self._tool_capabilities.damage_groups) do + _damage = _damage + + base_damage + * limit(self._tflp / self._tool_capabilities.full_punch_interval, 0.0, 1.0) + * ((target_armor_groups[group] or 0) + get_3d_armor_armor(pointed_thing.ref)) / 100.0 + end - pointed_thing.ref:add_velocity({ - x = dir.x * knockback * -1, - y = 7, - z = dir.z * knockback * -1 - }) + -- crits + if self._is_critical_hit then + _damage = _damage * 2 + end - pointed_thing.ref:punch( - self.object, - self._tflp, - { - full_punch_interval = self._tool_capabilities.full_punch_interval, - damage_groups = {fleshy = _damage, knockback = knockback} - }, - { - x = dir.x * -1, - y = 7, - z = dir.z * -1 - } - ) + -- knockback + local dir = vector.normalize(vector.subtract(self._shot_from_pos, ip_pos)) + local distance = vector.distance(self._shot_from_pos, ip_pos) + local knockback = minetest.calculate_knockback( + pointed_thing.ref, + self.object, + self._tflp, + { + full_punch_interval = self._tool_capabilities.full_punch_interval, + damage_groups = {fleshy = _damage}, + }, + dir, + distance, + _damage + ) - -- already dead (entity) - if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then - self.object:remove() - return - end + pointed_thing.ref:add_velocity({ + x = dir.x * knockback * -1, + y = 7, + z = dir.z * knockback * -1 + }) - -- already dead (player) - if pointed_thing.ref:get_hp() <= 0 then - if x_bows.hbhunger then - -- Reset HUD bar color - hb.change_hudbar(pointed_thing.ref, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png') - end - self.object:remove() - return - end + pointed_thing.ref:punch( + self.object, + self._tflp, + { + full_punch_interval = self._tool_capabilities.full_punch_interval, + damage_groups = {fleshy = _damage, knockback = knockback} + }, + { + x = dir.x * -1, + y = 7, + z = dir.z * -1 + } + ) - -- attach arrow prepare - local rotation = {x = 0, y = 0, z = 0} - local position = {x = 0, y = 0, z = 0} + -- already dead (entity) + if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then + self.object:remove() + return + end - if in_pos.x == 1 then - -- x = 0 - -- y = -90 - -- z = 0 - rotation.x = math.random(-10, 10) - rotation.y = math.random(-100, -80) - rotation.z = math.random(-10, 10) + -- already dead (player) + if pointed_thing.ref:get_hp() <= 0 then + if x_bows.hbhunger then + -- Reset HUD bar color + hb.change_hudbar(pointed_thing.ref, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png') + end + self.object:remove() + return + end - position.x = xmax / 10 - position.y = math.random(ymin, ymax) / 10 - position.z = math.random(zmin, zmax) / 10 - elseif in_pos.x == -1 then - -- x = 0 - -- y = 90 - -- z = 0 - rotation.x = math.random(-10, 10) - rotation.y = math.random(80, 100) - rotation.z = math.random(-10, 10) + -- attach arrow prepare + local rotation = {x = 0, y = 0, z = 0} + local position = {x = 0, y = 0, z = 0} - position.x = xmin / 10 - position.y = math.random(ymin, ymax) / 10 - position.z = math.random(zmin, zmax) / 10 - elseif in_pos.y == 1 then - -- x = -90 - -- y = 0 - -- z = -180 - rotation.x = math.random(-100, -80) - rotation.y = math.random(-10, 10) - rotation.z = math.random(-190, -170) + if in_pos.x == 1 then + -- x = 0 + -- y = -90 + -- z = 0 + rotation.x = math.random(-10, 10) + rotation.y = math.random(-100, -80) + rotation.z = math.random(-10, 10) - position.x = math.random(xmin, xmax) / 10 - position.y = ymax / 10 - position.z = math.random(zmin, zmax) / 10 - elseif in_pos.y == -1 then - -- x = 90 - -- y = 0 - -- z = 180 - rotation.x = math.random(80, 100) - rotation.y = math.random(-10, 10) - rotation.z = math.random(170, 190) + position.x = xmax / 10 + position.y = math.random(ymin, ymax) / 10 + position.z = math.random(zmin, zmax) / 10 + elseif in_pos.x == -1 then + -- x = 0 + -- y = 90 + -- z = 0 + rotation.x = math.random(-10, 10) + rotation.y = math.random(80, 100) + rotation.z = math.random(-10, 10) - position.x = math.random(xmin, xmax) / 10 - position.y = ymin / 10 - position.z = math.random(zmin, zmax) / 10 - elseif in_pos.z == 1 then - -- x = 180 - -- y = 0 - -- z = 180 - rotation.x = math.random(170, 190) - rotation.y = math.random(-10, 10) - rotation.z = math.random(170, 190) + position.x = xmin / 10 + position.y = math.random(ymin, ymax) / 10 + position.z = math.random(zmin, zmax) / 10 + elseif in_pos.y == 1 then + -- x = -90 + -- y = 0 + -- z = -180 + rotation.x = math.random(-100, -80) + rotation.y = math.random(-10, 10) + rotation.z = math.random(-190, -170) - position.x = math.random(xmin, xmax) / 10 - position.y = math.random(ymin, ymax) / 10 - position.z = zmax / 10 - elseif in_pos.z == -1 then - -- x = -180 - -- y = 180 - -- z = -180 - rotation.x = math.random(-190, -170) - rotation.y = math.random(170, 190) - rotation.z = math.random(-190, -170) + position.x = math.random(xmin, xmax) / 10 + position.y = ymax / 10 + position.z = math.random(zmin, zmax) / 10 + elseif in_pos.y == -1 then + -- x = 90 + -- y = 0 + -- z = 180 + rotation.x = math.random(80, 100) + rotation.y = math.random(-10, 10) + rotation.z = math.random(170, 190) - position.x = math.random(xmin, xmax) / 10 - position.y = math.random(ymin, ymax) / 10 - position.z = zmin / 10 - end + position.x = math.random(xmin, xmax) / 10 + position.y = ymin / 10 + position.z = math.random(zmin, zmax) / 10 + elseif in_pos.z == 1 then + -- x = 180 + -- y = 0 + -- z = 180 + rotation.x = math.random(170, 190) + rotation.y = math.random(-10, 10) + rotation.z = math.random(170, 190) - -- poison arrow - if self._poison_arrow then - local old_damage_texture_modifier = pointed_thing.ref:get_properties().damage_texture_modifier - local punch_def = {} - punch_def.puncher = self.object - punch_def.time_from_last_punch = self._tflp - punch_def.tool_capabilities = { - full_punch_interval = self._tool_capabilities.full_punch_interval, - damage_groups = {fleshy = _damage, knockback = knockback} - } + position.x = math.random(xmin, xmax) / 10 + position.y = math.random(ymin, ymax) / 10 + position.z = zmax / 10 + elseif in_pos.z == -1 then + -- x = -180 + -- y = 180 + -- z = -180 + rotation.x = math.random(-190, -170) + rotation.y = math.random(170, 190) + rotation.z = math.random(-190, -170) - if pointed_thing.ref:is_player() then - -- @TODO missing `active` posion arrow check for player (see lua_ent below) - if x_bows.hbhunger then - -- Set poison bar - hb.change_hudbar( - pointed_thing.ref, - 'health', - nil, - nil, - 'hbhunger_icon_health_poison.png', - nil, - 'hbhunger_bar_health_poison.png' - ) - end + position.x = math.random(xmin, xmax) / 10 + position.y = math.random(ymin, ymax) / 10 + position.z = zmin / 10 + end - x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def) - else - -- local lua_ent = pointed_thing.ref:get_luaentity() - -- if not lua_ent[self.arrow .. '_active'] or lua_ent[self.arrow .. '_active'] == 'false' then - -- lua_ent[self.arrow .. '_active'] = true - x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def) - -- end - end - end + -- poison arrow + if self._poison_arrow then + local old_damage_texture_modifier = pointed_thing.ref:get_properties().damage_texture_modifier + local punch_def = {} + punch_def.puncher = self.object + punch_def.time_from_last_punch = self._tflp + punch_def.tool_capabilities = { + full_punch_interval = self._tool_capabilities.full_punch_interval, + damage_groups = {fleshy = _damage, knockback = knockback} + } - if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then - self.object:remove() - return - end + if pointed_thing.ref:is_player() then + -- @TODO missing `active` posion arrow check for player (see lua_ent below) + if x_bows.hbhunger then + -- Set poison bar + hb.change_hudbar( + pointed_thing.ref, + 'health', + nil, + nil, + 'hbhunger_icon_health_poison.png', + nil, + 'hbhunger_bar_health_poison.png' + ) + end - -- attach arrow - self.object:set_attach( - pointed_thing.ref, - '', - position, - rotation, - true - ) - self._attached = true - self._attached_to.type = pointed_thing.type - self._attached_to.pos = position + x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def) + else + -- local lua_ent = pointed_thing.ref:get_luaentity() + -- if not lua_ent[self.arrow .. '_active'] or lua_ent[self.arrow .. '_active'] == 'false' then + -- lua_ent[self.arrow .. '_active'] = true + x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def) + -- end + end + end - -- remove last arrow when too many already attached - local children = {} + if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then + self.object:remove() + return + end - for _, object in ipairs(pointed_thing.ref:get_children()) do - if object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then - table.insert(children, object) - end - end + -- attach arrow + self.object:set_attach( + pointed_thing.ref, + '', + position, + rotation, + true + ) + self._attached = true + self._attached_to.type = pointed_thing.type + self._attached_to.pos = position - if #children >= 5 then - children[1]:remove() - end + -- remove last arrow when too many already attached + local children = {} - return + for _, object in ipairs(pointed_thing.ref:get_children()) do + if object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then + table.insert(children, object) + end + end - elseif pointed_thing.type == 'node' and not self._attached then - local node = minetest.get_node(pointed_thing.under) - local node_def = minetest.registered_nodes[node.name] + if #children >= 5 then + children[1]:remove() + end - if not node_def then - return - end + return - self._velocity = self.object:get_velocity() + elseif pointed_thing.type == 'node' and not self._attached then + local node = minetest.get_node(pointed_thing.under) + local node_def = minetest.registered_nodes[node.name] - if node_def.drawtype == 'liquid' and not self._is_drowning then - self._is_drowning = true - self._in_liquid = true - local drag = 1 / (node_def.liquid_viscosity * 6) - self.object:set_velocity(vector.multiply(self._velocity, drag)) - self.object:set_acceleration({x = 0, y = -1.0, z = 0}) + if not node_def then + return + end - x_bows.particle_effect(self._old_pos, 'bubble') - elseif self._is_drowning then - self._is_drowning = false + self._velocity = self.object:get_velocity() - if self._velocity then - self.object:set_velocity(self._velocity) - end + if node_def.drawtype == 'liquid' and not self._is_drowning then + self._is_drowning = true + self._in_liquid = true + local drag = 1 / (node_def.liquid_viscosity * 6) + self.object:set_velocity(vector.multiply(self._velocity, drag)) + self.object:set_acceleration({x = 0, y = -1.0, z = 0}) - self.object:set_acceleration({x = 0, y = -9.81, z = 0}) - end + x_bows.particle_effect(self._old_pos, 'bubble') + elseif self._is_drowning then + self._is_drowning = false - if x_bows.mesecons and node.name == 'x_bows:target' then - local distance = vector.distance(pointed_thing.under, ip_pos) - distance = math.floor(distance * 100) / 100 + if self._velocity then + self.object:set_velocity(self._velocity) + end - -- only close to the center of the target will trigger signal - if distance < 0.54 then - mesecon.receptor_on(pointed_thing.under) - minetest.get_node_timer(pointed_thing.under):start(2) - end - end + self.object:set_acceleration({x = 0, y = -9.81, z = 0}) + end - if node_def.walkable then - self.object:set_velocity({x=0, y=0, z=0}) - self.object:set_acceleration({x=0, y=0, z=0}) - self.object:set_pos(ip_pos) - self.object:set_rotation(self.object:get_rotation()) - self._attached = true - self._attached_to.type = pointed_thing.type - self._attached_to.pos = pointed_thing.under - self.object:set_properties({collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}}) + if x_bows.mesecons and node.name == 'x_bows:target' then + local distance = vector.distance(pointed_thing.under, ip_pos) + distance = math.floor(distance * 100) / 100 - -- remove last arrow when too many already attached - local children = {} + -- only close to the center of the target will trigger signal + if distance < 0.54 then + mesecon.receptor_on(pointed_thing.under) + minetest.get_node_timer(pointed_thing.under):start(2) + end + end - for _, object in ipairs(minetest.get_objects_inside_radius(pointed_thing.under, 1)) do - if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then - table.insert(children, object) - end - end + if node_def.walkable then + self.object:set_velocity({x=0, y=0, z=0}) + self.object:set_acceleration({x=0, y=0, z=0}) + self.object:set_pos(ip_pos) + self.object:set_rotation(self.object:get_rotation()) + self._attached = true + self._attached_to.type = pointed_thing.type + self._attached_to.pos = pointed_thing.under + self.object:set_properties({collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}}) - if #children >= 5 then - children[#children]:remove() - end + -- remove last arrow when too many already attached + local children = {} - minetest.sound_play('x_bows_arrow_hit', { - pos = pointed_thing.under, - gain = 0.6, - max_hear_distance = 16 - }) + for _, object in ipairs(minetest.get_objects_inside_radius(pointed_thing.under, 1)) do + if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then + table.insert(children, object) + end + end - return - end - end - pointed_thing = ray:next() - end + if #children >= 5 then + children[#children]:remove() + end - self._old_pos = pos - end, -}) \ No newline at end of file + minetest.sound_play('x_bows_arrow_hit', { + pos = pointed_thing.under, + gain = 0.6, + max_hear_distance = 16 + }) + + return + end + end + pointed_thing = ray:next() + end + + self._old_pos = pos + end, + + ---@param self table + ---@param puncher ObjectRef|nil + ---@param time_from_last_punch number|integer|nil + ---@param tool_capabilities ToolCapabilitiesDef|nil + ---@param dir Vector + ---@param damage number|integer + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage) + local wood_sound_def = default.node_sound_wood_defaults() + + minetest.sound_play(wood_sound_def.dig.name, { + pos = self.object:get_pos(), + gain = wood_sound_def.dig.gain + }) + + return false + end, +}) diff --git a/init.lua b/init.lua index 67830cb..dc8db35 100644 --- a/init.lua +++ b/init.lua @@ -6,430 +6,629 @@ ItemStack = ItemStack--[[@as ItemStack]] vector = vector--[[@as Vector]] 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 bow_charged_timer = 0 --- main class +---x_bows main class +---@class XBows x_bows = { - pvp = minetest.settings:get_bool('enable_pvp') or false, - creative = minetest.settings:get_bool('creative_mode') or false, - mesecons = minetest.get_modpath('mesecons'), - hbhunger = minetest.get_modpath('hbhunger'), - playerphysics = minetest.get_modpath('playerphysics'), - player_monoids = minetest.get_modpath('player_monoids'), - registered_arrows = {}, - registered_bows = {}, - player_bow_sneak = {}, - settings = { - x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false) - } + pvp = minetest.settings:get_bool('enable_pvp') or false, + creative = minetest.settings:get_bool('creative_mode') or false, + mesecons = minetest.get_modpath('mesecons'), + hbhunger = minetest.get_modpath('hbhunger'), + playerphysics = minetest.get_modpath('playerphysics'), + player_monoids = minetest.get_modpath('player_monoids'), + registered_arrows = {}, + registered_bows = {}, + registered_quivers = {}, + player_bow_sneak = {}, + 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) - 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 ---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also ---@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 local function reset_charged_bow(player, includeWielded) - local _includeWielded = includeWielded or false - local inv = player:get_inventory() + local _includeWielded = includeWielded or false + local inv = player:get_inventory() - ---@cast inv InvRef - if inv:contains_item('main', 'x_bows:bow_wood_charged') then - local inv_list = inv:get_list('main') + if inv and inv:contains_item('main', 'x_bows:bow_wood_charged') then + local inv_list = inv:get_list('main') - for i, st in ipairs(inv_list) do - local reset = _includeWielded or player:get_wield_index() ~= i + for i, st in ipairs(inv_list) do + 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 - local item_meta = st:get_meta() - local arrow = item_meta:get_string('arrow') + if not st:is_empty() and x_bows.registered_bows[st:get_name()] and reset then + local item_meta = st:get_meta() + local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string'))) - -- return arrow - if arrow ~= '' and not x_bows.is_creative(player:get_player_name()) then - if inv:room_for_item('main', {name=arrow}) then - inv:add_item('main', arrow) - else - minetest.item_drop(ItemStack({name=arrow, count=1}), player, player:get_pos()) - end - end + -- return arrow + if arrow_itemstack and not x_bows.is_creative(player:get_player_name()) then + if inv:room_for_item('main', {name=arrow_itemstack:get_name()}) then + inv:add_item('main', arrow_itemstack:get_name()) + else + minetest.item_drop(ItemStack({name=arrow_itemstack:get_name(), count=1}), player, player:get_pos()) + end + end - -- reset bow to uncharged bow - inv:set_stack('main', i, ItemStack({ - name=x_bows.registered_bows[st:get_name()].name, - count=st:get_count(), - wear=st:get_wear() - })) - end - end - end + -- reset bow to uncharged bow + inv:set_stack('main', i, ItemStack({ + name=x_bows.registered_bows[st:get_name()].name, + count=st:get_count(), + wear=st:get_wear() + })) + end + end + end end minetest.register_on_joinplayer(function(player) - reset_charged_bow(player, true) + reset_charged_bow(player, true) + x_bows.quiver.close_quiver(player) end) +---Register bows +---@param name string +---@param def table +---@return boolean|nil function x_bows.register_bow(name, def) - if name == nil or name == '' then - return false - end + if name == nil or name == '' then + return false + end - def.name = 'x_bows:' .. name - def.name_charged = 'x_bows:' .. name .. '_charged' - def.description = def.description or name - def.uses = def.uses or 150 + def.name = 'x_bows:' .. name + def.name_charged = 'x_bows:' .. name .. '_charged' + def.description = def.description or name + 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 - minetest.register_tool(def.name, { - description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' - .. (1 / def.crit_chance) * 100 .. '%'), - inventory_image = def.inventory_image or 'x_bows_bow_wood.png', - -- on_use = function(itemstack, user, pointed_thing) - -- end, - on_place = x_bows.load, - on_secondary_use = x_bows.load, - groups = {bow = 1, flammable = 1}, - -- range = 0 - }) + -- not charged bow + minetest.register_tool(def.name, { + description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' + .. (1 / def.crit_chance) * 100 .. '%'), + inventory_image = def.inventory_image or 'x_bows_bow_wood.png', + on_place = x_bows.load, + on_secondary_use = x_bows.load, + groups = {bow = 1, flammable = 1} + }) - -- charged bow - minetest.register_tool(def.name_charged, { - description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' - .. (1 / def.crit_chance) * 100 .. '%'), - inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png', - on_use = x_bows.shoot, - groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1}, - on_drop = function(itemstack, dropper, pos) - local item_meta = itemstack:get_meta() - local arrow = item_meta:get_string('arrow') + -- charged bow + minetest.register_tool(def.name_charged, { + description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' + .. (1 / def.crit_chance) * 100 .. '%'), + inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png', + on_use = x_bows.shoot, + groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1}, + on_drop = function(itemstack, dropper, pos) + local item_meta = itemstack:get_meta() + local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string'))) - -- return arrow - if arrow ~= '' 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}) - end + -- return arrow + if arrow_itemstack and not x_bows.is_creative(dropper:get_player_name()) then + 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 - itemstack:set_name(def.name) - -- returns leftover itemstack - return minetest.item_drop(itemstack, dropper, pos) - end - }) + itemstack:set_name(def.name) + -- returns leftover itemstack + return minetest.item_drop(itemstack, dropper, pos) + end + }) - -- recipes - if def.recipe then - minetest.register_craft({ - output = def.name, - recipe = def.recipe - }) - end + -- recipes + if def.recipe then + minetest.register_craft({ + output = def.name, + recipe = def.recipe + }) + end end +---Register arrows +---@param name string +---@param def table +---@return boolean|nil function x_bows.register_arrow(name, def) - if name == nil or name == '' then - return false - end + if name == nil or name == '' then + return false + end - def.name = 'x_bows:' .. name - def.description = def.description or name + def.name = 'x_bows:' .. 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, { - description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: ' - .. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: ' - .. def.tool_capabilities.full_punch_interval .. 's'), - inventory_image = def.inventory_image, - groups = {arrow = 1, flammable = 1} - }) + minetest.register_craftitem('x_bows:' .. name, { + description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: ' + .. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: ' + .. def.tool_capabilities.full_punch_interval .. 's'), + short_description = def.description, + inventory_image = def.inventory_image, + groups = {arrow = 1, flammable = 1} + }) - -- recipes - if def.craft then - minetest.register_craft({ - output = def.name ..' ' .. (def.craft_count or 4), - recipe = def.craft - }) - end + -- recipes + if def.craft then + minetest.register_craft({ + output = def.name ..' ' .. (def.craft_count or 4), + recipe = def.craft + }) + 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) - local time_load = minetest.get_us_time() - local inv = user:get_inventory() - local inv_list = inv:get_list('main') - local bow_name = itemstack:get_name() - local bow_def = x_bows.registered_bows[bow_name .. '_charged'] - local itemstack_arrows = {} + local player_name = user:get_player_name() + local inv = user:get_inventory()--[[@as InvRef]] + local inv_list = inv:get_list('main') + local bow_name = itemstack:get_name() + local bow_def = x_bows.registered_bows[bow_name .. '_charged'] + ---@alias ItemStackArrows {["stack"]: ItemStack, ["idx"]: number|integer}[] + ---@type ItemStackArrows + local itemstack_arrows = {} - if pointed_thing.under then - local node = minetest.get_node(pointed_thing.under) - local node_def = minetest.registered_nodes[node.name] + ---trigger right click event if pointed item has one + 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, user, itemstack, pointed_thing) - end - end + if node_def and node_def.on_rightclick then + return node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing) + end + end - for _, st in ipairs(inv_list) do - if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then - table.insert(itemstack_arrows, st) - end - end + ---find itemstack arrow in quiver + local quiver_result = x_bows.quiver.get_itemstack_arrow_from_quiver(user) + local itemstack_arrow = quiver_result.found_arrow_stack - -- take 1st found arrow in the list - local itemstack_arrow = itemstack_arrows[1] + if itemstack_arrow then + local itemstack_arrow_meta = itemstack_arrow:get_meta() - if itemstack_arrow and bow_def then - local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities + itemstack_arrow_meta:set_int('is_arrow_from_quiver', 1) + 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) - local wielded_item = v_user:get_wielded_item() - local wielded_item_name = wielded_item:get_name() + ---find itemstack arrow in players inventory + for i, st in ipairs(inv_list) do + 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 - local meta = wielded_item:get_meta() + -- take 1st found arrow in the list + itemstack_arrow = #itemstack_arrows > 0 and itemstack_arrows[1].stack or nil + end - meta:set_string('arrow', itemstack_arrow:get_name()) - meta:set_string('time_load', tostring(v_time_load)) - wielded_item:set_name(v_bow_name .. '_charged') - v_user:set_wielded_item(wielded_item) + if itemstack_arrow and bow_def then + local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities - if not x_bows.is_creative(user:get_player_name()) then - inv:remove_item('main', itemstack_arrow:get_name()) - end - end - end, user, bow_name, time_load) + ---@param v_user ObjectRef + ---@param v_bow_name string + ---@param v_itemstack_arrow ItemStack + ---@param v_inv InvRef + ---@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 - -- @TODO: find a way to prevent this from playing when not fully charged - 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:get_name() == v_bow_name then + local wielded_item_meta = wielded_item:get_meta() + local v_itemstack_arrow_meta = v_itemstack_arrow:get_meta() - if wielded_item_name == v_bow_name .. '_charged' then - minetest.sound_play('x_bows_bow_loaded', { - to_player = user:get_player_name(), - gain = 0.6 - }) - end - end, user, bow_name) + wielded_item_meta:set_string('arrow_itemstack_string', minetest.serialize(v_itemstack_arrow:to_table())) + wielded_item_meta:set_string('time_load', tostring(minetest.get_us_time())) - minetest.sound_play('x_bows_bow_load', { - to_player = user:get_player_name(), - gain = 0.6 - }) + wielded_item:set_name(v_bow_name .. '_charged') + v_user:set_wielded_item(wielded_item) - return itemstack - end + if not x_bows.is_creative(v_user:get_player_name()) and v_itemstack_arrow_meta:get_int('is_arrow_from_quiver') ~= 1 then + 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 +---Shoots the bow +---@param itemstack ItemStack +---@param user ObjectRef +---@param pointed_thing? PointedThingDef +---@return ItemStack function x_bows.shoot(itemstack, user, pointed_thing) - local time_shoot = minetest.get_us_time(); - local meta = itemstack:get_meta() - local meta_arrow = meta:get_string('arrow') - local time_load = tonumber(meta:get_string('time_load')) - local tflp = (time_shoot - time_load) / 1000000 + local time_shoot = minetest.get_us_time(); + local meta = itemstack:get_meta() + local time_load = tonumber(meta:get_string('time_load')) + 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 - return itemstack - end + if is_arrow_from_quiver == 1 then + x_bows.quiver.udate_or_create_hud(user, detached_inv:get_list('main')) + else + x_bows.quiver.remove_hud(user) + end - local bow_name_charged = itemstack:get_name() - local bow_name = x_bows.registered_bows[bow_name_charged].name - local uses = x_bows.registered_bows[bow_name_charged].uses - local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance - local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities + if not x_bows.registered_arrows[arrow_name] then + return itemstack + end - local staticdata = { - arrow = meta_arrow, - user_name = user:get_player_name(), - is_critical_hit = false, - _tool_capabilities = _tool_capabilities, - _tflp = tflp, - } + local bow_name_charged = itemstack:get_name() + local bow_name = x_bows.registered_bows[bow_name_charged].name + local uses = x_bows.registered_bows[bow_name_charged].uses + local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance + local _tool_capabilities = x_bows.registered_arrows[arrow_name].tool_capabilities + local quiver_xbows_def = x_bows.registered_quivers[quiver_name] - -- crits, only on full punch interval - if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then - if math.random(1, crit_chance) == 1 then - staticdata.is_critical_hit = true - end - end + local staticdata = { + arrow = arrow_name, + user_name = user:get_player_name(), + is_critical_hit = false, + _tool_capabilities = _tool_capabilities, + _tflp = tflp, + } - local sound_name = 'x_bows_bow_shoot' - if staticdata.is_critical_hit then - sound_name = 'x_bows_bow_shoot_crit' - end + ---crits, only on full punch interval + if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then + if math.random(1, crit_chance) == 1 then + staticdata.is_critical_hit = true + end + end - meta:set_string('arrow', '') - itemstack:set_name(bow_name) + ---speed multiply + 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() - local dir = user:get_look_dir() - local obj = minetest.add_entity( - { - x = pos.x, - y = pos.y + 1.5, - z = pos.z - }, - 'x_bows:arrow_entity', - minetest.serialize(staticdata) - ) + ---add damage + if quiver_xbows_def and quiver_xbows_def.add_damage and quiver_xbows_def.add_damage > 1 then + staticdata.add_damage = quiver_xbows_def.add_damage + end - if not obj then - return itemstack - end + ---sound + local sound_name = 'x_bows_bow_shoot' + 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 - strength_multiplier = 1 - end + local pos = user:get_pos() + local dir = user:get_look_dir() + 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)) - obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3}) - obj:set_yaw(minetest.dir_to_yaw(dir)) + local strength_multiplier = tflp - if not x_bows.is_creative(user:get_player_name()) then - itemstack:add_wear(65535 / uses) - end + if strength_multiplier > _tool_capabilities.full_punch_interval then + strength_multiplier = 1 - minetest.sound_play(sound_name, { - gain = 0.3, - pos = user:get_pos(), - max_hear_distance = 10 - }) + ---faster arrow, only on full punch interval + if staticdata.faster_arrows_multiplier then + strength_multiplier = strength_multiplier + (strength_multiplier / staticdata.faster_arrows_multiplier) + 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 +---Arrow particle +---@param pos Vector +---@param type 'arrow' | 'arrow_crit' | 'bubble' | 'arrow_tipped' +---@return number|nil function x_bows.particle_effect(pos, type) - if type == 'arrow' then - return minetest.add_particlespawner({ - amount = 1, - time = 0.1, - minpos = pos, - maxpos = pos, - minexptime = 1, - maxexptime = 1, - minsize = 2, - maxsize = 2, - texture = 'x_bows_arrow_particle.png', - animation = { - type = 'vertical_frames', - aspect_w = 8, - aspect_h = 8, - length = 1, - }, - glow = 1 - }) - elseif type == 'arrow_crit' then - return minetest.add_particlespawner({ - amount = 3, - time = 0.1, - minpos = pos, - maxpos = pos, - minexptime = 0.5, - maxexptime = 0.5, - minsize = 2, - maxsize = 2, - texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127', - animation = { - type = 'vertical_frames', - aspect_w = 8, - aspect_h = 8, - length = 1, - }, - glow = 1 - }) - elseif type == 'bubble' then - return minetest.add_particlespawner({ - amount = 1, - time = 1, - minpos = pos, - maxpos = pos, - minvel = {x=1, y=1, z=0}, - maxvel = {x=1, y=1, z=0}, - minacc = {x=1, y=1, z=1}, - maxacc = {x=1, y=1, z=1}, - minexptime = 0.2, - maxexptime = 0.5, - minsize = 0.5, - maxsize = 1, - texture = 'x_bows_bubble.png' - }) - elseif type == 'arrow_tipped' then - return minetest.add_particlespawner({ - amount = 5, - time = 1, - 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 + if type == 'arrow' then + return minetest.add_particlespawner({ + amount = 1, + time = 0.1, + minpos = pos, + maxpos = pos, + minexptime = 1, + maxexptime = 1, + minsize = 2, + maxsize = 2, + texture = 'x_bows_arrow_particle.png', + animation = { + type = 'vertical_frames', + aspect_w = 8, + aspect_h = 8, + length = 1, + }, + glow = 1 + }) + elseif type == 'arrow_crit' then + return minetest.add_particlespawner({ + amount = 3, + time = 0.1, + minpos = pos, + maxpos = pos, + minexptime = 0.5, + maxexptime = 0.5, + minsize = 2, + maxsize = 2, + texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127', + animation = { + type = 'vertical_frames', + aspect_w = 8, + aspect_h = 8, + length = 1, + }, + glow = 1 + }) + elseif type == 'arrow_fast' then + return minetest.add_particlespawner({ + amount = 3, + time = 0.1, + minpos = pos, + maxpos = pos, + minexptime = 0.5, + maxexptime = 0.5, + minsize = 2, + maxsize = 2, + texture = 'x_bows_arrow_particle.png^[colorize:#0000FF:64', + animation = { + type = 'vertical_frames', + aspect_w = 8, + aspect_h = 8, + length = 1, + }, + glow = 1 + }) + elseif type == 'bubble' then + return minetest.add_particlespawner({ + amount = 1, + time = 1, + minpos = pos, + maxpos = pos, + minvel = {x=1, y=1, z=0}, + maxvel = {x=1, y=1, z=0}, + minacc = {x=1, y=1, z=1}, + maxacc = {x=1, y=1, z=1}, + minexptime = 0.2, + maxexptime = 0.5, + minsize = 0.5, + maxsize = 1, + texture = 'x_bows_bubble.png' + }) + elseif type == 'arrow_tipped' then + return minetest.add_particlespawner({ + amount = 5, + time = 1, + 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 -- sneak, fov adjustments when bow is charged 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 - for _, player in ipairs(minetest.get_connected_players()) do - local name = player:get_player_name() - local stack = player:get_wielded_item() - local item = stack:get_name() + if bow_charged_timer > 0.5 then + for _, player in ipairs(minetest.get_connected_players()) do + local name = player:get_player_name() + local stack = player:get_wielded_item() + local item = stack:get_name() - if not item then - return - end + if not item then + return + end - if not x_bows.player_bow_sneak[name] then - x_bows.player_bow_sneak[name] = {} - end + if not x_bows.player_bow_sneak[name] then + x_bows.player_bow_sneak[name] = {} + end - if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then - if x_bows.playerphysics then - playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25) - elseif x_bows.player_monoids then - player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_wood_charged') - end + if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then + if x_bows.playerphysics then + playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25) + elseif x_bows.player_monoids then + player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_wood_charged') + end - x_bows.player_bow_sneak[name].sneak = true - player:set_fov(0.9, true, 0.4) - elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then - if x_bows.playerphysics then - playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged') - elseif x_bows.player_monoids then - player_monoids.speed:del_change(player, 'x_bows:bow_wood_charged') - end + x_bows.player_bow_sneak[name].sneak = true + player:set_fov(0.9, true, 0.4) + elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then + if x_bows.playerphysics then + playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged') + elseif x_bows.player_monoids then + player_monoids.speed:del_change(player, 'x_bows:bow_wood_charged') + end - x_bows.player_bow_sneak[name].sneak = false - player:set_fov(0, true, 0.4) - end + x_bows.player_bow_sneak[name].sneak = false + player:set_fov(0, true, 0.4) + end - reset_charged_bow(player) - end + reset_charged_bow(player) + end - bow_charged_timer = 0 - end + bow_charged_timer = 0 + end end) local path = minetest.get_modpath('x_bows') @@ -437,6 +636,7 @@ local path = minetest.get_modpath('x_bows') dofile(path .. '/nodes.lua') dofile(path .. '/arrow.lua') dofile(path .. '/items.lua') +dofile(path .. '/quiver.lua') local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000 diff --git a/items.lua b/items.lua index 24c2050..5831cf2 100644 --- a/items.lua +++ b/items.lua @@ -1,130 +1,149 @@ x_bows.register_bow('bow_wood', { - description = 'Wooden Bow', - uses = 385, - -- `crit_chance` 10% chance, 5 is 20% chance - -- (1 / crit_chance) * 100 = % chance - crit_chance = 10, - recipe = { - {'', 'default:stick', 'farming:string'}, - {'default:stick', '', 'farming:string'}, - {'', 'default:stick', 'farming:string'}, - } + description = 'Wooden Bow', + uses = 385, + -- `crit_chance` 10% chance, 5 is 20% chance + -- (1 / crit_chance) * 100 = % chance + crit_chance = 10, + recipe = { + {'', 'default:stick', 'farming:string'}, + {'default:stick', '', 'farming:string'}, + {'', 'default:stick', 'farming:string'}, + } }) x_bows.register_arrow('arrow_wood', { - description = 'Arrow Wood', - inventory_image = 'x_bows_arrow_wood.png', - craft = { - {'default:flint'}, - {'group:stick'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 1, - max_drop_level = 0, - damage_groups = {fleshy=2} - } + description = 'Arrow Wood', + inventory_image = 'x_bows_arrow_wood.png', + craft = { + {'default:flint'}, + {'group:stick'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 1, + max_drop_level = 0, + damage_groups = {fleshy=2} + } }) x_bows.register_arrow('arrow_stone', { - description = 'Arrow Stone', - inventory_image = 'x_bows_arrow_stone.png', - craft = { - {'default:flint'}, - {'group:stone'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 1.2, - max_drop_level = 0, - damage_groups = {fleshy=4} - } + description = 'Arrow Stone', + inventory_image = 'x_bows_arrow_stone.png', + craft = { + {'default:flint'}, + {'group:stone'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 1.2, + max_drop_level = 0, + damage_groups = {fleshy=4} + } }) x_bows.register_arrow('arrow_bronze', { - description = 'Arrow Bronze', - inventory_image = 'x_bows_arrow_bronze.png', - craft = { - {'default:flint'}, - {'default:bronze_ingot'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 0.8, - max_drop_level = 1, - damage_groups = {fleshy=6} - } + description = 'Arrow Bronze', + inventory_image = 'x_bows_arrow_bronze.png', + craft = { + {'default:flint'}, + {'default:bronze_ingot'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 0.8, + max_drop_level = 1, + damage_groups = {fleshy=6} + } }) x_bows.register_arrow('arrow_steel', { - description = 'Arrow Steel', - inventory_image = 'x_bows_arrow_steel.png', - craft = { - {'default:flint'}, - {'default:steel_ingot'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 0.7, - max_drop_level = 1, - damage_groups = {fleshy=6} - } + description = 'Arrow Steel', + inventory_image = 'x_bows_arrow_steel.png', + craft = { + {'default:flint'}, + {'default:steel_ingot'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = {fleshy=6} + } }) x_bows.register_arrow('arrow_mese', { - description = 'Arrow Mese', - inventory_image = 'x_bows_arrow_mese.png', - craft = { - {'default:flint'}, - {'default:mese_crystal'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 0.7, - max_drop_level = 1, - damage_groups = {fleshy=7} - } + description = 'Arrow Mese', + inventory_image = 'x_bows_arrow_mese.png', + craft = { + {'default:flint'}, + {'default:mese_crystal'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = {fleshy=7} + } }) x_bows.register_arrow('arrow_diamond', { - description = 'Arrow Diamond', - inventory_image = 'x_bows_arrow_diamond.png', - craft = { - {'default:flint'}, - {'default:diamond'}, - {'group:wool'} - }, - tool_capabilities = { - full_punch_interval = 0.7, - max_drop_level = 1, - damage_groups = {fleshy=8} - } + description = 'Arrow Diamond', + inventory_image = 'x_bows_arrow_diamond.png', + craft = { + {'default:flint'}, + {'default:diamond'}, + {'group:wool'} + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = {fleshy=8} + } }) x_bows.register_arrow('arrow_diamond_tipped_poison', { - description = 'Arrow Diamond Tipped Poison (0:05)', - inventory_image = 'x_bows_arrow_diamond_poison.png', - craft = { - {'', '', ''}, - {'', 'default:marram_grass_1', ''}, - {'', 'x_bows:arrow_diamond', ''} - }, - tool_capabilities = { - full_punch_interval = 0.7, - max_drop_level = 1, - damage_groups = {fleshy=8} - }, - craft_count = 1 + description = 'Arrow Diamond Tipped Poison (0:05)', + inventory_image = 'x_bows_arrow_diamond_poison.png', + craft = { + {'', '', ''}, + {'', 'default:marram_grass_1', ''}, + {'', 'x_bows:arrow_diamond', ''} + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = {fleshy=8} + }, + 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({ - type = 'fuel', - recipe = 'x_bows:bow_wood', - burntime = 3, + type = 'fuel', + recipe = 'x_bows:bow_wood', + burntime = 3, }) minetest.register_craft({ - type = 'fuel', - recipe = 'x_bows:arrow_wood', - burntime = 1, + type = 'fuel', + recipe = 'x_bows:arrow_wood', + burntime = 1, +}) + +minetest.register_craft({ + type = 'fuel', + recipe = 'x_bows:quiver', + burntime = 3, }) diff --git a/mod.conf b/mod.conf index ef303d9..6ddd851 100644 --- a/mod.conf +++ b/mod.conf @@ -1,6 +1,6 @@ name = x_bows description = Adds bow and arrows to Minetest. 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 -min_minetest_version = 5.4 \ No newline at end of file +min_minetest_version = 5.4 diff --git a/models/x_bows_arrow.obj b/models/x_bows_arrow.obj new file mode 100644 index 0000000..e04a169 --- /dev/null +++ b/models/x_bows_arrow.obj @@ -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 diff --git a/nodes.lua b/nodes.lua index 9c8200c..a6a1278 100644 --- a/nodes.lua +++ b/nodes.lua @@ -1,54 +1,38 @@ minetest.register_node('x_bows:arrow_node', { - drawtype = 'nodebox', - node_box = { - type = 'fixed', - fixed = { - {-0.1875, 0, -0.5, 0.1875, 0, 0.5}, - {0, -0.1875, -0.5, 0, 0.1875, 0.5}, - {-0.5, -0.5, -0.5, 0.5, 0.5, -0.5}, - } - }, - -- 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} + drawtype = 'mesh', + mesh = 'x_bows_arrow.obj', + tiles = {'x_bows_arrow_mesh.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', { - description = 'Straw', - tiles = {'x_bows_target.png'}, - is_ground_content = false, - groups = {snappy=3, flammable=4, fall_damage_add_percent=-30}, - sounds = default.node_sound_leaves_defaults(), - mesecons = {receptor = {state = 'off'}}, - on_timer = function (pos, elapsed) - mesecon.receptor_off(pos) - return false - end, + description = 'Straw', + tiles = {'x_bows_target.png'}, + is_ground_content = false, + groups = {snappy=3, flammable=4, fall_damage_add_percent=-30}, + sounds = default.node_sound_leaves_defaults(), + mesecons = {receptor = {state = 'off'}}, + on_timer = function (pos, elapsed) + mesecon.receptor_off(pos) + return false + end, }) minetest.register_craft({ - type = 'fuel', - recipe = 'x_bows:target', - burntime = 3, + type = 'fuel', + recipe = 'x_bows:target', + burntime = 3, }) minetest.register_craft({ - output = 'x_bows:target', - recipe = { - {'', 'default:mese_crystal', ''}, - {'default:mese_crystal', 'farming:straw', 'default:mese_crystal'}, - {'', 'default:mese_crystal', ''}, - } + output = 'x_bows:target', + recipe = { + {'', 'default:mese_crystal', ''}, + {'default:mese_crystal', 'farming:straw', 'default:mese_crystal'}, + {'', 'default:mese_crystal', ''}, + } }) diff --git a/quiver.lua b/quiver.lua new file mode 100644 index 0000000..ff04ebf --- /dev/null +++ b/quiver.lua @@ -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) diff --git a/screenshot.1.png b/screenshot.1.png new file mode 100644 index 0000000..a6bf611 Binary files /dev/null and b/screenshot.1.png differ diff --git a/screenshot.2.png b/screenshot.2.png new file mode 100644 index 0000000..bff8fbf Binary files /dev/null and b/screenshot.2.png differ diff --git a/screenshot.3.png b/screenshot.3.png new file mode 100644 index 0000000..adc2b26 Binary files /dev/null and b/screenshot.3.png differ diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index c5fd447..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/sounds/x_bows_quiver.1.ogg b/sounds/x_bows_quiver.1.ogg new file mode 100644 index 0000000..4413a41 Binary files /dev/null and b/sounds/x_bows_quiver.1.ogg differ diff --git a/sounds/x_bows_quiver.2.ogg b/sounds/x_bows_quiver.2.ogg new file mode 100644 index 0000000..23cc781 Binary files /dev/null and b/sounds/x_bows_quiver.2.ogg differ diff --git a/sounds/x_bows_quiver.3.ogg b/sounds/x_bows_quiver.3.ogg new file mode 100644 index 0000000..0e90209 Binary files /dev/null and b/sounds/x_bows_quiver.3.ogg differ diff --git a/sounds/x_bows_quiver.4.ogg b/sounds/x_bows_quiver.4.ogg new file mode 100644 index 0000000..cf38cd9 Binary files /dev/null and b/sounds/x_bows_quiver.4.ogg differ diff --git a/sounds/x_bows_quiver.5.ogg b/sounds/x_bows_quiver.5.ogg new file mode 100644 index 0000000..71df409 Binary files /dev/null and b/sounds/x_bows_quiver.5.ogg differ diff --git a/sounds/x_bows_quiver.6.ogg b/sounds/x_bows_quiver.6.ogg new file mode 100644 index 0000000..9fadd5b Binary files /dev/null and b/sounds/x_bows_quiver.6.ogg differ diff --git a/sounds/x_bows_quiver.7.ogg b/sounds/x_bows_quiver.7.ogg new file mode 100644 index 0000000..d8b6f3b Binary files /dev/null and b/sounds/x_bows_quiver.7.ogg differ diff --git a/sounds/x_bows_quiver.8.ogg b/sounds/x_bows_quiver.8.ogg new file mode 100644 index 0000000..289513b Binary files /dev/null and b/sounds/x_bows_quiver.8.ogg differ diff --git a/sounds/x_bows_quiver.9.ogg b/sounds/x_bows_quiver.9.ogg new file mode 100644 index 0000000..127d194 Binary files /dev/null and b/sounds/x_bows_quiver.9.ogg differ diff --git a/textures/x_bows_arrow_mesh.png b/textures/x_bows_arrow_mesh.png new file mode 100644 index 0000000..3ba03bb Binary files /dev/null and b/textures/x_bows_arrow_mesh.png differ diff --git a/textures/x_bows_arrow_slot.png b/textures/x_bows_arrow_slot.png new file mode 100644 index 0000000..579b6f3 Binary files /dev/null and b/textures/x_bows_arrow_slot.png differ diff --git a/textures/x_bows_arrow_tile_point_bottom.png b/textures/x_bows_arrow_tile_point_bottom.png deleted file mode 100644 index 6cf6f9c..0000000 Binary files a/textures/x_bows_arrow_tile_point_bottom.png and /dev/null differ diff --git a/textures/x_bows_arrow_tile_point_left.png b/textures/x_bows_arrow_tile_point_left.png deleted file mode 100644 index f407440..0000000 Binary files a/textures/x_bows_arrow_tile_point_left.png and /dev/null differ diff --git a/textures/x_bows_arrow_tile_point_right.png b/textures/x_bows_arrow_tile_point_right.png deleted file mode 100644 index 28ef4c6..0000000 Binary files a/textures/x_bows_arrow_tile_point_right.png and /dev/null differ diff --git a/textures/x_bows_arrow_tile_point_top.png b/textures/x_bows_arrow_tile_point_top.png deleted file mode 100644 index 67a0bc6..0000000 Binary files a/textures/x_bows_arrow_tile_point_top.png and /dev/null differ diff --git a/textures/x_bows_arrow_tile_tail.png b/textures/x_bows_arrow_tile_tail.png deleted file mode 100644 index 6790099..0000000 Binary files a/textures/x_bows_arrow_tile_tail.png and /dev/null differ diff --git a/textures/x_bows_quiver.png b/textures/x_bows_quiver.png new file mode 100644 index 0000000..c026ad2 Binary files /dev/null and b/textures/x_bows_quiver.png differ diff --git a/textures/x_bows_quiver_hotbar.png b/textures/x_bows_quiver_hotbar.png new file mode 100644 index 0000000..b664084 Binary files /dev/null and b/textures/x_bows_quiver_hotbar.png differ diff --git a/textures/x_bows_quiver_hotbar_selected.png b/textures/x_bows_quiver_hotbar_selected.png new file mode 100644 index 0000000..0666240 Binary files /dev/null and b/textures/x_bows_quiver_hotbar_selected.png differ diff --git a/textures/x_bows_quiver_open.png b/textures/x_bows_quiver_open.png new file mode 100644 index 0000000..cdb08dc Binary files /dev/null and b/textures/x_bows_quiver_open.png differ diff --git a/types/entity.type.lua b/types/entity.type.lua index b218648..96afdc1 100644 --- a/types/entity.type.lua +++ b/types/entity.type.lua @@ -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 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. - ----Entity definition ----@class ObjectProperties diff --git a/types/generic.type.lua b/types/generic.type.lua index 78fd79d..4f29264 100644 --- a/types/generic.type.lua +++ b/types/generic.type.lua @@ -1,7 +1,3 @@ ---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: `{}` diff --git a/types/inventory.type.lua b/types/inventory.type.lua index 5b755d0..442fc91 100644 --- a/types/inventory.type.lua +++ b/types/inventory.type.lua @@ -17,4 +17,4 @@ ---@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 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 diff --git a/types/itemstack.type.lua b/types/itemstack.type.lua index 7aa5284..c3d516f 100644 --- a/types/itemstack.type.lua +++ b/types/itemstack.type.lua @@ -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_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 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 name string ---@field count integer diff --git a/types/meta.type.lua b/types/meta.type.lua index ccfd03f..234ce74 100644 --- a/types/meta.type.lua +++ b/types/meta.type.lua @@ -12,7 +12,7 @@ ---@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 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 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) diff --git a/types/minetest.type.lua b/types/minetest.type.lua index 213f66f..faf4e09 100644 --- a/types/minetest.type.lua +++ b/types/minetest.type.lua @@ -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 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 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:"` 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 ---@class MinetestSettings @@ -83,6 +88,15 @@ ---@field param1 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 ---@class JobTable ---@field cancel fun(self: JobTable) Cancels the job function from being called diff --git a/types/mtg-default.type.lua b/types/mtg-default.type.lua index c05483d..884202b 100644 --- a/types/mtg-default.type.lua +++ b/types/mtg-default.type.lua @@ -29,6 +29,7 @@ ---@field node_sound_glass_defaults fun(table?: NodeSoundDef): NodeSoundDef ---@field node_sound_metal_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 ---@class RegisterLeafdecayDef diff --git a/types/node.type.lua b/types/node.type.lua index 5f4ad3f..8290fc3 100644 --- a/types/node.type.lua +++ b/types/node.type.lua @@ -13,6 +13,10 @@ ---@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 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. ---@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 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. + +---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"}},` diff --git a/types/object.type.lua b/types/object.type.lua index 5301922..2e97551 100644 --- a/types/object.type.lua +++ b/types/object.type.lua @@ -1,6 +1,6 @@ ---https://github.com/sumneko/lua-language-server/wiki ----@alias ObjectRef ObjectRefAbstract|ObjectRefLuaEntityRef +---@alias ObjectRef ObjectRefAbstract | ObjectRefLuaEntityRef ---Moving things in the game are generally these. ---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 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 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. ---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 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. + +---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. diff --git a/types/sound.type.lua b/types/sound.type.lua index 93707a6..a4abbc3 100644 --- a/types/sound.type.lua +++ b/types/sound.type.lua @@ -8,6 +8,7 @@ --- Definition of node sounds to be played at various events. ---@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 dig SimpleSoundSpec|'__group' While digging node. If `"__group"`, then the sound will be `default_dig_`, where `` 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