update x_bows #3684

Closed
opened 2023-01-31 00:59:51 +00:00 by flux · 11 comments
Member

desired for 1.1.118

desired for 1.1.118
flux added this to the 1.1.117.1 milestone 2023-01-31 00:59:52 +00:00
flux added the
1. kind/enhancement
label 2023-01-31 00:59:52 +00:00
Author
Member

our own diff since we've started making custom changes:

```diff diff --git a/alias.lua b/alias.lua new file mode 100644 index 0000000..31bdce4 --- /dev/null +++ b/alias.lua @@ -0,0 +1,24 @@ +-- bows + +minetest.register_alias("bows:bow_wood", "x_bows:bow_wood") +minetest.register_alias("bows:bow_steel", "x_bows:bow_steel") +minetest.register_alias("bows:bow_bronze", "x_bows:bow_steel") +minetest.register_alias("bows:bow_bowie", "x_bows:bow_wood") + +minetest.register_alias("bows:arrow", "x_bows:arrow_wood") +minetest.register_alias("bows:arrow_steel", "x_bows:arrow_steel") +minetest.register_alias("bows:arrow_mese", "x_bows:arrow_mese") +minetest.register_alias("bows:arrow_diamond", "x_bows:arrow_diamond") + +-- projectile + +minetest.register_alias("projectile:bow", "x_bows:bow_wood") +minetest.register_alias("projectile:steel_bow", "x_bows:bow_steel") + +minetest.register_alias("projectile:arrow", "x_bows:arrow_steel") +minetest.register_alias("projectile:arrow_high_velocity", "x_bows:arrow_diamond") +minetest.register_alias("projectile:arrow_fire", "x_bows:arrow_fire") + +minetest.register_alias("projectile:slingshot", "x_bows:slingshot_wood") +minetest.register_alias("projectile:steel_slingshot", "x_bows:slingshot_steel") +minetest.register_alias("projectile:rock", "x_bows:ball_rock") \ No newline at end of file diff --git a/arrow.lua b/arrow.lua index 9f4e158..c6e6db6 100644 --- a/arrow.lua +++ b/arrow.lua @@ -29,6 +29,7 @@ local function get_obj_box(obj) return box end

+--[[
-- Poison Arrow Effects
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
@@ -39,8 +40,10 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_

time_left = time_left + tick
  • local poison_job = nil
  • 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)
    
  •   poison_job = 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
    @@ -70,8 +73,14 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_

    local _damage = punch_def.tool_capabilities.damage_groups.fleshy
    if target_obj:get_hp() - _damage > 0 then

  •   local shooter = minetest.get_player_by_name(arrow_obj.user_name)
    
  •   if not shooter then
    
  •   	-- pvp: shooter is offline, stop the poinson effect
    
  •   	if poison_job then poison_job.cancel() end
    
  •   	return
    
  •   end
      target_obj:punch(
    
  •   	punch_def.puncher,
    
  •   	shooter,
      	punch_def.time_from_last_punch,
      	punch_def.tool_capabilities
      )
    

@@ -83,6 +92,7 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_
end
end
end
+]]--

-- Main Arrow Entity
minetest.register_entity('x_bows:arrow_entity', {
@@ -93,7 +103,7 @@ minetest.register_entity('x_bows:arrow_entity', {
selectionbox = {0, 0, 0, 0, 0, 0},
physical = false,
textures = {'air'},

  •   hp_max = 0.5
    
  •   hp_max = 1
    

    },

    on_activate = function(self, staticdata)
    @@ -103,7 +113,6 @@ minetest.register_entity('x_bows:arrow_entity', {
    end

      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
    

@@ -120,17 +129,19 @@ minetest.register_entity('x_bows:arrow_entity', {
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.user_name = _staticdata.user_name
      self._tflp = _staticdata._tflp
      self._tool_capabilities = _staticdata._tool_capabilities
      self._is_critical_hit = _staticdata.is_critical_hit
    
  •   self._effects = _staticdata._effects or {}
    
  •   --[[
      if self.arrow == 'x_bows:arrow_diamond_tipped_poison' then
      	self._poison_arrow = true
      end
    
  •   ]]--
    
      self.object:set_properties({
    
  •   	textures = {'x_bows:arrow_node'},
    
  •   	textures = {self.arrow..'_node'},
      	infotext = self.arrow
      })
    

    end,
    @@ -177,7 +188,9 @@ minetest.register_entity('x_bows:arrow_entity', {
    self._has_particles = true

      	if self._tflp >= self._tool_capabilities.full_punch_interval then
    
  •   		if self._is_critical_hit then
    
  •   		if self._effects and self._effects.fire == "fire" then
    
  •   			x_bows.particle_effect(self._old_pos, 'fire')
    
  •   		elseif self._is_critical_hit then
      			x_bows.particle_effect(self._old_pos, 'arrow_crit')
      		else
      			x_bows.particle_effect(self._old_pos, 'arrow')
    

@@ -221,17 +234,17 @@ minetest.register_entity('x_bows:arrow_entity', {
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 ((pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= self.user_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(),
    
  •   				to_player = self.user_name,
      				gain = 0.3
      			})
      		else
      			minetest.sound_play('x_bows_arrow_hit', {
    
  •   				to_player = self.user:get_player_name(),
    
  •   				to_player = self.user_name,
      				gain = 0.6
      			})
      		end
    

@@ -262,9 +275,10 @@ minetest.register_entity('x_bows:arrow_entity', {
if self._is_critical_hit then
_damage = _damage * 2
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,
    

@@ -279,18 +293,25 @@ minetest.register_entity('x_bows:arrow_entity', {
_damage
)

  •   		-- TODO check pvp and don't push players around?
      		pointed_thing.ref:add_velocity({
      			x = dir.x * knockback * -1,
      			y = 7,
      			z = dir.z * knockback * -1
      		})
    
  •   		]]--
    
  •   		-- pvp: arrows shot by offline players will do no damage
    
  •   		local shooter = minetest.get_player_by_name(self.user_name)
    
  •   		if not shooter then
    
  •   			self.object:remove()
    
  •   			return
    
  •   		end
      		pointed_thing.ref:punch(
    
  •   			self.object,
    
  •   			shooter,
      			self._tflp,
      			{
      				full_punch_interval = self._tool_capabilities.full_punch_interval,
    
  •   				damage_groups = {fleshy = _damage, knockback = knockback}
    
  •   				damage_groups = {fleshy = _damage}
      			},
      			{
      				x = dir.x * -1,
    

@@ -387,6 +408,7 @@ minetest.register_entity('x_bows:arrow_entity', {
position.z = zmin / 10
end

  •   		--[[
      		-- poison arrow
      		if self._poison_arrow then
      			local old_damage_texture_modifier = pointed_thing.ref:get_properties().damage_texture_modifier
    

@@ -414,6 +436,7 @@ minetest.register_entity('x_bows:arrow_entity', {
-- end
end
end

  •   		]]--
    
      		if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then
      			self.object:remove()
    

@@ -475,6 +498,15 @@ minetest.register_entity('x_bows:arrow_entity', {

				-- only close to the center of the target will trigger signal
				if distance < 0.54 then
  •   				minetest.sound_play('x_bows_arrow_successful_hit', {
    
  •   					pos = pointed_thing.under,
    
  •   					gain = 0.6,
    
  •   					max_hear_distance = 32,
    
  •   					exclude_player = self.user_name,
    
  •   				}, true)
    
  •   				minetest.sound_play('x_bows_arrow_successful_hit', {
    
  •   					to_player = self.user_name,
    
  •   				}, true)
      				mesecon.receptor_on(pointed_thing.under)
      				minetest.get_node_timer(pointed_thing.under):start(2)
      			end
    

@@ -490,6 +522,12 @@ minetest.register_entity('x_bows:arrow_entity', {
self._attached_to.pos = pointed_thing.under
self.object:set_properties({collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}})

  •   			-- Apply fire effect
    
  •   			if self._effects and self._effects.fire == "fire" and not core.is_protected(pointed_thing.above,"") and core.get_node(pointed_thing.above).name == "air" then
    
  •   				minetest.set_node(pointed_thing.above, {name = "fire:basic_flame"})
    
  •   				return
    
  •   			end
    
  •   			-- remove last arrow when too many already attached
      			local children = {}
    

@@ -517,4 +555,4 @@ minetest.register_entity('x_bows:arrow_entity', {

	self._old_pos = pos
end,

-})
\ No newline at end of file
+})
diff --git a/init.lua b/init.lua
index c244a93..424aa5c 100644
--- a/init.lua
+++ b/init.lua
@@ -1,6 +1,12 @@
+core.log('action','[Mod] loading x_bows')
local mod_start_time = minetest.get_us_time()
+
local bow_charged_timer = 0

+local wielditem_now={}
+local wielditem_last={}
+local wielditem_shot={}
+
x_bows = {
pvp = minetest.settings:get_bool('enable_pvp') or false,
creative = minetest.settings:get_bool('creative_mode') or false,
@@ -18,21 +24,26 @@ function x_bows.is_creative(name)
return x_bows.creative or minetest.check_player_privs(name, {creative = true})
end

+function x_bows.on_drop(itemstack, dropper, pos)

  • return nil
    +end

function x_bows.register_bow(name, def)
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.strength = def.strength or 10

  • def.ammotype = def.ammotype or "none"

    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 .. '%'),
    
  •   description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. def.crit_chance .. '%'),
      inventory_image = def.inventory_image or 'x_bows_bow_wood.png',
      -- on_use = function(itemstack, user, pointed_thing)
      -- end,
    

@@ -44,9 +55,10 @@ function x_bows.register_bow(name, def)

-- charged bow
minetest.register_tool(def.name_charged, {
  •   description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. (1 / def.crit_chance) * 100 .. '%'),
    
  •   description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. def.crit_chance .. '%'),
      inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png',
      on_use = x_bows.shoot,
    
  •   on_drop = x_bows.on_drop,
      groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1},
    
    })

@@ -63,9 +75,9 @@ function x_bows.register_arrow(name, def)
if name == nil or name == '' then
return false
end

def.name = 'x_bows:' .. name
def.description = def.description or name
  • def.ammotype = def.ammotype or "none"

    x_bows.registered_arrows[def.name] = def

@@ -82,6 +94,85 @@ function x_bows.register_arrow(name, def)
recipe = def.craft
})
end
+

  • -- arrow node
  • minetest.register_node(def.name..'_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 = def.textures or {
    
  •   	'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}
    
  • })
    +end

+function x_bows.unload(itemstack, user, pointed_thing)

  • local meta = itemstack:get_meta()
  • local inv = user:get_inventory()
  • local inv_list = inv:get_list('main')
  • local meta_arrow = meta:get_string('arrow')
  • local bow_name = itemstack:get_name()
  • local bow_def = x_bows.registered_bows[bow_name]

+-- Unload the arrow

  • local found_arrow, found_empty = nil
  • for k, st in ipairs(inv_list) do
  •   if not found_arrow and st:get_free_space() >= 1 and x_bows.registered_arrows[st:get_name()] and st:get_name() == meta_arrow then
    
  •   	found_arrow = k
    
  •   end
    
  •   if not found_empty and st:get_free_space() >= 1 and st:get_name() == "" then
    
  •   	found_empty = k
    
  •   end
    
  • end
  • if found_arrow then
  •   -- add +1 arrow to the itemstack
    
  •   local c = inv:get_stack("main", found_arrow)
    
  •   c:set_count(c:get_count()+1)
    
  •   inv:set_stack("main", found_arrow, c)
    
  • elseif found_empty then
  •   -- add arrow to the empty place
    
  •   local as = ItemStack(meta_arrow)
    
  •   inv:set_stack("main", found_empty, as)
    
  • else
  •   -- No space, drop arrow
    
  •   if not x_bows.is_creative(user:get_player_name()) then
    
  •   	local pos = user:get_pos()
    
  •   	local as = ItemStack(meta_arrow)
    
  •   	minetest.item_drop(as, nil, pos)
    
  •   	core.log("[x_bows] Arrow "..meta_arrow.." dropped at "..core.pos_to_string(pos))
    
  •   end
    
  • end

+-- Unload the bow
+

  • for k, st in ipairs(inv_list) do
  •   if x_bows.registered_bows[st:get_name()] then
    
  •   	meta:set_string('arrow', '')
    
  •   	itemstack:set_name(bow_def.name)
    
  •   	inv:set_stack("main", k, itemstack)
    
  •   end
    
  • end
  • return true
    end

function x_bows.load(itemstack, user, pointed_thing)
@@ -101,8 +192,10 @@ function x_bows.load(itemstack, user, pointed_thing)
end
end

  • if not bow_def then core.log("error","[MOD] x_bows: no bow_def") return end
  • for k, st in ipairs(inv_list) do
  •   if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then
    
  •   if not st:is_empty() and x_bows.registered_arrows[st:get_name()] and bow_def.ammotype == x_bows.registered_arrows[st:get_name()].ammotype then
      	table.insert(itemstack_arrows, st)
      end
    

    end
    @@ -169,7 +262,9 @@ function x_bows.shoot(itemstack, user, pointed_thing)
    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 bow_strength = x_bows.registered_bows[bow_name_charged].strength
    local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities

  • local _effects = x_bows.registered_arrows[meta_arrow].effects

    local staticdata = {
    arrow = meta_arrow,
    @@ -177,11 +272,12 @@ function x_bows.shoot(itemstack, user, pointed_thing)
    is_critical_hit = false,
    _tool_capabilities = _tool_capabilities,
    _tflp = tflp,

  •   _effects = _effects
    

    }

    -- 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
    
  •   if math.random()*100 <= crit_chance then
      	staticdata.is_critical_hit = true
      end
    

    end
    @@ -196,20 +292,28 @@ function x_bows.shoot(itemstack, user, pointed_thing)

    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 eye_height = user:get_properties().eye_height

  • local arrow_pos = {x = pos.x, y = pos.y + eye_height, z = pos.z}

  • local eye_offset = vector.multiply(user:get_eye_offset(), 0.1)

  • local yaw = user:get_look_horizontal()

  • arrow_pos = vector.add(arrow_pos, vector.rotate_around_axis(eye_offset, {x=0,y=1,z=0}, yaw))

  • local obj = minetest.add_entity(arrow_pos, 'x_bows:arrow_entity', minetest.serialize(staticdata))

    if not obj then
    return itemstack
    end

  • wielditem_shot[user:get_player_name()] = true -- needed to determine whether we switched away from the item or we actually shot

  • local lua_ent = obj:get_luaentity()

  • lua_ent._old_pos = arrow_pos
    local strength_multiplier = tflp

  • if strength_multiplier > _tool_capabilities.full_punch_interval then
  • if strength_multiplier < 0 or strength_multiplier > _tool_capabilities.full_punch_interval then
    strength_multiplier = 1
    end
  • local strength = 30 * strength_multiplier
  • local strength = 30 * strength_multiplier * bow_strength

    obj:set_velocity(vector.multiply(dir, strength))
    obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3})
    @@ -306,9 +410,36 @@ function x_bows.particle_effect(pos, type)
    },
    glow = 1
    })

  • elseif type == 'fire' 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',
    
  •   	glow = 7
    
  •   })
    

    end
    end

+local function should_unload(pname, pitem, last_item)

  • local registration = x_bows.registered_bows[last_item]
  • return (
  •   pitem ~= last_item and -- is the current item different than the last one?
    
  •   registration and -- lets not crash if this is not set
    
  •   last_item == registration.name_charged and -- was the last item a charged bow?
    
  •   not wielditem_shot[pname] -- we didn't shoot, did we?
    
  • )
    +end

-- sneak, fov adjustments when bow is charged
minetest.register_globalstep(function(dtime)
bow_charged_timer = bow_charged_timer + dtime
@@ -340,12 +471,39 @@ minetest.register_globalstep(function(dtime)
end

			x_bows.player_bow_sneak[name].sneak = false
  •   		player:set_fov(1, true, 0.4)
    
  •   		player:set_fov(0, true, 0.4) -- setting to 0 resets the override
      	end
      end
    
      bow_charged_timer = 0
    
    end
  • wielditem_now={}
  • for _,v in ipairs(core.get_connected_players()) do
  •   local pname = v:get_player_name()
    
  •   local itemstack = v:get_wielded_item() or ItemStack({})
    
  •   local pitem = itemstack:get_name() or ""
    
  •   wielditem_now[pname] = itemstack
    
  •   local last_itemstack = wielditem_last[pname] or ItemStack({})
    
  •   local last_item = last_itemstack:get_name() or ""
    
  •   if should_unload(pname, pitem, last_item) then
    
  •   	x_bows.unload(wielditem_last[pname], v, nil)
    
  •   end
    
  •   wielditem_shot[pname] = nil
    
  • end
  • wielditem_last = wielditem_now
    +end)

+core.register_on_leaveplayer(function(pobj, timed_out)

  • local pname = pobj:get_player_name()
  • wielditem_now[pname]=nil
  • wielditem_last[pname]=nil
  • wielditem_shot[pname] = nil
    end)

local path = minetest.get_modpath('x_bows')
@@ -353,7 +511,8 @@ local path = minetest.get_modpath('x_bows')
dofile(path .. '/nodes.lua')
dofile(path .. '/arrow.lua')
dofile(path .. '/items.lua')
+dofile(path .. '/alias.lua')

local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000

-print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]')
+core.log('action','[Mod] x_bows loaded.. ['.. mod_end_time ..'s]')
diff --git a/items.lua b/items.lua
index 24c2050..afc78fc 100644
--- a/items.lua
+++ b/items.lua
@@ -1,109 +1,135 @@
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,
  • uses = 185,
  • -- crit_chance in %%
  • crit_chance = 2,
  • strength = 0.8,
  • ammotype = "arrow",
    recipe = {
  •   {'', 'default:stick', 'farming:string'},
    
  •   {'default:stick', '', 'farming:string'},
    
  •   {'', 'default:stick', 'farming:string'},
    
  •   {'default:stick', 'default:stick', 'farming:string'},
    
  •   {'default:stick', 'farming:string', ''},
    
  •   {'farming:string', '', ''},
    
  • }
    +})

+x_bows.register_bow('bow_steel', {

  • description = 'Steel Bow',
  • uses = 285,
  • -- crit_chance in %%
  • inventory_image = "x_bows_bow_steel.png",
  • inventory_image_charged = "x_bows_bow_steel_charged.png",
  • crit_chance = 5,
  • strength = 1.0,
  • ammotype = "arrow",
  • recipe = {
  •   {'default:steel_ingot', 'default:steel_ingot', 'farming:string'},
    
  •   {'default:steel_ingot', 'farming:string', ''},
    
  •   {'farming:string', '', ''},
    
    }
    })

x_bows.register_arrow('arrow_wood', {
description = 'Arrow Wood',
inventory_image = 'x_bows_arrow_wood.png',

  • ammotype = "arrow",
  • craft = {
  •   {'default:flint'},
    
  •   {'group:stick'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'group:stick', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 1,
    max_drop_level = 0,
  •   damage_groups = {fleshy=2}
    
  •   damage_groups = {fleshy=4}
    
    }
    })

x_bows.register_arrow('arrow_stone', {
description = 'Arrow Stone',
inventory_image = 'x_bows_arrow_stone.png',

  • ammotype = "arrow",
    craft = {
  •   {'default:flint'},
    
  •   {'group:stone'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'group:stone', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 1.2,
    max_drop_level = 0,
  •   damage_groups = {fleshy=4}
    
  •   damage_groups = {fleshy=8}
    
    }
    })

x_bows.register_arrow('arrow_bronze', {
description = 'Arrow Bronze',
inventory_image = 'x_bows_arrow_bronze.png',

  • ammotype = "arrow",
    craft = {
  •   {'default:flint'},
    
  •   {'default:bronze_ingot'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'default:bronze_ingot', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 0.8,
    max_drop_level = 1,
  •   damage_groups = {fleshy=6}
    
  •   damage_groups = {fleshy=12}
    
    }
    })

x_bows.register_arrow('arrow_steel', {
description = 'Arrow Steel',
inventory_image = 'x_bows_arrow_steel.png',

  • ammotype = "arrow",
    craft = {
  •   {'default:flint'},
    
  •   {'default:steel_ingot'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'default:steel_ingot', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 0.7,
    max_drop_level = 1,
  •   damage_groups = {fleshy=6}
    
  •   damage_groups = {fleshy=12}
    
    }
    })

x_bows.register_arrow('arrow_mese', {
description = 'Arrow Mese',
inventory_image = 'x_bows_arrow_mese.png',

  • ammotype = "arrow",
    craft = {
  •   {'default:flint'},
    
  •   {'default:mese_crystal'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'default:mese_crystal', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 0.7,
    max_drop_level = 1,
  •   damage_groups = {fleshy=7}
    
  •   damage_groups = {fleshy=14}
    
    }
    })

x_bows.register_arrow('arrow_diamond', {
description = 'Arrow Diamond',
inventory_image = 'x_bows_arrow_diamond.png',

  • ammotype = "arrow",
    craft = {
  •   {'default:flint'},
    
  •   {'default:diamond'},
    
  •   {'group:wool'}
    
  •   {'default:flint', '', ''},
    
  •   {'', 'default:diamond', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
    },
    tool_capabilities = {
    full_punch_interval = 0.7,
    max_drop_level = 1,
  •   damage_groups = {fleshy=8}
    
  •   damage_groups = {fleshy=16}
    
    }
    })

+--[[
x_bows.register_arrow('arrow_diamond_tipped_poison', {
description = 'Arrow Diamond Tipped Poison (0:05)',
inventory_image = 'x_bows_arrow_diamond_poison.png',

  • ammotype = "arrow",
    craft = {
    {'', '', ''},
    {'', 'default:marram_grass_1', ''},
    @@ -116,6 +142,7 @@ x_bows.register_arrow('arrow_diamond_tipped_poison', {
    },
    craft_count = 1
    })
    +]]--

minetest.register_craft({
type = 'fuel',
@@ -128,3 +155,138 @@ minetest.register_craft({
recipe = 'x_bows:arrow_wood',
burntime = 1,
})
+
+-- #12 training arrows
+
+local default_dyes = {

  • "black",
  • "blue",
  • "brown",
  • "cyan",
  • "green",
  • "grey",
  • "magenta",
  • "orange",
  • "pink",
  • "red",
  • "violet",
  • "white",
  • "yellow"
    +}

+for _,v in ipairs(default_dyes) do
+

  • local ratio = 100
  • x_bows.register_arrow('arrow_training_'..v, {
  •   description = v..' Training Arrow',
    
  •   inventory_image = 'x_bows_arrow_wood.png^[colorize:'..v..':'..ratio,
    
  •   ammotype = "arrow",
    
  •   craft = {
    
  •   	{'default:flint', '', 'dye:'..v},
    
  •   	{'', 'group:stick', ''},
    
  •   	{'', '', 'petz:ducky_feather'}
    
  •   },
    
  •   tool_capabilities = {
    
  •   	full_punch_interval = 3,
    
  •   	max_drop_level = 1,
    
  •   	damage_groups = {fleshy=0}
    
  •   },
    
  •   textures = {
    
  •   	'x_bows_arrow_tile_point_top.png^[colorize:'..v..':'..ratio,
    
  •   	'x_bows_arrow_tile_point_bottom.png^[colorize:'..v..':'..ratio,
    
  •   	'x_bows_arrow_tile_point_right.png^[colorize:'..v..':'..ratio,
    
  •   	'x_bows_arrow_tile_point_left.png^[colorize:'..v..':'..ratio,
    
  •   	'x_bows_arrow_tile_tail.png^[colorize:'..v..':'..ratio,
    
  •   	'x_bows_arrow_tile_tail.png^[colorize:'..v..':'..ratio
    
  •   },
    
  • })

+end
+
+-- #10 Fire arrow
+
+x_bows.register_arrow('arrow_fire', {

  • description = 'Fire Arrow',
  • inventory_image = 'x_bows_arrow_fire.png',
  • ammotype = "arrow",
  • effects = {fire = "fire"},
  • craft = {
  •   {'basic_materials:oil_extract', '', ''},
    
  •   {'', 'default:torch', ''},
    
  •   {'', '', 'petz:ducky_feather'}
    
  • },
  • tool_capabilities = {
  •   full_punch_interval = 3.7,
    
  •   max_drop_level = 0,
    
  •   damage_groups = {fleshy=3}
    
  • },
  • craft_count = 1,
  • textures = {
  •   'x_bows_firearrow_tile_point_top.png',
    
  •   'x_bows_firearrow_tile_point_bottom.png',
    
  •   'x_bows_firearrow_tile_point_right.png',
    
  •   'x_bows_firearrow_tile_point_left.png',
    
  •   'x_bows_firearrow_tile_tail.png',
    
  •   'x_bows_firearrow_tile_tail.png'
    
  • },
    +})

+-- #Slingshot
+
+x_bows.register_bow('slingshot_wood', {

  • description = 'Wooden Slingshot',
  • uses = 85,
  • -- crit_chance in %%
  • inventory_image = "x_bows_slingshot_wood.png",
  • inventory_image_charged = "x_bows_slingshot_wood_charged.png",
  • crit_chance = 1,
  • strength = 0.7,
  • ammotype = "ball",
  • recipe = {
  •   {'', 'default:stick', 'farming:string'},
    
  •   {'', 'default:stick', 'default:stick'},
    
  •   {'default:stick', '', ''},
    
  • }
    +})

+x_bows.register_bow('slingshot_steel', {

  • description = 'Steel Slingshot',
  • uses = 135,
  • -- crit_chance in %%
  • inventory_image = "x_bows_slingshot_steel.png",
  • inventory_image_charged = "x_bows_slingshot_steel_charged.png",
  • crit_chance = 5,
  • strength = 1.0,
  • ammotype = "ball",
  • recipe = {
  •   {'', 'default:steel_ingot', 'farming:string'},
    
  •   {'', 'default:steel_ingot', 'default:steel_ingot'},
    
  •   {'default:steel_ingot', '', ''},
    
  • }
    +})

+x_bows.register_arrow('ball_rock', {

  • description = 'Ball of Rock',
  • inventory_image = 'x_bows_ball_rock.png',
  • ammotype = "ball",
  • craft = {
  •   {'default:cobble'},
    
  •   {'default:cobble'},
    
  • },
  • tool_capabilities = {
  •   full_punch_interval = 1.7,
    
  •   max_drop_level = 1,
    
  •   damage_groups = {fleshy=5}
    
  • },
  • craft_count = 10,
  • textures = {
  •   'x_bows_ball_rock_tile_point_top.png',
    
  •   'x_bows_ball_rock_tile_point_bottom.png',
    
  •   'x_bows_ball_rock_tile_point_right.png',
    
  •   'x_bows_ball_rock_tile_point_left.png',
    
  •   'x_bows_ball_rock_tile_tail.png',
    
  •   'x_bows_ball_rock_tile_tail.png'
    
  • },
    +})
    \ No newline at end of file
    diff --git a/mod.conf b/mod.conf
    index f5c7e0d..68189b6 100644
    --- a/mod.conf
    +++ b/mod.conf
    @@ -1,5 +1,5 @@
    name = x_bows
    description = Adds bow and arrows to Minetest.
    depends =
    -optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics
    +optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics, petz
    min_minetest_version = 5.0
    \ No newline at end of file
    diff --git a/nodes.lua b/nodes.lua
    index 9c8200c..aa38418 100644
    --- a/nodes.lua
    +++ b/nodes.lua
    @@ -1,30 +1,3 @@
    -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}
    -})

minetest.register_node('x_bows:target', {
description = 'Straw',
tiles = {'x_bows_target.png'},
diff --git a/textures/x_bows_arrow_fire.png b/textures/x_bows_arrow_fire.png
new file mode 100644
index 0000000..b33baf3
Binary files /dev/null and b/textures/x_bows_arrow_fire.png differ
diff --git a/textures/x_bows_ball_rock.png b/textures/x_bows_ball_rock.png
new file mode 100644
index 0000000..f1f5e9d
Binary files /dev/null and b/textures/x_bows_ball_rock.png differ
diff --git a/textures/x_bows_ball_rock_tile_point_bottom.png b/textures/x_bows_ball_rock_tile_point_bottom.png
new file mode 100644
index 0000000..96c6261
Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_bottom.png differ
diff --git a/textures/x_bows_ball_rock_tile_point_left.png b/textures/x_bows_ball_rock_tile_point_left.png
new file mode 100644
index 0000000..aa80c70
Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_left.png differ
diff --git a/textures/x_bows_ball_rock_tile_point_right.png b/textures/x_bows_ball_rock_tile_point_right.png
new file mode 100644
index 0000000..231f048
Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_right.png differ
diff --git a/textures/x_bows_ball_rock_tile_point_top.png b/textures/x_bows_ball_rock_tile_point_top.png
new file mode 100644
index 0000000..f5ebea0
Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_top.png differ
diff --git a/textures/x_bows_ball_rock_tile_tail.png b/textures/x_bows_ball_rock_tile_tail.png
new file mode 100644
index 0000000..c50c2b4
Binary files /dev/null and b/textures/x_bows_ball_rock_tile_tail.png differ
diff --git a/textures/x_bows_bow_steel.png b/textures/x_bows_bow_steel.png
new file mode 100644
index 0000000..666b861
Binary files /dev/null and b/textures/x_bows_bow_steel.png differ
diff --git a/textures/x_bows_bow_steel_charged.png b/textures/x_bows_bow_steel_charged.png
new file mode 100644
index 0000000..3508a6d
Binary files /dev/null and b/textures/x_bows_bow_steel_charged.png differ
diff --git a/textures/x_bows_firearrow_tile_point_bottom.png b/textures/x_bows_firearrow_tile_point_bottom.png
new file mode 100644
index 0000000..37ef5ee
Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_bottom.png differ
diff --git a/textures/x_bows_firearrow_tile_point_left.png b/textures/x_bows_firearrow_tile_point_left.png
new file mode 100644
index 0000000..95628b3
Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_left.png differ
diff --git a/textures/x_bows_firearrow_tile_point_right.png b/textures/x_bows_firearrow_tile_point_right.png
new file mode 100644
index 0000000..9785b02
Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_right.png differ
diff --git a/textures/x_bows_firearrow_tile_point_top.png b/textures/x_bows_firearrow_tile_point_top.png
new file mode 100644
index 0000000..dbc33ad
Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_top.png differ
diff --git a/textures/x_bows_firearrow_tile_tail.png b/textures/x_bows_firearrow_tile_tail.png
new file mode 100644
index 0000000..0fb00d3
Binary files /dev/null and b/textures/x_bows_firearrow_tile_tail.png differ
diff --git a/textures/x_bows_slingshot_steel.png b/textures/x_bows_slingshot_steel.png
new file mode 100644
index 0000000..affbefa
Binary files /dev/null and b/textures/x_bows_slingshot_steel.png differ
diff --git a/textures/x_bows_slingshot_steel_charged.png b/textures/x_bows_slingshot_steel_charged.png
new file mode 100644
index 0000000..a31c440
Binary files /dev/null and b/textures/x_bows_slingshot_steel_charged.png differ
diff --git a/textures/x_bows_slingshot_wood.png b/textures/x_bows_slingshot_wood.png
new file mode 100644
index 0000000..00a2e72
Binary files /dev/null and b/textures/x_bows_slingshot_wood.png differ
diff --git a/textures/x_bows_slingshot_wood_charged.png b/textures/x_bows_slingshot_wood_charged.png
new file mode 100644
index 0000000..ff39d1e
Binary files /dev/null and b/textures/x_bows_slingshot_wood_charged.png differ

</details>
our own diff since we've started making custom changes: <details> ```diff diff --git a/alias.lua b/alias.lua new file mode 100644 index 0000000..31bdce4 --- /dev/null +++ b/alias.lua @@ -0,0 +1,24 @@ +-- bows + +minetest.register_alias("bows:bow_wood", "x_bows:bow_wood") +minetest.register_alias("bows:bow_steel", "x_bows:bow_steel") +minetest.register_alias("bows:bow_bronze", "x_bows:bow_steel") +minetest.register_alias("bows:bow_bowie", "x_bows:bow_wood") + +minetest.register_alias("bows:arrow", "x_bows:arrow_wood") +minetest.register_alias("bows:arrow_steel", "x_bows:arrow_steel") +minetest.register_alias("bows:arrow_mese", "x_bows:arrow_mese") +minetest.register_alias("bows:arrow_diamond", "x_bows:arrow_diamond") + +-- projectile + +minetest.register_alias("projectile:bow", "x_bows:bow_wood") +minetest.register_alias("projectile:steel_bow", "x_bows:bow_steel") + +minetest.register_alias("projectile:arrow", "x_bows:arrow_steel") +minetest.register_alias("projectile:arrow_high_velocity", "x_bows:arrow_diamond") +minetest.register_alias("projectile:arrow_fire", "x_bows:arrow_fire") + +minetest.register_alias("projectile:slingshot", "x_bows:slingshot_wood") +minetest.register_alias("projectile:steel_slingshot", "x_bows:slingshot_steel") +minetest.register_alias("projectile:rock", "x_bows:ball_rock") \ No newline at end of file diff --git a/arrow.lua b/arrow.lua index 9f4e158..c6e6db6 100644 --- a/arrow.lua +++ b/arrow.lua @@ -29,6 +29,7 @@ local function get_obj_box(obj) return box end +--[[ -- Poison Arrow Effects 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 @@ -39,8 +40,10 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_ time_left = time_left + tick + local poison_job = nil + 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) + poison_job = 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 @@ -70,8 +73,14 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_ local _damage = punch_def.tool_capabilities.damage_groups.fleshy if target_obj:get_hp() - _damage > 0 then + local shooter = minetest.get_player_by_name(arrow_obj.user_name) + if not shooter then + -- pvp: shooter is offline, stop the poinson effect + if poison_job then poison_job.cancel() end + return + end target_obj:punch( - punch_def.puncher, + shooter, punch_def.time_from_last_punch, punch_def.tool_capabilities ) @@ -83,6 +92,7 @@ function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_ end end end +]]-- -- Main Arrow Entity minetest.register_entity('x_bows:arrow_entity', { @@ -93,7 +103,7 @@ minetest.register_entity('x_bows:arrow_entity', { selectionbox = {0, 0, 0, 0, 0, 0}, physical = false, textures = {'air'}, - hp_max = 0.5 + hp_max = 1 }, on_activate = function(self, staticdata) @@ -103,7 +113,6 @@ minetest.register_entity('x_bows:arrow_entity', { end 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 @@ -120,17 +129,19 @@ minetest.register_entity('x_bows:arrow_entity', { 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.user_name = _staticdata.user_name self._tflp = _staticdata._tflp self._tool_capabilities = _staticdata._tool_capabilities self._is_critical_hit = _staticdata.is_critical_hit - + self._effects = _staticdata._effects or {} + --[[ if self.arrow == 'x_bows:arrow_diamond_tipped_poison' then self._poison_arrow = true end + ]]-- self.object:set_properties({ - textures = {'x_bows:arrow_node'}, + textures = {self.arrow..'_node'}, infotext = self.arrow }) end, @@ -177,7 +188,9 @@ minetest.register_entity('x_bows:arrow_entity', { self._has_particles = true if self._tflp >= self._tool_capabilities.full_punch_interval then - if self._is_critical_hit then + if self._effects and self._effects.fire == "fire" then + x_bows.particle_effect(self._old_pos, 'fire') + elseif self._is_critical_hit then x_bows.particle_effect(self._old_pos, 'arrow_crit') else x_bows.particle_effect(self._old_pos, 'arrow') @@ -221,17 +234,17 @@ minetest.register_entity('x_bows:arrow_entity', { 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 ((pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= self.user_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(), + to_player = self.user_name, gain = 0.3 }) else minetest.sound_play('x_bows_arrow_hit', { - to_player = self.user:get_player_name(), + to_player = self.user_name, gain = 0.6 }) end @@ -262,9 +275,10 @@ minetest.register_entity('x_bows:arrow_entity', { if self._is_critical_hit then _damage = _damage * 2 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, @@ -279,18 +293,25 @@ minetest.register_entity('x_bows:arrow_entity', { _damage ) + -- TODO check pvp and don't push players around? pointed_thing.ref:add_velocity({ x = dir.x * knockback * -1, y = 7, z = dir.z * knockback * -1 }) - + ]]-- + -- pvp: arrows shot by offline players will do no damage + local shooter = minetest.get_player_by_name(self.user_name) + if not shooter then + self.object:remove() + return + end pointed_thing.ref:punch( - self.object, + shooter, self._tflp, { full_punch_interval = self._tool_capabilities.full_punch_interval, - damage_groups = {fleshy = _damage, knockback = knockback} + damage_groups = {fleshy = _damage} }, { x = dir.x * -1, @@ -387,6 +408,7 @@ minetest.register_entity('x_bows:arrow_entity', { position.z = zmin / 10 end + --[[ -- poison arrow if self._poison_arrow then local old_damage_texture_modifier = pointed_thing.ref:get_properties().damage_texture_modifier @@ -414,6 +436,7 @@ minetest.register_entity('x_bows:arrow_entity', { -- end end end + ]]-- if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then self.object:remove() @@ -475,6 +498,15 @@ minetest.register_entity('x_bows:arrow_entity', { -- only close to the center of the target will trigger signal if distance < 0.54 then + minetest.sound_play('x_bows_arrow_successful_hit', { + pos = pointed_thing.under, + gain = 0.6, + max_hear_distance = 32, + exclude_player = self.user_name, + }, true) + minetest.sound_play('x_bows_arrow_successful_hit', { + to_player = self.user_name, + }, true) mesecon.receptor_on(pointed_thing.under) minetest.get_node_timer(pointed_thing.under):start(2) end @@ -490,6 +522,12 @@ minetest.register_entity('x_bows:arrow_entity', { self._attached_to.pos = pointed_thing.under self.object:set_properties({collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}}) + -- Apply fire effect + if self._effects and self._effects.fire == "fire" and not core.is_protected(pointed_thing.above,"") and core.get_node(pointed_thing.above).name == "air" then + minetest.set_node(pointed_thing.above, {name = "fire:basic_flame"}) + return + end + -- remove last arrow when too many already attached local children = {} @@ -517,4 +555,4 @@ minetest.register_entity('x_bows:arrow_entity', { self._old_pos = pos end, -}) \ No newline at end of file +}) diff --git a/init.lua b/init.lua index c244a93..424aa5c 100644 --- a/init.lua +++ b/init.lua @@ -1,6 +1,12 @@ +core.log('action','[Mod] loading x_bows') local mod_start_time = minetest.get_us_time() + local bow_charged_timer = 0 +local wielditem_now={} +local wielditem_last={} +local wielditem_shot={} + x_bows = { pvp = minetest.settings:get_bool('enable_pvp') or false, creative = minetest.settings:get_bool('creative_mode') or false, @@ -18,21 +24,26 @@ function x_bows.is_creative(name) return x_bows.creative or minetest.check_player_privs(name, {creative = true}) end +function x_bows.on_drop(itemstack, dropper, pos) + return nil +end + function x_bows.register_bow(name, def) 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.strength = def.strength or 10 + def.ammotype = def.ammotype or "none" 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 .. '%'), + description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. def.crit_chance .. '%'), inventory_image = def.inventory_image or 'x_bows_bow_wood.png', -- on_use = function(itemstack, user, pointed_thing) -- end, @@ -44,9 +55,10 @@ function x_bows.register_bow(name, def) -- charged bow minetest.register_tool(def.name_charged, { - description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. (1 / def.crit_chance) * 100 .. '%'), + description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. def.crit_chance .. '%'), inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png', on_use = x_bows.shoot, + on_drop = x_bows.on_drop, groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1}, }) @@ -63,9 +75,9 @@ function x_bows.register_arrow(name, def) if name == nil or name == '' then return false end - def.name = 'x_bows:' .. name def.description = def.description or name + def.ammotype = def.ammotype or "none" x_bows.registered_arrows[def.name] = def @@ -82,6 +94,85 @@ function x_bows.register_arrow(name, def) recipe = def.craft }) end + + -- arrow node + minetest.register_node(def.name..'_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 = def.textures or { + '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} + }) +end + +function x_bows.unload(itemstack, user, pointed_thing) + local meta = itemstack:get_meta() + local inv = user:get_inventory() + local inv_list = inv:get_list('main') + local meta_arrow = meta:get_string('arrow') + local bow_name = itemstack:get_name() + local bow_def = x_bows.registered_bows[bow_name] + +-- Unload the arrow + local found_arrow, found_empty = nil + for k, st in ipairs(inv_list) do + if not found_arrow and st:get_free_space() >= 1 and x_bows.registered_arrows[st:get_name()] and st:get_name() == meta_arrow then + found_arrow = k + end + if not found_empty and st:get_free_space() >= 1 and st:get_name() == "" then + found_empty = k + end + end + + if found_arrow then + -- add +1 arrow to the itemstack + local c = inv:get_stack("main", found_arrow) + c:set_count(c:get_count()+1) + inv:set_stack("main", found_arrow, c) + elseif found_empty then + -- add arrow to the empty place + local as = ItemStack(meta_arrow) + inv:set_stack("main", found_empty, as) + else + -- No space, drop arrow + if not x_bows.is_creative(user:get_player_name()) then + local pos = user:get_pos() + local as = ItemStack(meta_arrow) + minetest.item_drop(as, nil, pos) + core.log("[x_bows] Arrow "..meta_arrow.." dropped at "..core.pos_to_string(pos)) + end + end + +-- Unload the bow + + for k, st in ipairs(inv_list) do + if x_bows.registered_bows[st:get_name()] then + meta:set_string('arrow', '') + itemstack:set_name(bow_def.name) + inv:set_stack("main", k, itemstack) + end + end + + return true end function x_bows.load(itemstack, user, pointed_thing) @@ -101,8 +192,10 @@ function x_bows.load(itemstack, user, pointed_thing) end end + if not bow_def then core.log("error","[MOD] x_bows: no bow_def") return end + for k, st in ipairs(inv_list) do - if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then + if not st:is_empty() and x_bows.registered_arrows[st:get_name()] and bow_def.ammotype == x_bows.registered_arrows[st:get_name()].ammotype then table.insert(itemstack_arrows, st) end end @@ -169,7 +262,9 @@ function x_bows.shoot(itemstack, user, pointed_thing) 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 bow_strength = x_bows.registered_bows[bow_name_charged].strength local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities + local _effects = x_bows.registered_arrows[meta_arrow].effects local staticdata = { arrow = meta_arrow, @@ -177,11 +272,12 @@ function x_bows.shoot(itemstack, user, pointed_thing) is_critical_hit = false, _tool_capabilities = _tool_capabilities, _tflp = tflp, + _effects = _effects } -- 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 + if math.random()*100 <= crit_chance then staticdata.is_critical_hit = true end end @@ -196,20 +292,28 @@ function x_bows.shoot(itemstack, user, pointed_thing) 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 eye_height = user:get_properties().eye_height + local arrow_pos = {x = pos.x, y = pos.y + eye_height, z = pos.z} + local eye_offset = vector.multiply(user:get_eye_offset(), 0.1) + local yaw = user:get_look_horizontal() + arrow_pos = vector.add(arrow_pos, vector.rotate_around_axis(eye_offset, {x=0,y=1,z=0}, yaw)) + local obj = minetest.add_entity(arrow_pos, 'x_bows:arrow_entity', minetest.serialize(staticdata)) if not obj then return itemstack end + wielditem_shot[user:get_player_name()] = true -- needed to determine whether we switched away from the item or we actually shot + local lua_ent = obj:get_luaentity() + lua_ent._old_pos = arrow_pos local strength_multiplier = tflp - if strength_multiplier > _tool_capabilities.full_punch_interval then + if strength_multiplier < 0 or strength_multiplier > _tool_capabilities.full_punch_interval then strength_multiplier = 1 end - local strength = 30 * strength_multiplier + local strength = 30 * strength_multiplier * bow_strength obj:set_velocity(vector.multiply(dir, strength)) obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3}) @@ -306,9 +410,36 @@ function x_bows.particle_effect(pos, type) }, glow = 1 }) + elseif type == 'fire' 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', + glow = 7 + }) end end +local function should_unload(pname, pitem, last_item) + local registration = x_bows.registered_bows[last_item] + return ( + pitem ~= last_item and -- is the current item different than the last one? + registration and -- lets not crash if this is not set + last_item == registration.name_charged and -- was the last item a charged bow? + not wielditem_shot[pname] -- we didn't shoot, did we? + ) +end + -- sneak, fov adjustments when bow is charged minetest.register_globalstep(function(dtime) bow_charged_timer = bow_charged_timer + dtime @@ -340,12 +471,39 @@ minetest.register_globalstep(function(dtime) end x_bows.player_bow_sneak[name].sneak = false - player:set_fov(1, true, 0.4) + player:set_fov(0, true, 0.4) -- setting to 0 resets the override end end bow_charged_timer = 0 end + + wielditem_now={} + for _,v in ipairs(core.get_connected_players()) do + local pname = v:get_player_name() + + local itemstack = v:get_wielded_item() or ItemStack({}) + local pitem = itemstack:get_name() or "" + wielditem_now[pname] = itemstack + + local last_itemstack = wielditem_last[pname] or ItemStack({}) + local last_item = last_itemstack:get_name() or "" + + if should_unload(pname, pitem, last_item) then + x_bows.unload(wielditem_last[pname], v, nil) + end + + wielditem_shot[pname] = nil + + end + wielditem_last = wielditem_now +end) + +core.register_on_leaveplayer(function(pobj, timed_out) + local pname = pobj:get_player_name() + wielditem_now[pname]=nil + wielditem_last[pname]=nil + wielditem_shot[pname] = nil end) local path = minetest.get_modpath('x_bows') @@ -353,7 +511,8 @@ local path = minetest.get_modpath('x_bows') dofile(path .. '/nodes.lua') dofile(path .. '/arrow.lua') dofile(path .. '/items.lua') +dofile(path .. '/alias.lua') local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000 -print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]') +core.log('action','[Mod] x_bows loaded.. ['.. mod_end_time ..'s]') diff --git a/items.lua b/items.lua index 24c2050..afc78fc 100644 --- a/items.lua +++ b/items.lua @@ -1,109 +1,135 @@ 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, + uses = 185, + -- `crit_chance` in %% + crit_chance = 2, + strength = 0.8, + ammotype = "arrow", recipe = { - {'', 'default:stick', 'farming:string'}, - {'default:stick', '', 'farming:string'}, - {'', 'default:stick', 'farming:string'}, + {'default:stick', 'default:stick', 'farming:string'}, + {'default:stick', 'farming:string', ''}, + {'farming:string', '', ''}, + } +}) + +x_bows.register_bow('bow_steel', { + description = 'Steel Bow', + uses = 285, + -- `crit_chance` in %% + inventory_image = "x_bows_bow_steel.png", + inventory_image_charged = "x_bows_bow_steel_charged.png", + crit_chance = 5, + strength = 1.0, + ammotype = "arrow", + recipe = { + {'default:steel_ingot', 'default:steel_ingot', 'farming:string'}, + {'default:steel_ingot', 'farming:string', ''}, + {'farming:string', '', ''}, } }) x_bows.register_arrow('arrow_wood', { description = 'Arrow Wood', inventory_image = 'x_bows_arrow_wood.png', + ammotype = "arrow", + craft = { - {'default:flint'}, - {'group:stick'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'group:stick', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 1, max_drop_level = 0, - damage_groups = {fleshy=2} + damage_groups = {fleshy=4} } }) x_bows.register_arrow('arrow_stone', { description = 'Arrow Stone', inventory_image = 'x_bows_arrow_stone.png', + ammotype = "arrow", craft = { - {'default:flint'}, - {'group:stone'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'group:stone', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 1.2, max_drop_level = 0, - damage_groups = {fleshy=4} + damage_groups = {fleshy=8} } }) x_bows.register_arrow('arrow_bronze', { description = 'Arrow Bronze', inventory_image = 'x_bows_arrow_bronze.png', + ammotype = "arrow", craft = { - {'default:flint'}, - {'default:bronze_ingot'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'default:bronze_ingot', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 0.8, max_drop_level = 1, - damage_groups = {fleshy=6} + damage_groups = {fleshy=12} } }) x_bows.register_arrow('arrow_steel', { description = 'Arrow Steel', inventory_image = 'x_bows_arrow_steel.png', + ammotype = "arrow", craft = { - {'default:flint'}, - {'default:steel_ingot'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'default:steel_ingot', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 0.7, max_drop_level = 1, - damage_groups = {fleshy=6} + damage_groups = {fleshy=12} } }) x_bows.register_arrow('arrow_mese', { description = 'Arrow Mese', inventory_image = 'x_bows_arrow_mese.png', + ammotype = "arrow", craft = { - {'default:flint'}, - {'default:mese_crystal'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'default:mese_crystal', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 0.7, max_drop_level = 1, - damage_groups = {fleshy=7} + damage_groups = {fleshy=14} } }) x_bows.register_arrow('arrow_diamond', { description = 'Arrow Diamond', inventory_image = 'x_bows_arrow_diamond.png', + ammotype = "arrow", craft = { - {'default:flint'}, - {'default:diamond'}, - {'group:wool'} + {'default:flint', '', ''}, + {'', 'default:diamond', ''}, + {'', '', 'petz:ducky_feather'} }, tool_capabilities = { full_punch_interval = 0.7, max_drop_level = 1, - damage_groups = {fleshy=8} + damage_groups = {fleshy=16} } }) +--[[ x_bows.register_arrow('arrow_diamond_tipped_poison', { description = 'Arrow Diamond Tipped Poison (0:05)', inventory_image = 'x_bows_arrow_diamond_poison.png', + ammotype = "arrow", craft = { {'', '', ''}, {'', 'default:marram_grass_1', ''}, @@ -116,6 +142,7 @@ x_bows.register_arrow('arrow_diamond_tipped_poison', { }, craft_count = 1 }) +]]-- minetest.register_craft({ type = 'fuel', @@ -128,3 +155,138 @@ minetest.register_craft({ recipe = 'x_bows:arrow_wood', burntime = 1, }) + +-- #12 training arrows + +local default_dyes = { + "black", + "blue", + "brown", + "cyan", + "green", + "grey", + "magenta", + "orange", + "pink", + "red", + "violet", + "white", + "yellow" +} + +for _,v in ipairs(default_dyes) do + + local ratio = 100 + + x_bows.register_arrow('arrow_training_'..v, { + description = v..' Training Arrow', + inventory_image = 'x_bows_arrow_wood.png^[colorize:'..v..':'..ratio, + ammotype = "arrow", + + craft = { + {'default:flint', '', 'dye:'..v}, + {'', 'group:stick', ''}, + {'', '', 'petz:ducky_feather'} + }, + tool_capabilities = { + full_punch_interval = 3, + max_drop_level = 1, + damage_groups = {fleshy=0} + }, + textures = { + 'x_bows_arrow_tile_point_top.png^[colorize:'..v..':'..ratio, + 'x_bows_arrow_tile_point_bottom.png^[colorize:'..v..':'..ratio, + 'x_bows_arrow_tile_point_right.png^[colorize:'..v..':'..ratio, + 'x_bows_arrow_tile_point_left.png^[colorize:'..v..':'..ratio, + 'x_bows_arrow_tile_tail.png^[colorize:'..v..':'..ratio, + 'x_bows_arrow_tile_tail.png^[colorize:'..v..':'..ratio + }, + }) + +end + +-- #10 Fire arrow + +x_bows.register_arrow('arrow_fire', { + description = 'Fire Arrow', + inventory_image = 'x_bows_arrow_fire.png', + ammotype = "arrow", + effects = {fire = "fire"}, + craft = { + {'basic_materials:oil_extract', '', ''}, + {'', 'default:torch', ''}, + {'', '', 'petz:ducky_feather'} + }, + tool_capabilities = { + full_punch_interval = 3.7, + max_drop_level = 0, + damage_groups = {fleshy=3} + }, + craft_count = 1, + textures = { + 'x_bows_firearrow_tile_point_top.png', + 'x_bows_firearrow_tile_point_bottom.png', + 'x_bows_firearrow_tile_point_right.png', + 'x_bows_firearrow_tile_point_left.png', + 'x_bows_firearrow_tile_tail.png', + 'x_bows_firearrow_tile_tail.png' + }, +}) + +-- #Slingshot + +x_bows.register_bow('slingshot_wood', { + description = 'Wooden Slingshot', + uses = 85, + -- `crit_chance` in %% + inventory_image = "x_bows_slingshot_wood.png", + inventory_image_charged = "x_bows_slingshot_wood_charged.png", + crit_chance = 1, + strength = 0.7, + ammotype = "ball", + recipe = { + {'', 'default:stick', 'farming:string'}, + {'', 'default:stick', 'default:stick'}, + {'default:stick', '', ''}, + } +}) + +x_bows.register_bow('slingshot_steel', { + description = 'Steel Slingshot', + uses = 135, + -- `crit_chance` in %% + inventory_image = "x_bows_slingshot_steel.png", + inventory_image_charged = "x_bows_slingshot_steel_charged.png", + crit_chance = 5, + strength = 1.0, + ammotype = "ball", + recipe = { + {'', 'default:steel_ingot', 'farming:string'}, + {'', 'default:steel_ingot', 'default:steel_ingot'}, + {'default:steel_ingot', '', ''}, + } +}) + +x_bows.register_arrow('ball_rock', { + description = 'Ball of Rock', + inventory_image = 'x_bows_ball_rock.png', + ammotype = "ball", + craft = { + {'default:cobble'}, + {'default:cobble'}, + }, + tool_capabilities = { + full_punch_interval = 1.7, + max_drop_level = 1, + damage_groups = {fleshy=5} + }, + craft_count = 10, + textures = { + 'x_bows_ball_rock_tile_point_top.png', + 'x_bows_ball_rock_tile_point_bottom.png', + 'x_bows_ball_rock_tile_point_right.png', + 'x_bows_ball_rock_tile_point_left.png', + 'x_bows_ball_rock_tile_tail.png', + 'x_bows_ball_rock_tile_tail.png' + }, +}) \ No newline at end of file diff --git a/mod.conf b/mod.conf index f5c7e0d..68189b6 100644 --- a/mod.conf +++ b/mod.conf @@ -1,5 +1,5 @@ name = x_bows description = Adds bow and arrows to Minetest. depends = -optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics +optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics, petz min_minetest_version = 5.0 \ No newline at end of file diff --git a/nodes.lua b/nodes.lua index 9c8200c..aa38418 100644 --- a/nodes.lua +++ b/nodes.lua @@ -1,30 +1,3 @@ -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} -}) - minetest.register_node('x_bows:target', { description = 'Straw', tiles = {'x_bows_target.png'}, diff --git a/textures/x_bows_arrow_fire.png b/textures/x_bows_arrow_fire.png new file mode 100644 index 0000000..b33baf3 Binary files /dev/null and b/textures/x_bows_arrow_fire.png differ diff --git a/textures/x_bows_ball_rock.png b/textures/x_bows_ball_rock.png new file mode 100644 index 0000000..f1f5e9d Binary files /dev/null and b/textures/x_bows_ball_rock.png differ diff --git a/textures/x_bows_ball_rock_tile_point_bottom.png b/textures/x_bows_ball_rock_tile_point_bottom.png new file mode 100644 index 0000000..96c6261 Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_bottom.png differ diff --git a/textures/x_bows_ball_rock_tile_point_left.png b/textures/x_bows_ball_rock_tile_point_left.png new file mode 100644 index 0000000..aa80c70 Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_left.png differ diff --git a/textures/x_bows_ball_rock_tile_point_right.png b/textures/x_bows_ball_rock_tile_point_right.png new file mode 100644 index 0000000..231f048 Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_right.png differ diff --git a/textures/x_bows_ball_rock_tile_point_top.png b/textures/x_bows_ball_rock_tile_point_top.png new file mode 100644 index 0000000..f5ebea0 Binary files /dev/null and b/textures/x_bows_ball_rock_tile_point_top.png differ diff --git a/textures/x_bows_ball_rock_tile_tail.png b/textures/x_bows_ball_rock_tile_tail.png new file mode 100644 index 0000000..c50c2b4 Binary files /dev/null and b/textures/x_bows_ball_rock_tile_tail.png differ diff --git a/textures/x_bows_bow_steel.png b/textures/x_bows_bow_steel.png new file mode 100644 index 0000000..666b861 Binary files /dev/null and b/textures/x_bows_bow_steel.png differ diff --git a/textures/x_bows_bow_steel_charged.png b/textures/x_bows_bow_steel_charged.png new file mode 100644 index 0000000..3508a6d Binary files /dev/null and b/textures/x_bows_bow_steel_charged.png differ diff --git a/textures/x_bows_firearrow_tile_point_bottom.png b/textures/x_bows_firearrow_tile_point_bottom.png new file mode 100644 index 0000000..37ef5ee Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_bottom.png differ diff --git a/textures/x_bows_firearrow_tile_point_left.png b/textures/x_bows_firearrow_tile_point_left.png new file mode 100644 index 0000000..95628b3 Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_left.png differ diff --git a/textures/x_bows_firearrow_tile_point_right.png b/textures/x_bows_firearrow_tile_point_right.png new file mode 100644 index 0000000..9785b02 Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_right.png differ diff --git a/textures/x_bows_firearrow_tile_point_top.png b/textures/x_bows_firearrow_tile_point_top.png new file mode 100644 index 0000000..dbc33ad Binary files /dev/null and b/textures/x_bows_firearrow_tile_point_top.png differ diff --git a/textures/x_bows_firearrow_tile_tail.png b/textures/x_bows_firearrow_tile_tail.png new file mode 100644 index 0000000..0fb00d3 Binary files /dev/null and b/textures/x_bows_firearrow_tile_tail.png differ diff --git a/textures/x_bows_slingshot_steel.png b/textures/x_bows_slingshot_steel.png new file mode 100644 index 0000000..affbefa Binary files /dev/null and b/textures/x_bows_slingshot_steel.png differ diff --git a/textures/x_bows_slingshot_steel_charged.png b/textures/x_bows_slingshot_steel_charged.png new file mode 100644 index 0000000..a31c440 Binary files /dev/null and b/textures/x_bows_slingshot_steel_charged.png differ diff --git a/textures/x_bows_slingshot_wood.png b/textures/x_bows_slingshot_wood.png new file mode 100644 index 0000000..00a2e72 Binary files /dev/null and b/textures/x_bows_slingshot_wood.png differ diff --git a/textures/x_bows_slingshot_wood_charged.png b/textures/x_bows_slingshot_wood_charged.png new file mode 100644 index 0000000..ff39d1e Binary files /dev/null and b/textures/x_bows_slingshot_wood_charged.png differ ``` </details>
Author
Member

upstream x_bows changes:

```diff diff --git a/.cdb.json b/.cdb.json new file mode 100644 index 0000000..74d5ca1 --- /dev/null +++ b/.cdb.json @@ -0,0 +1,20 @@ +{ + "type": "MOD", + "title": "X Bows", + "name": "x_bows", + "short_description": "Adds bow and arrows with API.", + "dev_state": "MAINTENANCE_ONLY", + "tags": [ + "pvp", + "shooter", + "survival", + "tools" + ], + "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?status=new&status=open", + "forums": 26466, + "video_url": "https://youtu.be/pItpltmUoa8", + "website": "https://bitbucket.org/minetest_gamers/x_bows/wiki/Home" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb53136 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2285905 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Creating an archive + +.* export-ignore +assets export-ignore +scripts export-ignore +bin export-ignore +docs export-ignore +types export-ignore +*.zip export-ignore +bitbucket-pipelines.yml export-ignore +package.json export-ignore +package-lock.json export-ignore +screenshot*.png export-ignore +i18n.py export-ignore +config.ld export-ignore diff --git a/.gitignore b/.gitignore index 496ee2c..3e69eba 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ -.DS_Store \ No newline at end of file +.DS_Store +docs/build +*.blend1 +*.blend2 +*.old +node_modules +*.log +logs diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..227756b --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,86 @@ +unused_args = false +allow_defined_top = true +max_line_length = false + +exclude_files = { + './scripts', + './bin', + './logs', + './node_modules', + './sounds', + './textures', + './models', + './docs', + './locale', + './types', +} + +globals = { + 'XBows', + 'XBowsQuiver', + 'XBowsEntityDefBase', + 'XBowsEntityDefCustom' +} + +read_globals = { + "DIR_DELIM", "INIT", + + "minetest", "core", + "dump", "dump2", + + "Raycast", + "Settings", + "PseudoRandom", + "PerlinNoise", + "VoxelManip", + "SecureRandom", + "VoxelArea", + "PerlinNoiseMap", + "PcgRandom", + "ItemStack", + "AreaStore", + "unpack", + + "vector", + + table = { + fields = { + "copy", + "indexof", + "insert_all", + "key_value_swap", + "shuffle", + } + }, + + string = { + fields = { + "split", + "trim", + } + }, + + math = { + fields = { + "hypot", + "sign", + "factorial", + "round", + } + }, + + "player_monoids", + "playerphysics", + "hb", + "mesecon", + "armor", + "default", + "i3", + "unified_inventory", + "player_api", + "u_skins", + "wardrobe", + "3d_armor", + "skinsdb", + "skins" +} diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..04a89d7 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "Lua 5.1", + "path": [ + "?.lua", + "?/init.lua" + ], + "pathStrict": true + }, + "workspace": { + "maxPreload": 1600, + "preloadFileSize": 1000, + "ignoreDir": [ + "/locale/", + "/libs/", + "/3rd", + "/.vscode", + "/meta", + "/.git", + "/docs", + "/bin" + ], + "checkThirdParty": false + }, + "typeFormat": { + "config": { + "format_line": "false" + } + }, + "type": { + "castNumberToInteger": true + }, + "doc": { + "privateName": [ + "_*" + ] + }, + "diagnostics": { + "disable": [ + "close-non-object" + ], + "groupFileStatus": { + "ambiguity": "Any", + "await": "Any", + "duplicate": "Any", + "global": "Any", + "luadoc": "Any", + "redefined": "Any", + "strict": "Any", + "type-check": "Any", + "unbalanced": "Any", + "unused": "Any" + }, + "ignoredFiles": "Disable", + "libraryFiles": "Disable", + "neededFileStatus": { + "codestyle-check": "Any" + }, + "disableScheme": [ + "git", + "type" + ], + "globals": [ + "DIR_DELIM", + "INIT", + "minetest", + "core", + "dump", + "dump2", + "Raycast", + "Settings", + "PseudoRandom", + "PerlinNoise", + "VoxelManip", + "SecureRandom", + "VoxelArea", + "PerlinNoiseMap", + "PcgRandom", + "ItemStack", + "AreaStore", + "unpack", + "vector", + "player_monoids", + "playerphysics", + "hb", + "mesecon", + "armor", + "default", + "i3", + "unified_inventory", + "player_api", + "u_skins", + "wardrobe", + "3d_armor", + "skinsdb", + "skins" + ] + } +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/LICENSE.txt b/LICENSE.txt index f166cc5..bce588e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,7 @@ GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999
  • Copyright (C) 1991, 1999 Free Software Foundation, Inc.
  • 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
    Everyone is permitted to copy and distribute verbatim copies
    of this license document, but changing it is not allowed.

@@ -55,7 +54,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
-
+
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
@@ -111,7 +110,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
-
+
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

@@ -158,7 +157,7 @@ Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
-
+
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
@@ -216,7 +215,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
-
+
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
@@ -267,7 +266,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
-
+
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
@@ -329,7 +328,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
-
+
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
@@ -370,7 +369,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
-
+
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +421,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
-
+
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
@@ -454,49 +453,3 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

  •                 END OF TERMS AND CONDITIONS
    

-

  •       How to Apply These Terms to Your New Libraries
    
  • If you develop a new library, and you want it to be of the greatest
    -possible use to the public, we recommend making it free software that
    -everyone can redistribute and change. You can do so by permitting
    -redistribution under these terms (or, alternatively, under the terms of the
    -ordinary General Public License).
  • To apply these terms, attach the following notices to the library. It is
    -safest to attach them to the start of each source file to most effectively
    -convey the exclusion of warranty; and each file should have at least the
    -"copyright" line and a pointer to where the full notice is found.
  • <one line to give the library's name and a brief idea of what it does.>
  • Copyright (C)
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to the Free Software
  • Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

-Also add information on how to contact you by electronic and paper mail.

-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:

  • Yoyodyne, Inc., hereby disclaims all copyright interest in the
  • library `Frob' (a library for tweaking knobs) written by James Random Hacker.
  • , 1 April 1990
  • Ty Coon, President of Vice

-That's all there is to it!
\ No newline at end of file
diff --git a/README.md b/README.md
index 683b697..b9de0d7 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@

Bow and Arrows [x_bows]

-Adds bow and arrows to Minetest.
+Adds bow and arrows with API. The goal of this Mod is to make most complete single bow with arrow what will work with MTG damage system, time from last punch as simple as possible. Eventually due to the damage tiers in MTG additional arrows were added.

screenshot

@@ -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)
    @@ -22,8 +23,52 @@ Video: https://youtu.be/pItpltmUoa8
  • arrows adjusts pitch when flying
  • arrows can be picked up again after stuck in solid nodes
  • 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)
    +* X Bows API for creating custom shooters and projectiles
    +* 3d quiver shown in 3rd person view (compatible with 3d armor)
    +* x_enchanting support

+## 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 arrow/quiver inventory - there should be extra tab in your inventory MOD to show arrow and quiver inventory slots.
+Arrows and quivers in the players main inventory don't count and will not be used.
+You have to have arrows and/or quiver in dedicated arrow/quiver inventory slots in order to charge the bow.
+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
+as it will give the arrow full speed (maximum shooting distance) and chance for critical arrow (double damage).
+
+There are few indications on how to know when the bow is fully charged:
+
+* there is a distinct "click" sound
+* each arrow has "charge time" in the description
+* after shooting, arrow will have particle trail
+
+There are few indications on how to know when the arrow is a critical arrow:
+
+* there is a distinct arrow flying sound
+* after shooting, arrow will have red particle trail
+
+If you shoot the arrow before the bow is fully charged the speed/distance will be lower and no arrow particle trail will be shown (also no chance for critical arrow).
+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 quiver inventory slot - there should be extra tab in your inventory MOD to show arrow and quiver inventory slots, bow can take arrows from quiver, otherwise arrows outside of the quiver are used to load the bow.
+Though, if arrows from quiver are used to load 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

@@ -34,9 +79,18 @@ Video: https://youtu.be/pItpltmUoa8

  • default (recipes)
  • farming (bow and target recipes)
  • 3d_armor (calculates damage including the armor)
    -- hbhunger (changes hudbar when poisoned)
  • mesecons (target can be used to trigger mesecon signal)
  • playerphysics (force sneak when holding charged bow)
    +- player_monoids (force sneak when holding charged bow)
    +- wool (quiver recipe)
    +- i3
    +- unified_inventory
    +- simple_skins
    +- u_skins
    +- wardrobe
    +- sfinv
    +- skinsdb
    +- player_api (shows 3d quiver)

License:

@@ -51,13 +105,7 @@ 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
  • x_bows_target.png

@@ -68,7 +116,33 @@ Modified by SaKeL:

  • x_bows_arrow_steel.png
  • x_bows_arrow_mese.png
  • x_bows_arrow_diamond.png
    -- x_bows_arrow_diamond_poison.png

+CC-BY-SA-3.0, by paramat
+
+- x_bows_hotbar_selected.png
+- x_bows_quiver_hotbar.png
+- x_bows_single_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
+- x_bows_quiver_mesh.png
+- x_bows_quiver_empty_mesh.png
+- x_bows_quiver_blank_mesh.png
+- x_bows_quiver_slot.png
+- x_bows_dmg_0.png
+- x_bows_dmg_1.png
+- x_bows_dmg_2.png
+- x_bows_dmg_3.png
+- x_bows_dmg_4.png
+- x_bows_dmg_5.png
+- x_bows_dmg_6.png
+- x_bows_dmg_7.png
+- x_bows_dmg_8.png
+- x_bows_dmg_9.png

Sounds

@@ -100,6 +174,35 @@ 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
+- x_bows_arrow.blend
+
+Original model by MirceaKitsune (CC BY-SA 3.0).
+Various alterations and fixes by kilbith, sofar, xunto, Rogier-5, TeTpaAka, Desour, stujones11, An0n3m0us (CC BY-SA 3.0):
+
+Modified by SaKeL (added quiver):
+
+- x_bows_3d_armor_character.b3d
+- x_bows_3d_armor_character.blend
+- x_bows_character.b3d
+- x_bows_character.blend
+

Installation

-see: http://wiki.minetest.com/wiki/Installing_Mods
+see: https://wiki.minetest.net/Installing_Mods
diff --git a/api.lua b/api.lua
new file mode 100644
index 0000000..58ee320
--- /dev/null
+++ b/api.lua
@@ -0,0 +1,2644 @@
+--[[

  • X Bows. Adds bow and arrows with API.
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+local S = minetest.get_translator(minetest.get_current_modname())
+
+sfinv = sfinv --@as Sfinv
+
+---Check if table contains value
+---@param table table
+---@param value string|number
+---@return boolean
+local function table_contains(table, value)

  • for _, v in ipairs(table) do
  •    if v == value then
    
  •        return true
    
  •    end
    
  • end
  • return false
    +end

+---Merge two tables with key/value pair
+---@param t1 table
+---@param t2 table
+---@return table
+local function mergeTables(t1, t2)

  • for k, v in pairs(t2) do t1[k] = v end
  • return t1
    +end

+---@type XBows
+XBows = {

  • pvp = minetest.settings:get_bool('enable_pvp') or false,
  • creative = minetest.settings:get_bool('creative_mode') or false,
  • mesecons = minetest.get_modpath('mesecons'),
  • playerphysics = minetest.get_modpath('playerphysics'),
  • player_monoids = minetest.get_modpath('player_monoids'),
  • i3 = minetest.get_modpath('i3'),
  • unified_inventory = minetest.get_modpath('unified_inventory'),
  • u_skins = minetest.get_modpath('u_skins'),
  • wardrobe = minetest.get_modpath('wardrobe'),
  • _3d_armor = minetest.get_modpath('3d_armor'),
  • skinsdb = minetest.get_modpath('skinsdb'),
  • player_api = minetest.get_modpath('player_api'),
  • registered_bows = {},
  • registered_arrows = {},
  • registered_quivers = {},
  • registered_particle_spawners = {},
  • registered_entities = {},
  • player_bow_sneak = {},
  • settings = {
  •    x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false),
    
  •    x_bows_show_damage_numbers = minetest.settings:get_bool('x_bows_show_damage_numbers', false),
    
  •    x_bows_show_3d_quiver = minetest.settings:get_bool('x_bows_show_3d_quiver', true)
    
  • },
  • charge_sound_after_job = {},
  • fallback_quiver = not minetest.global_exists('sfinv')
  •    and not minetest.global_exists('unified_inventory')
    
  •    and not minetest.global_exists('i3')
    

+}
+
+XBows.__index = XBows
+
+---@type XBowsQuiver
+XBowsQuiver = {

  • hud_item_ids = {},
  • after_job = {},
  • quiver_empty_state = {}
    +}
    +XBowsQuiver.__index = XBowsQuiver
    +setmetatable(XBowsQuiver, XBows)

+---@type XBowsEntityDef
+local XBowsEntityDef = {}
+XBowsEntityDef.__index = XBowsEntityDef
+setmetatable(XBowsEntityDef, XBows)
+
+---create UUID
+---@return string
+function XBows.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

+---Check if creative is enabled or if player has creative priv
+---@param self XBows
+---@param name string
+---@return boolean
+function XBows.is_creative(self, name)

  • return self.creative or minetest.check_player_privs(name, { creative = true })
    +end

+---Updates allowed_ammunition definition on already registered item, so MODs can add new ammunitions to this list.
+---@param self XBows
+---@param name string
+---@param allowed_ammunition string[]
+---@return nil
+function XBows.update_bow_allowed_ammunition(self, name, allowed_ammunition)

  • local _name = 'x_bows:' .. name
  • local def = self.registered_bows[_name]
  • if not def then
  •    return
    
  • end
  • local def_copy = table.copy(def)
  • minetest.unregister_item(_name)
  • for _, v in ipairs(allowed_ammunition) do
  •    table.insert(def_copy.custom.allowed_ammunition, v)
    
  • end
  • self:register_bow(name, def_copy, true)
    +end

+---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
+---@param self XBows
+---@param player ObjectRef Player Ref
+---@param includeWielded? boolean Will include reset for wielded bow also. default: false
+---@return nil
+function XBows.reset_charged_bow(self, player, includeWielded)

  • local _includeWielded = includeWielded or false
  • local inv = player:get_inventory()
  • if not inv then
  •    return
    
  • end
  • local inv_list = inv:get_list('main')
  • for i, st in ipairs(inv_list) do
  •    local st_name = st:get_name()
    
  •    local x_bows_registered_bow_def = self.registered_bows[st_name]
    
  •    local reset = _includeWielded or player:get_wield_index() ~= i
    
  •    if not st:is_empty()
    
  •        and x_bows_registered_bow_def
    
  •        and reset
    
  •        and minetest.get_item_group(st_name, 'bow_charged') ~= 0
    
  •    then
    
  •        local item_meta = st:get_meta()
    
  •        local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string')))
    
  •        --return arrow
    
  •        if arrow_itemstack and not self: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_bow_def.custom.name,
    
  •            count = st:get_count(),
    
  •            wear = st:get_wear()
    
  •        }))
    
  •    end
    
  • end
    +end

+---Register bows
+---@param self XBows
+---@param name string
+---@param def ItemDef | BowItemDefCustom
+---@param override? boolean MOD everride
+---@return boolean|nil
+function XBows.register_bow(self, name, def, override)

  • if name == nil or name == '' then
  •    return false
    
  • end
  • local mod_name = def.custom.mod_name or 'x_bows'
  • def.custom.name = mod_name .. ':' .. name
  • def.custom.name_charged = mod_name .. ':' .. name .. '_charged'
  • def.short_description = def.short_description
  • def.description = override and def.short_description or (def.description or name)
  • def.custom.uses = def.custom.uses or 150
  • def.groups = mergeTables({ bow = 1, flammable = 1, enchantability = 1 }, def.groups or {})
  • def.custom.groups_charged = mergeTables(
  •    { bow_charged = 1, flammable = 1, not_in_creative_inventory = 1 },
    
  •    def.groups or {}
    
  • )
  • def.custom.strength = def.custom.strength or 30
  • def.custom.allowed_ammunition = def.custom.allowed_ammunition or nil
  • def.custom.sound_load = def.custom.sound_load or 'x_bows_bow_load'
  • def.custom.sound_hit = def.custom.sound_hit or 'x_bows_arrow_hit'
  • def.custom.sound_shoot = def.custom.sound_shoot or 'x_bows_bow_shoot'
  • def.custom.sound_shoot_crit = def.custom.sound_shoot_crit or 'x_bows_bow_shoot_crit'
  • def.custom.gravity = def.custom.gravity or -10
  • if def.custom.crit_chance then
  •    def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Critical Arrow Chance') .. ': '
    
  •        .. (1 / def.custom.crit_chance) * 100 .. '%')
    
  • end
  • def.description = def.description .. '\n' .. minetest.colorize('#00BFFF', S('Strength') .. ': '
  •    .. def.custom.strength)
    
  • if def.custom.allowed_ammunition then
  •    local allowed_amm_desc = table.concat(def.custom.allowed_ammunition, '\n')
    
  •    if allowed_amm_desc ~= '' then
    
  •        def.description = def.description .. '\n' .. S('Allowed ammunition') .. ':\n' .. allowed_amm_desc
    
  •    else
    
  •        def.description = def.description .. '\n' .. S('Allowed ammunition') .. ': ' .. S('none')
    
  •    end
    
  • end
  • self.registered_bows[def.custom.name] = def
  • self.registered_bows[def.custom.name_charged] = def
  • ---not charged bow
  • minetest.register_tool(override and ':' .. def.custom.name or def.custom.name, {
  •    description = def.description,
    
  •    inventory_image = def.inventory_image or 'x_bows_bow_wood.png',
    
  •    wield_image = def.wield_image or def.inventory_image,
    
  •    groups = def.groups,
    
  •    wield_scale = { x = 2, y = 2, z = 1.5 },
    
  •    ---@param itemstack ItemStack
    
  •    ---@param placer ObjectRef|nil
    
  •    ---@param pointed_thing PointedThingDef
    
  •    ---@return ItemStack|nil
    
  •    on_place = function(itemstack, placer, pointed_thing)
    
  •        if placer then
    
  •            return self:load(itemstack, placer, pointed_thing)
    
  •        end
    
  •    end,
    
  •    ---@param itemstack ItemStack
    
  •    ---@param user ObjectRef|nil
    
  •    ---@param pointed_thing PointedThingDef
    
  •    ---@return ItemStack|nil
    
  •    on_secondary_use = function(itemstack, user, pointed_thing)
    
  •        if user then
    
  •            return self:load(itemstack, user, pointed_thing)
    
  •        end
    
  •    end
    
  • })
  • ---charged bow
  • minetest.register_tool(override and ':' .. def.custom.name_charged or def.custom.name_charged, {
  •    description = def.description,
    
  •    inventory_image = def.custom.inventory_image_charged or 'x_bows_bow_wood_charged.png',
    
  •    wield_image = def.custom.wield_image_charged or def.custom.inventory_image_charged,
    
  •    groups = def.custom.groups_charged,
    
  •    wield_scale = { x = 2, y = 2, z = 1.5 },
    
  •    range = 0,
    
  •    ---@param itemstack ItemStack
    
  •    ---@param user ObjectRef|nil
    
  •    ---@param pointed_thing PointedThingDef
    
  •    ---@return ItemStack|nil
    
  •    on_use = function(itemstack, user, pointed_thing)
    
  •        if user then
    
  •            return self:shoot(itemstack, user, pointed_thing)
    
  •        end
    
  •    end,
    
  •    ---@param itemstack ItemStack
    
  •    ---@param dropper ObjectRef|nil
    
  •    ---@param pos Vector
    
  •    ---@return ItemStack|nil
    
  •    on_drop = function(itemstack, dropper, pos)
    
  •        if dropper then
    
  •            local item_meta = itemstack:get_meta()
    
  •            local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string')))
    
  •            ---return arrow
    
  •            if arrow_itemstack and not self: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.custom.name)
    
  •            ---returns leftover itemstack
    
  •            return minetest.item_drop(itemstack, dropper, pos)
    
  •        end
    
  •    end
    
  • })
  • ---recipes
  • if def.custom.recipe then
  •    minetest.register_craft({
    
  •        output = def.custom.name,
    
  •        recipe = def.custom.recipe
    
  •    })
    
  • end
  • ---fuel recipe
  • if def.custom.fuel_burntime then
  •    minetest.register_craft({
    
  •        type = 'fuel',
    
  •        recipe = def.custom.name,
    
  •        burntime = def.custom.fuel_burntime,
    
  •    })
    
  • end
    +end

+---Register arrows
+---@param self XBows
+---@param name string
+---@param def ItemDef | ArrowItemDefCustom
+---@return boolean|nil
+function XBows.register_arrow(self, name, def)

  • if name == nil or name == '' then
  •    return false
    
  • end
  • local mod_name = def.custom.mod_name or 'x_bows'
  • def.custom.name = mod_name .. ':' .. name
  • def.description = def.description or name
  • def.short_description = def.short_description or name
  • def.custom.tool_capabilities = def.custom.tool_capabilities or {
  •    full_punch_interval = 1,
    
  •    max_drop_level = 0,
    
  •    damage_groups = { fleshy = 2 }
    
  • }
  • def.custom.description_abilities = minetest.colorize('#00FF00', S('Damage') .. ': '
  •    .. def.custom.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', S('Charge Time') .. ': '
    
  •    .. def.custom.tool_capabilities.full_punch_interval .. 's')
    
  • def.groups = mergeTables({ arrow = 1, flammable = 1 }, def.groups or {})
  • def.custom.particle_effect = def.custom.particle_effect or 'arrow'
  • def.custom.particle_effect_crit = def.custom.particle_effect_crit or 'arrow_crit'
  • def.custom.particle_effect_fast = def.custom.particle_effect_fast or 'arrow_fast'
  • def.custom.projectile_entity = def.custom.projectile_entity or 'x_bows:arrow_entity'
  • def.custom.on_hit_node = def.custom.on_hit_node or nil
  • def.custom.on_hit_entity = def.custom.on_hit_entity or nil
  • def.custom.on_hit_player = def.custom.on_hit_player or nil
  • def.custom.on_after_activate = def.custom.on_after_activate or nil
  • self.registered_arrows[def.custom.name] = def
  • minetest.register_craftitem(def.custom.name, {
  •    description = def.description .. '\n' .. def.custom.description_abilities,
    
  •    short_description = def.short_description,
    
  •    inventory_image = def.inventory_image,
    
  •    groups = def.groups
    
  • })
  • ---recipes
  • if def.custom.recipe then
  •    minetest.register_craft({
    
  •        output = def.custom.name .. ' ' .. (def.custom.craft_count or 4),
    
  •        recipe = def.custom.recipe
    
  •    })
    
  • end
  • ---fuel recipe
  • if def.custom.fuel_burntime then
  •    minetest.register_craft({
    
  •        type = 'fuel',
    
  •        recipe = def.custom.name,
    
  •        burntime = def.custom.fuel_burntime,
    
  •    })
    
  • end
    +end

+---Register quivers
+---@param self XBows
+---@param name string
+---@param def ItemDef | QuiverItemDefCustom
+---@return boolean|nil
+function XBows.register_quiver(self, name, def)

  • if name == nil or name == '' then
  •    return false
    
  • end
  • def.custom.name = 'x_bows:' .. name
  • def.custom.name_open = 'x_bows:' .. name .. '_open'
  • def.description = def.description or name
  • def.short_description = def.short_description or name
  • def.groups = mergeTables({ quiver = 1, flammable = 1 }, def.groups or {})
  • def.custom.groups_charged = mergeTables({
  •        quiver = 1, quiver_open = 1, flammable = 1, not_in_creative_inventory = 1
    
  •    },
    
  •    def.groups or {}
    
  • )
  • if def.custom.faster_arrows then
  •    def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') ..
    
  •        ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%')
    
  •    def.short_description = def.short_description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') ..
    
  •        ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%')
    
  • end
  • if def.custom.add_damage then
  •    def.description = def.description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') ..
    
  •        ': +' .. def.custom.add_damage)
    
  •    def.short_description = def.short_description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') ..
    
  •        ': +' .. def.custom.add_damage)
    
  • end
  • self.registered_quivers[def.custom.name] = def
  • self.registered_quivers[def.custom.name_open] = def
  • ---closed quiver
  • minetest.register_tool(def.custom.name, {
  •    description = def.description,
    
  •    short_description = def.short_description,
    
  •    inventory_image = def.inventory_image or 'x_bows_quiver.png',
    
  •    wield_image = def.wield_image or 'x_bows_quiver.png',
    
  •    groups = def.groups,
    
  •    ---@param itemstack ItemStack
    
  •    ---@param user ObjectRef|nil
    
  •    ---@param pointed_thing PointedThingDef
    
  •    ---@return ItemStack|nil
    
  •    on_secondary_use = function(itemstack, user, pointed_thing)
    
  •        if user then
    
  •            return self:open_quiver(itemstack, user)
    
  •        end
    
  •    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 self:open_quiver(itemstack, placer)
    
  •    end
    
  • })
  • ---open quiver
  • minetest.register_tool(def.custom.name_open, {
  •    description = def.description,
    
  •    short_description = def.short_description,
    
  •    inventory_image = def.custom.inventory_image_open or 'x_bows_quiver_open.png',
    
  •    wield_image = def.custom.wield_image_open or 'x_bows_quiver_open.png',
    
  •    groups = def.custom.groups_charged,
    
  •    ---@param itemstack ItemStack
    
  •    ---@param dropper ObjectRef|nil
    
  •    ---@param pos Vector
    
  •    ---@return ItemStack
    
  •    on_drop = function(itemstack, dropper, pos)
    
  •        if not dropper then
    
  •            return itemstack
    
  •        end
    
  •        local replace_item = XBowsQuiver:get_replacement_item(itemstack, 'x_bows:quiver')
    
  •        return minetest.item_drop(replace_item, dropper, pos)
    
  •    end
    
  • })
  • ---recipes
  • if def.custom.recipe then
  •    minetest.register_craft({
    
  •        output = def.custom.name,
    
  •        recipe = def.custom.recipe
    
  •    })
    
  • end
  • ---fuel recipe
  • if def.custom.fuel_burntime then
  •    minetest.register_craft({
    
  •        type = 'fuel',
    
  •        recipe = def.custom.name,
    
  •        burntime = def.custom.fuel_burntime,
    
  •    })
    
  • end
    +end

+---Load bow
+---@param self XBows
+---@param itemstack ItemStack
+---@param user ObjectRef
+---@param pointed_thing PointedThingDef
+---@return ItemStack
+function XBows.load(self, itemstack, user, pointed_thing)

  • local player_name = user:get_player_name()
  • local inv = user:get_inventory() --@as InvRef
  • local bow_name = itemstack:get_name()
  • local bow_def = self.registered_bows[bow_name]
  • ---@alias ItemStackArrows {["stack"]: ItemStack, ["idx"]: number|integer}[]
  • ---@type ItemStackArrows
  • local itemstack_arrows = {}
  • ---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
  • ---find itemstack arrow in quiver
  • local quiver_result = XBowsQuiver:get_itemstack_arrow_from_quiver(user)
  • local itemstack_arrow = quiver_result.found_arrow_stack
  • if itemstack_arrow then
  •    ---we got arrow from quiver
    
  •    local itemstack_arrow_meta = itemstack_arrow:get_meta()
    
  •    itemstack_arrow_meta:set_int('is_arrow_from_quiver', 1)
    
  •    itemstack_arrow_meta:set_int('found_arrow_stack_idx', quiver_result.found_arrow_stack_idx)
    
  •    itemstack_arrow_meta:set_string('quiver_name', quiver_result.quiver_name)
    
  •    itemstack_arrow_meta:set_string('quiver_id', quiver_result.quiver_id)
    
  • else
  •    if not inv:is_empty('x_bows:arrow_inv') then
    
  •        XBowsQuiver:udate_or_create_hud(user, inv:get_list('x_bows:arrow_inv'))
    
  •    else
    
  •        ---no ammo (fake stack)
    
  •        XBowsQuiver:udate_or_create_hud(user, {
    
  •            ItemStack({ name = 'x_bows:no_ammo' })
    
  •        })
    
  •    end
    
  •    ---find itemstack arrow in players inventory
    
  •    local arrow_stack = inv:get_stack('x_bows:arrow_inv', 1)
    
  •    local is_allowed_ammunition = self:is_allowed_ammunition(bow_name, arrow_stack:get_name())
    
  •    if self.registered_arrows[arrow_stack:get_name()] and is_allowed_ammunition then
    
  •        table.insert(itemstack_arrows, { stack = arrow_stack, idx = 1 })
    
  •    end
    
  •    ---if everything else fails
    
  •    if self.fallback_quiver then
    
  •        local inv_list = inv:get_list('main')
    
  •        for i, st in ipairs(inv_list) do
    
  •            local st_name = st:get_name()
    
  •            if not st:is_empty() and self.registered_arrows[st_name] then
    
  •                local _is_allowed_ammunition = self:is_allowed_ammunition(bow_name, st_name)
    
  •                if self.registered_arrows[st_name] and _is_allowed_ammunition then
    
  •                    table.insert(itemstack_arrows, { stack = st, idx = i })
    
  •                end
    
  •            end
    
  •        end
    
  •    end
    
  •    -- take 1st found arrow in the list
    
  •    itemstack_arrow = #itemstack_arrows > 0 and itemstack_arrows[1].stack or nil
    
  • end
  • if itemstack_arrow and bow_def then
  •    local _tool_capabilities = self.registered_arrows[itemstack_arrow:get_name()].custom.tool_capabilities
    
  •    ---@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()
    
  •        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()
    
  •            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()))
    
  •            wielded_item:set_name(v_bow_name .. '_charged')
    
  •            v_user:set_wielded_item(wielded_item)
    
  •            if not self: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('x_bows:arrow_inv', 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 self.charge_sound_after_job[player_name] then
    
  •        for _, v in pairs(self.charge_sound_after_job[player_name]) do
    
  •            v:cancel()
    
  •        end
    
  •        self.charge_sound_after_job[player_name] = {}
    
  •    else
    
  •        self.charge_sound_after_job[player_name] = {}
    
  •    end
    
  •    ---sound plays when charge time reaches full punch interval time
    
  •    table.insert(self.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(bow_def.custom.sound_load, {
    
  •        to_player = player_name,
    
  •        gain = 0.6
    
  •    })
    
  •    return itemstack
    
  • end
  • return itemstack
    +end

+---Shoot bow
+---@param self XBows
+---@param itemstack ItemStack
+---@param user ObjectRef
+---@param pointed_thing? PointedThingDef
+---@return ItemStack
+function XBows.shoot(self, itemstack, user, pointed_thing)

  • 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 found_arrow_stack_idx = arrow_itemstack_meta:get_int('found_arrow_stack_idx')
  • local quiver_id = arrow_itemstack_meta:get_string('quiver_id')
  • local detached_inv = XBowsQuiver:get_or_create_detached_inv(
  •    quiver_id,
    
  •    user:get_player_name()
    
  • )
  • ---Handle HUD and 3d Quiver
  • if is_arrow_from_quiver == 1 then
  •    XBowsQuiver:udate_or_create_hud(user, detached_inv:get_list('main'), found_arrow_stack_idx)
    
  •    if detached_inv:is_empty('main') then
    
  •        XBowsQuiver:show_3d_quiver(user, { is_empty = true })
    
  •    else
    
  •        XBowsQuiver:show_3d_quiver(user)
    
  •    end
    
  • else
  •    local inv = user:get_inventory() --[[@as InvRef]]
    
  •    if not inv:is_empty('x_bows:arrow_inv') then
    
  •        XBowsQuiver:udate_or_create_hud(user, inv:get_list('x_bows:arrow_inv'))
    
  •    else
    
  •        ---no ammo (fake stack just for the HUD)
    
  •        XBowsQuiver:udate_or_create_hud(user, {
    
  •            ItemStack({ name = 'x_bows:no_ammo' })
    
  •        })
    
  •    end
    
  • end
  • local x_bows_registered_arrow_def = self.registered_arrows[arrow_name]
  • if not x_bows_registered_arrow_def then
  •    return itemstack
    
  • end
  • local bow_name_charged = itemstack:get_name()
  • ---Bow
  • local x_bows_registered_bow_charged_def = self.registered_bows[bow_name_charged]
  • local bow_name = x_bows_registered_bow_charged_def.custom.name
  • local uses = x_bows_registered_bow_charged_def.custom.uses
  • local crit_chance = x_bows_registered_bow_charged_def.custom.crit_chance
  • ---Arrow
  • local projectile_entity = x_bows_registered_arrow_def.custom.projectile_entity
  • ---Quiver
  • local x_bows_registered_quiver_def = self.registered_quivers[quiver_name]
  • local _tool_capabilities = x_bows_registered_arrow_def.custom.tool_capabilities
  • local quiver_xbows_def = x_bows_registered_quiver_def
  • ---X Enchanting
  • local x_enchanting = minetest.deserialize(meta:get_string('x_enchanting')) or {}
  • ---@type EnityStaticDataAttrDef
  • local staticdata = {
  •    _arrow_name = arrow_name,
    
  •    _bow_name = bow_name,
    
  •    _user_name = user:get_player_name(),
    
  •    _is_critical_hit = false,
    
  •    _tool_capabilities = _tool_capabilities,
    
  •    _tflp = tflp,
    
  •    _add_damage = 0,
    
  •    _x_enchanting = x_enchanting
    
  • }
  • ---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
  • ---speed multiply
  • if quiver_xbows_def and quiver_xbows_def.custom.faster_arrows and quiver_xbows_def.custom.faster_arrows > 1 then
  •    staticdata._faster_arrows_multiplier = quiver_xbows_def.custom.faster_arrows
    
  • end
  • ---add quiver damage
  • if quiver_xbows_def and quiver_xbows_def.custom.add_damage and quiver_xbows_def.custom.add_damage > 0 then
  •    staticdata._add_damage = staticdata._add_damage + quiver_xbows_def.custom.add_damage
    
  • end
  • ---sound
  • local sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot
  • if staticdata._is_critical_hit then
  •    sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot_crit
    
  • end
  • ---stop punching close objects/nodes when shooting
  • minetest.after(0.2, function()
  •    local wield_item = user:get_wielded_item()
    
  •    if wield_item:get_count() > 0 and wield_item:get_name() == itemstack:get_name() then
    
  •        local new_stack = ItemStack(mergeTables(itemstack:to_table(), { name = bow_name }))
    
  •        user:set_wielded_item(new_stack)
    
  •    end
    
  • end)
  • local player_pos = user:get_pos()
  • local obj = minetest.add_entity(
  •    {
    
  •        x = player_pos.x,
    
  •        y = player_pos.y + 1.5,
    
  •        z = player_pos.z
    
  •    },
    
  •    projectile_entity,
    
  •    minetest.serialize(staticdata)
    
  • )
  • if not obj then
  •    return itemstack
    
  • end
  • minetest.sound_play(sound_name, {
  •    gain = 0.3,
    
  •    pos = user:get_pos(),
    
  •    max_hear_distance = 10
    
  • })
  • if not self:is_creative(user:get_player_name()) then
  •    itemstack:add_wear(65535 / uses)
    
  • end
  • if itemstack:get_count() == 0 then
  •    minetest.sound_play('default_tool_breaks', {
    
  •        gain = 0.3,
    
  •        pos = user:get_pos(),
    
  •        max_hear_distance = 10
    
  •    })
    
  • end
  • return itemstack
    +end

+---Add new particle to XBow registration
+---@param self XBows
+---@param name string
+---@param def ParticlespawnerDef|ParticlespawnerDefCustom
+---@return nil
+function XBows.register_particle_effect(self, name, def)

  • if self.registered_particle_spawners[name] then
  •    minetest.log('warning', 'Particle effect "' .. name .. '" already exists and will not be overwritten.')
    
  •    return
    
  • end
  • self.registered_particle_spawners[name] = def
    +end

+---Get particle effect from registered spawners table
+---@param self XBows
+---@param name string
+---@param pos Vector
+---@return number|boolean
+function XBows.get_particle_effect_for_arrow(self, name, pos)

  • local def = self.registered_particle_spawners[name]
  • if not def then
  •    minetest.log('warning', 'Particle effect "' .. name .. '" is not registered.')
    
  •    return false
    
  • end
  • def.custom = def.custom or {}
  • def.minpos = def.custom.minpos and vector.add(pos, def.custom.minpos) or pos
  • def.maxpos = def.custom.maxpos and vector.add(pos, def.custom.maxpos) or pos
  • return minetest.add_particlespawner(def--@as ParticlespawnerDef )
    +end

+---Check if ammunition is allowed to charge this weapon
+---@param self XBows
+---@param weapon_name string
+---@param ammo_name string
+---@return boolean
+function XBows.is_allowed_ammunition(self, weapon_name, ammo_name)

  • local x_bows_weapon_def = self.registered_bows[weapon_name]
  • if not x_bows_weapon_def then
  •    return false
    
  • end
  • if not x_bows_weapon_def.custom.allowed_ammunition then
  •    return true
    
  • end
  • if #x_bows_weapon_def.custom.allowed_ammunition == 0 then
  •    return false
    
  • end
  • return table_contains(x_bows_weapon_def.custom.allowed_ammunition, ammo_name)
    +end

+----
+--- ENTITY API
+----
+
+---Gets total armor level from 3d armor
+---@param player ObjectRef
+---@return integer
+local function get_3d_armor_armor(player)

  • 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
  • armor_total = armor.def[player:get_player_name()].level
  • return armor_total
    +end

+---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)
    +end

+---Function receive a "luaentity" table as self. Called when the object is instantiated.
+---@param self EntityDef|EntityDefCustom|XBows
+---@param selfObj EnityCustomAttrDef
+---@param staticdata string
+---@param dtime_s? integer|number
+---@return nil
+function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s)

  • if not selfObj or not staticdata or staticdata == '' then
  •    selfObj.object:remove()
    
  •    return
    
  • end
  • local _staticdata = minetest.deserialize(staticdata) --@as EnityStaticDataAttrDef
  • -- set/reset - do not inherit from previous entity table
  • selfObj._velocity = { x = 0, y = 0, z = 0 }
  • selfObj._old_pos = nil
  • selfObj._attached = false
  • selfObj._attached_to = {
  •    type = '',
    
  •    pos = nil
    
  • }
  • selfObj._has_particles = false
  • selfObj._lifetimer = 60
  • selfObj._nodechecktimer = 0.5
  • selfObj._is_drowning = false
  • selfObj._in_liquid = false
  • selfObj._shot_from_pos = selfObj.object:get_pos()
  • selfObj._arrow_name = _staticdata._arrow_name
  • selfObj._bow_name = _staticdata._bow_name
  • selfObj._user_name = _staticdata._user_name
  • selfObj._user = minetest.get_player_by_name(_staticdata._user_name)
  • selfObj._tflp = _staticdata._tflp
  • selfObj._tool_capabilities = _staticdata._tool_capabilities
  • selfObj._is_critical_hit = _staticdata._is_critical_hit
  • selfObj._faster_arrows_multiplier = _staticdata._faster_arrows_multiplier
  • selfObj._add_damage = _staticdata._add_damage
  • selfObj._caused_damage = 0
  • selfObj._caused_knockback = 0
  • local x_bows_registered_arrow_def = self.registered_arrows[selfObj._arrow_name]
  • selfObj._arrow_particle_effect = x_bows_registered_arrow_def.custom.particle_effect
  • selfObj._arrow_particle_effect_crit = x_bows_registered_arrow_def.custom.particle_effect_crit
  • selfObj._arrow_particle_effect_fast = x_bows_registered_arrow_def.custom.particle_effect_fast
  • ---Bow Def
  • local x_bows_registered_bow_def = self.registered_bows[selfObj._bow_name]
  • selfObj._sound_hit = x_bows_registered_bow_def.custom.sound_hit
  • local bow_strength = x_bows_registered_bow_def.custom.strength
  • local acc_x_min = x_bows_registered_bow_def.custom.acc_x_min
  • local acc_y_min = x_bows_registered_bow_def.custom.acc_y_min
  • local acc_z_min = x_bows_registered_bow_def.custom.acc_z_min
  • local acc_x_max = x_bows_registered_bow_def.custom.acc_x_max
  • local acc_y_max = x_bows_registered_bow_def.custom.acc_y_max
  • local acc_z_max = x_bows_registered_bow_def.custom.acc_z_max
  • local gravity = x_bows_registered_bow_def.custom.gravity
  • local bow_strength_min = x_bows_registered_bow_def.custom.strength_min
  • local bow_strength_max = x_bows_registered_bow_def.custom.strength_max
  • ---X Enchanting
  • selfObj._x_enchanting = _staticdata._x_enchanting
  • ---acceleration
  • selfObj._player_look_dir = selfObj._user:get_look_dir()
  • selfObj._acc_x = selfObj._player_look_dir.x
  • selfObj._acc_y = gravity
  • selfObj._acc_z = selfObj._player_look_dir.z
  • if acc_x_min and acc_x_max then
  •    selfObj._acc_x = math.random(acc_x_min, acc_x_max)
    
  • end
  • if acc_y_min and acc_y_max then
  •    selfObj._acc_y = math.random(acc_y_min, acc_y_max)
    
  • end
  • if acc_z_min and acc_z_max then
  •    selfObj._acc_z = math.random(acc_z_min, acc_z_max)
    
  • end
  • ---strength
  • local strength_multiplier = selfObj._tflp
  • if strength_multiplier > selfObj._tool_capabilities.full_punch_interval then
  •    strength_multiplier = 1
    
  •    ---faster arrow, only on full punch interval
    
  •    if selfObj._faster_arrows_multiplier then
    
  •        strength_multiplier = strength_multiplier + (strength_multiplier / selfObj._faster_arrows_multiplier)
    
  •    end
    
  • end
  • if bow_strength_max and bow_strength_min then
  •    bow_strength = math.random(bow_strength_min, bow_strength_max)
    
  • end
  • selfObj._strength = bow_strength * strength_multiplier
  • ---rotation factor
  • local x_bows_registered_entity_def = self.registered_entities[selfObj.name]
  • selfObj._rotation_factor = x_bows_registered_entity_def._custom.rotation_factor
  • if type(selfObj._rotation_factor) == 'function' then
  •    selfObj._rotation_factor = selfObj._rotation_factor()
    
  • end
  • ---add infotext
  • selfObj.object:set_properties({
  •    infotext = selfObj._arrow_name,
    
  • })
  • ---idle animation
  • if x_bows_registered_entity_def and x_bows_registered_entity_def._custom.animations.idle then
  •    selfObj.object:set_animation(unpack(x_bows_registered_entity_def._custom.animations.idle)--[[@as table]] )
    
  • end
  • ---counter, e.g. for initial values set on_step
  • selfObj._step_count = 0
  • ---Callbacks
  • local on_after_activate_callback = x_bows_registered_arrow_def.custom.on_after_activate
  • if on_after_activate_callback then
  •    on_after_activate_callback(selfObj)
    
  • end
    +end

+---Function receive a "luaentity" table as self. Called when the object dies.
+---@param self XBows
+---@param selfObj EnityCustomAttrDef
+---@param killer ObjectRef|nil
+---@return nil
+function XBowsEntityDef.on_death(self, selfObj, killer)

  • if not selfObj._old_pos then
  •    selfObj.object:remove()
    
  •    return
    
  • end
  • -- Infinity enchantment - arrows cannot be retrieved
  • if selfObj._x_enchanting.infinity and selfObj._x_enchanting.infinity.value > 0 then
  •    return
    
  • end
  • minetest.item_drop(ItemStack(selfObj._arrow_name), nil, vector.round(selfObj._old_pos))
    +end

+--- Function receive a "luaentity" table as self. Called on every server tick, after movement and collision processing.
+---dtime: elapsed time since last call. moveresult: table with collision info (only available if physical=true).
+---@param self XBows
+---@param selfObj EnityCustomAttrDef
+---@param dtime number
+---@return nil
+function XBowsEntityDef.on_step(self, selfObj, dtime)

  • selfObj._step_count = selfObj._step_count + 1
  • if selfObj._step_count == 1 then
  •    ---initialize
    
  •    ---this has to be done here for raycast to kick-in asap
    
  •    selfObj.object:set_velocity(vector.multiply(selfObj._player_look_dir, selfObj._strength))
    
  •    selfObj.object:set_acceleration({ x = selfObj._acc_x, y = selfObj._acc_y, z = selfObj._acc_z })
    
  •    selfObj.object:set_yaw(minetest.dir_to_yaw(selfObj._player_look_dir))
    
  • end
  • local pos = selfObj.object:get_pos()
  • selfObj._old_pos = selfObj._old_pos or pos
  • local ray = minetest.raycast(selfObj._old_pos, pos, true, true)
  • local pointed_thing = ray:next()
  • selfObj._lifetimer = selfObj._lifetimer - dtime
  • selfObj._nodechecktimer = selfObj._nodechecktimer - dtime
  • -- adjust pitch when flying
  • if not selfObj._attached then
  •    local velocity = selfObj.object:get_velocity()
    
  •    local v_rotation = selfObj.object:get_rotation()
    
  •    local pitch = math.atan2(velocity.y, math.sqrt(velocity.x ^ 2 + velocity.z ^ 2))
    
  •    selfObj.object:set_rotation({
    
  •        x = pitch,
    
  •        y = v_rotation.y,
    
  •        z = v_rotation.z + (selfObj._rotation_factor or math.pi / 2)
    
  •    })
    
  • end
  • -- remove attached arrows after lifetime
  • if selfObj._lifetimer <= 0 then
  •    selfObj.object:remove()
    
  •    return
    
  • end
  • -- add particles only when not attached
  • if not selfObj._attached and not selfObj._in_liquid then
  •    selfObj._has_particles = true
    
  •    if selfObj._tflp >= selfObj._tool_capabilities.full_punch_interval then
    
  •        if selfObj._is_critical_hit then
    
  •            self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect_crit, selfObj._old_pos)
    
  •        elseif selfObj._faster_arrows_multiplier then
    
  •            self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect_fast, selfObj._old_pos)
    
  •        else
    
  •            self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect, selfObj._old_pos)
    
  •        end
    
  •    end
    
  • end
  • -- remove attached arrows after object dies
  • if not selfObj.object:get_attach() and selfObj._attached_to.type == 'object' then
  •    selfObj.object:remove()
    
  •    return
    
  • end
  • -- arrow falls down when not attached to node any more
  • if selfObj._attached_to.type == 'node' and selfObj._attached and selfObj._nodechecktimer <= 0 then
  •    local node = minetest.get_node(selfObj._attached_to.pos)
    
  •    selfObj._nodechecktimer = 0.5
    
  •    if not node then
    
  •        return
    
  •    end
    
  •    if node.name == 'air' then
    
  •        selfObj.object:set_velocity({ x = 0, y = -3, z = 0 })
    
  •        selfObj.object:set_acceleration({ x = 0, y = -3, z = 0 })
    
  •        -- reset values
    
  •        selfObj._attached = false
    
  •        selfObj._attached_to.type = ''
    
  •        selfObj._attached_to.pos = nil
    
  •        selfObj.object:set_properties({ collisionbox = { 0, 0, 0, 0, 0, 0 } })
    
  •        return
    
  •    end
    
  • end
  • while pointed_thing do
  •    local ip_pos = pointed_thing.intersection_point
    
  •    local in_pos = pointed_thing.intersection_normal
    
  •    selfObj.pointed_thing = pointed_thing
    
  •    if pointed_thing.type == 'object'
    
  •        and pointed_thing.ref ~= selfObj.object
    
  •        and pointed_thing.ref:get_hp() > 0
    
  •        and (
    
  •            (
    
  •                pointed_thing.ref:is_player()
    
  •                and pointed_thing.ref:get_player_name() ~= selfObj._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 selfObj.object:get_attach() == nil
    
  •        and not selfObj._attached
    
  •    then
    
  •        if pointed_thing.ref:is_player() then
    
  •            minetest.sound_play('x_bows_arrow_successful_hit', {
    
  •                to_player = selfObj._user:get_player_name(),
    
  •                gain = 0.3
    
  •            })
    
  •        else
    
  •            minetest.sound_play(selfObj._sound_hit, {
    
  •                to_player = selfObj._user:get_player_name(),
    
  •                gain = 0.6
    
  •            })
    
  •        end
    
  •        selfObj.object:set_velocity({ x = 0, y = 0, z = 0 })
    
  •        selfObj.object:set_acceleration({ x = 0, y = 0, z = 0 })
    
  •        -- calculate damage
    
  •        local target_armor_groups = pointed_thing.ref:get_armor_groups()
    
  •        local _damage = 0
    
  •        if selfObj._add_damage then
    
  •            -- add damage from quiver
    
  •            _damage = _damage + selfObj._add_damage
    
  •        end
    
  •        if selfObj._x_enchanting.power then
    
  •            -- add damage from enchantment
    
  •            _damage = _damage + _damage * (selfObj._x_enchanting.power.value / 100)
    
  •        end
    
  •        for group, base_damage in pairs(selfObj._tool_capabilities.damage_groups) do
    
  •            _damage = _damage
    
  •                + base_damage
    
  •                * limit(selfObj._tflp / selfObj._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
    
  •        -- crits
    
  •        if selfObj._is_critical_hit then
    
  •            _damage = _damage * 2
    
  •        end
    
  •        -- knockback
    
  •        local dir = vector.normalize(vector.subtract(selfObj._shot_from_pos, ip_pos))
    
  •        local distance = vector.distance(selfObj._shot_from_pos, ip_pos)
    
  •        local knockback = minetest.calculate_knockback(
    
  •            pointed_thing.ref,
    
  •            selfObj.object,
    
  •            selfObj._tflp,
    
  •            {
    
  •                full_punch_interval = selfObj._tool_capabilities.full_punch_interval,
    
  •                damage_groups = { fleshy = _damage },
    
  •            },
    
  •            dir,
    
  •            distance,
    
  •            _damage
    
  •        )
    
  •        if selfObj._x_enchanting.punch then
    
  •            -- add knockback from enchantment
    
  •            -- the `punch.value` multiplier is too strong so divide it by half
    
  •            knockback = knockback * (selfObj._x_enchanting.punch.value / 2)
    
  •            pointed_thing.ref:add_velocity({
    
  •                x = dir.x * knockback * -1,
    
  •                y = 7,
    
  •                z = dir.z * knockback * -1
    
  •            })
    
  •        else
    
  •            pointed_thing.ref:add_velocity({
    
  •                x = dir.x * knockback * -1,
    
  •                y = 5,
    
  •                z = dir.z * knockback * -1
    
  •            })
    
  •        end
    
  •        pointed_thing.ref:punch(
    
  •            selfObj.object,
    
  •            selfObj._tflp,
    
  •            {
    
  •                full_punch_interval = selfObj._tool_capabilities.full_punch_interval,
    
  •                damage_groups = { fleshy = _damage, knockback = knockback }
    
  •            },
    
  •            {
    
  •                x = dir.x * -1,
    
  •                y = -7,
    
  •                z = dir.z * -1
    
  •            }
    
  •        )
    
  •        selfObj._caused_damage = _damage
    
  •        selfObj._caused_knockback = knockback
    
  •        XBows:show_damage_numbers(selfObj.object:get_pos(), _damage, selfObj._is_critical_hit)
    
  •        -- already dead (entity)
    
  •        if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then
    
  •            selfObj.object:remove()
    
  •            return
    
  •        end
    
  •        -- already dead (player)
    
  •        if pointed_thing.ref:get_hp() <= 0 then
    
  •            selfObj.object:remove()
    
  •            return
    
  •        end
    
  •        -- attach arrow prepare
    
  •        local rotation = { x = 0, y = 0, z = 0 }
    
  •        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)
    
  •        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)
    
  •        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)
    
  •        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)
    
  •        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)
    
  •        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)
    
  •        end
    
  •        if not XBows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then
    
  •            selfObj.object:remove()
    
  •            return
    
  •        end
    
  •        ---normalize arrow scale when attached to scaled entity
    
  •        ---(prevents huge arrows when attached to scaled up entity models)
    
  •        local obj_props = selfObj.object:get_properties()
    
  •        local obj_to_props = pointed_thing.ref:get_properties()
    
  •        local vs = vector.divide(obj_props.visual_size, obj_to_props.visual_size)
    
  •        selfObj.object:set_properties({ visual_size = vs })
    
  •        -- attach arrow
    
  •        local position = vector.subtract(
    
  •            ip_pos,
    
  •            pointed_thing.ref:get_pos()
    
  •        )
    
  •        if pointed_thing.ref:is_player() then
    
  •            position = vector.multiply(position, 10)
    
  •        end
    
  •        ---`after` here prevents visual glitch when the arrow still shows as huge for a split second
    
  •        ---before the new calculated scale is applied
    
  •        minetest.after(0, function()
    
  •            selfObj.object:set_attach(
    
  •                pointed_thing.ref,
    
  •                '',
    
  •                position,
    
  •                rotation,
    
  •                true
    
  •            )
    
  •        end)
    
  •        selfObj._attached = true
    
  •        selfObj._attached_to.type = pointed_thing.type
    
  •        selfObj._attached_to.pos = position
    
  •        -- remove last arrow when too many already attached
    
  •        local children = {}
    
  •        local projectile_entity = self.registered_arrows[selfObj._arrow_name].custom.projectile_entity
    
  •        for _, object in ipairs(pointed_thing.ref:get_children()) do
    
  •            if object:get_luaentity() and object:get_luaentity().name == projectile_entity then
    
  •                table.insert(children, object)
    
  •            end
    
  •        end
    
  •        if #children >= 5 then
    
  •            children[1]:remove()
    
  •        end
    
  •        if pointed_thing.ref:is_player() then
    
  •            local on_hit_player_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_player
    
  •            if on_hit_player_callback then
    
  •                on_hit_player_callback(selfObj, pointed_thing)
    
  •            end
    
  •        else
    
  •            local on_hit_entity_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_entity
    
  •            if on_hit_entity_callback then
    
  •                on_hit_entity_callback(selfObj, pointed_thing)
    
  •            end
    
  •        end
    
  •        return
    
  •    elseif pointed_thing.type == 'node' and not selfObj._attached then
    
  •        local node = minetest.get_node(pointed_thing.under)
    
  •        local node_def = minetest.registered_nodes[node.name]
    
  •        if not node_def then
    
  •            return
    
  •        end
    
  •        selfObj._velocity = selfObj.object:get_velocity()
    
  •        if node_def.drawtype == 'liquid' and not selfObj._is_drowning then
    
  •            selfObj._is_drowning = true
    
  •            selfObj._in_liquid = true
    
  •            local drag = 1 / (node_def.liquid_viscosity * 6)
    
  •            selfObj.object:set_velocity(vector.multiply(selfObj._velocity, drag))
    
  •            selfObj.object:set_acceleration({ x = 0, y = -1.0, z = 0 })
    
  •            XBows:get_particle_effect_for_arrow('bubble', selfObj._old_pos)
    
  •        elseif selfObj._is_drowning then
    
  •            selfObj._is_drowning = false
    
  •            if selfObj._velocity then
    
  •                selfObj.object:set_velocity(selfObj._velocity)
    
  •            end
    
  •            selfObj.object:set_acceleration({ x = 0, y = -9.81, z = 0 })
    
  •        end
    
  •        if XBows.mesecons and node.name == 'x_bows:target' then
    
  •            local distance = vector.distance(pointed_thing.under, ip_pos)
    
  •            distance = math.floor(distance * 100) / 100
    
  •            -- 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
    
  •        if node_def.walkable then
    
  •            selfObj.object:set_velocity({ x = 0, y = 0, z = 0 })
    
  •            selfObj.object:set_acceleration({ x = 0, y = 0, z = 0 })
    
  •            selfObj.object:set_pos(ip_pos)
    
  •            selfObj.object:set_rotation(selfObj.object:get_rotation())
    
  •            selfObj._attached = true
    
  •            selfObj._attached_to.type = pointed_thing.type
    
  •            selfObj._attached_to.pos = pointed_thing.under
    
  •            selfObj.object:set_properties({ collisionbox = { -0.2, -0.2, -0.2, 0.2, 0.2, 0.2 } })
    
  •            -- remove last arrow when too many already attached
    
  •            local children = {}
    
  •            local projectile_entity = self.registered_arrows[selfObj._arrow_name].custom.projectile_entity
    
  •            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 == projectile_entity
    
  •                then
    
  •                    table.insert(children, object)
    
  •                end
    
  •            end
    
  •            if #children >= 5 then
    
  •                children[#children]:remove()
    
  •            end
    
  •            ---Wiggle
    
  •            local x_bows_registered_entity_def = self.registered_entities[selfObj.name]
    
  •            if x_bows_registered_entity_def and x_bows_registered_entity_def._custom.animations.on_hit_node then
    
  •                selfObj.object:set_animation(
    
  •                    unpack(x_bows_registered_entity_def._custom.animations.on_hit_node)--[[@as table]]
    
  •                )
    
  •            end
    
  •            ---API callbacks
    
  •            local on_hit_node_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_node
    
  •            if on_hit_node_callback then
    
  •                on_hit_node_callback(selfObj, pointed_thing)
    
  •            end
    
  •            local new_pos = selfObj.object:get_pos()
    
  •            if new_pos then
    
  •                minetest.add_particlespawner({
    
  •                    amount = 5,
    
  •                    time = 0.25,
    
  •                    minpos = { x = new_pos.x - 0.4, y = new_pos.y + 0.2, z = new_pos.z - 0.4 },
    
  •                    maxpos = { x = new_pos.x + 0.4, y = new_pos.y + 0.3, z = new_pos.z + 0.4 },
    
  •                    minvel = { x = 0, y = 3, z = 0 },
    
  •                    maxvel = { x = 0, y = 4, z = 0 },
    
  •                    minacc = { x = 0, y = -28, z = 0 },
    
  •                    maxacc = { x = 0, y = -32, z = 0 },
    
  •                    minexptime = 1,
    
  •                    maxexptime = 1.5,
    
  •                    node = { name = node_def.name },
    
  •                    collisiondetection = true,
    
  •                    object_collision = true,
    
  •                })
    
  •            end
    
  •            minetest.sound_play(selfObj._sound_hit, {
    
  •                pos = pointed_thing.under,
    
  •                gain = 0.6,
    
  •                max_hear_distance = 16
    
  •            })
    
  •            return
    
  •        end
    
  •    end
    
  •    pointed_thing = ray:next()
    
  • end
  • selfObj._old_pos = pos
    +end

+---Function receive a "luaentity" table as self. Called when somebody punches the object.
+---Note that you probably want to handle most punches using the automatic armor group system.
+---Can return true to prevent the default damage mechanism.
+---@param self XBows
+---@param selfObj EnityCustomAttrDef
+---@param puncher ObjectRef|nil
+---@param time_from_last_punch number|integer|nil
+---@param tool_capabilities ToolCapabilitiesDef
+---@param dir Vector
+---@param damage number|integer
+---@return boolean
+function XBowsEntityDef.on_punch(self, selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage)

  • local pos = selfObj.object:get_pos()
  • if pos then
  •    minetest.sound_play('default_dig_choppy', {
    
  •        pos = pos,
    
  •        gain = 0.4
    
  •    })
    
  • end
  • return false
    +end

+---Register new projectile entity
+---@param self XBows
+---@param name string
+---@param def XBowsEntityDef
+function XBows.register_entity(self, name, def)

  • def._custom = def._custom or {}
  • def._custom.animations = def._custom.animations or {}
  • local mod_name = def._custom.mod_name or 'x_bows'
  • def._custom.name = mod_name .. ':' .. name
  • def.initial_properties = mergeTables({
  •    ---defaults
    
  •    visual = 'wielditem',
    
  •    collisionbox = { 0, 0, 0, 0, 0, 0 },
    
  •    selectionbox = { 0, 0, 0, 0, 0, 0 },
    
  •    physical = false,
    
  •    textures = { 'air' },
    
  •    hp_max = 1,
    
  •    visual_size = { x = 1, y = 1, z = 1 },
    
  •    glow = 1
    
  • }, def.initial_properties or {})
  • def.on_death = function(selfObj, killer)
  •    return XBowsEntityDef:on_death(selfObj, killer)
    
  • end
  • if def._custom.on_death then
  •    def.on_death = def._custom.on_death
    
  • end
  • def.on_activate = function(selfObj, killer)
  •    return XBowsEntityDef:on_activate(selfObj, killer)
    
  • end
  • def.on_step = function(selfObj, dtime)
  •    return XBowsEntityDef:on_step(selfObj, dtime)
    
  • end
  • def.on_punch = function(selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage)
  •    return XBowsEntityDef:on_punch(selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage)
    
  • end
  • if def._custom.on_punch then
  •    def.on_punch = def._custom.on_punch
    
  • end
  • self.registered_entities[def._custom.name] = def
  • minetest.register_entity(def._custom.name, {
  •    initial_properties = def.initial_properties,
    
  •    on_death = def.on_death,
    
  •    on_activate = def.on_activate,
    
  •    on_step = def.on_step,
    
  •    on_punch = def.on_punch
    
  • })
    +end

+----
+--- QUIVER API
+----
+
+---Close one or all open quivers in players inventory
+---@param self XBowsQuiver
+---@param player ObjectRef
+---@param quiver_id? string If nil then all open quivers will be closed
+---@return nil
+function XBowsQuiver.close_quiver(self, 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 = self:get_replacement_item(st, 'x_bows:quiver')
    
  •                player_inv:set_stack('main', i, replace_item)
    
  •                break
    
  •            else
    
  •                local replace_item = self: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 self XBowsQuiver
+---@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 XBowsQuiver.get_replacement_item(self, 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 self XBowsQuiver
+---@param player ObjectRef
+---@diagnostic disable-next-line: codestyle-check
+---@return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number}
+function XBowsQuiver.get_itemstack_arrow_from_quiver(self, player)

  • local player_inv = player:get_inventory()
  • local wielded_stack = player:get_wielded_item()
  • ---@type ItemStack|nil
  • local found_arrow_stack
  • local found_arrow_stack_idx = 1
  • local prev_detached_inv_list = {}
  • local quiver_id
  • local quiver_name
  • ---check quiver inventory slot
  • if player_inv and player_inv:contains_item('x_bows:quiver_inv', 'x_bows:quiver') then
  •    local player_name = player:get_player_name()
    
  •    local quiver_stack = player_inv:get_stack('x_bows:quiver_inv', 1)
    
  •    local st_meta = quiver_stack:get_meta()
    
  •    quiver_id = st_meta:get_string('quiver_id')
    
  •    local detached_inv = self: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
    
  •                local is_allowed_ammunition = self:is_allowed_ammunition(wielded_stack:get_name(), qst:get_name())
    
  •                if is_allowed_ammunition then
    
  •                    quiver_name = quiver_stack:get_name()
    
  •                    found_arrow_stack = qst:take_item()
    
  •                    found_arrow_stack_idx = j
    
  •                    ---X Enchanting
    
  •                    local wielded_stack_meta = wielded_stack:get_meta()
    
  •                    local is_infinity = wielded_stack_meta:get_float('is_infinity')
    
  •                    if not self:is_creative(player_name) and is_infinity == 0 then
    
  •                        -- take item will be set
    
  •                        detached_inv:set_list('main', detached_inv_list)
    
  •                        self:save(detached_inv, player, true)
    
  •                    end
    
  •                end
    
  •            end
    
  •        end
    
  •    end
    
  •    if found_arrow_stack then
    
  •        ---show HUD - quiver inventory
    
  •        self:udate_or_create_hud(player, prev_detached_inv_list, found_arrow_stack_idx)
    
  •    end
    
  • end
  • if self.fallback_quiver then
  •    ---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 = self: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
    
  •                            local is_allowed_ammunition = self:is_allowed_ammunition(
    
  •                                wielded_stack:get_name(),
    
  •                                qst:get_name()
    
  •                            )
    
  •                            if is_allowed_ammunition then
    
  •                                quiver_name = st:get_name()
    
  •                                found_arrow_stack = qst:take_item()
    
  •                                found_arrow_stack_idx = j
    
  •                                if not self:is_creative(player_name) then
    
  •                                    detached_inv:set_list('main', detached_inv_list)
    
  •                                    self:save(detached_inv, player, true)
    
  •                                end
    
  •                            end
    
  •                        end
    
  •                    end
    
  •                end
    
  •            end
    
  •            if found_arrow_stack then
    
  •                ---show HUD - quiver inventory
    
  •                self:udate_or_create_hud(player, prev_detached_inv_list, found_arrow_stack_idx)
    
  •                break
    
  •            end
    
  •        end
    
  •    end
    
  • end
  • return {
  •    found_arrow_stack = found_arrow_stack,
    
  •    quiver_id = quiver_id,
    
  •    quiver_name = quiver_name,
    
  •    found_arrow_stack_idx = found_arrow_stack_idx
    
  • }
    +end

+---Remove all added HUDs
+---@param self XBowsQuiver
+---@param player ObjectRef
+---@return nil
+function XBowsQuiver.remove_hud(self, player)

  • local player_name = player:get_player_name()
  • if self.hud_item_ids[player_name] then
  •    for _, v in pairs(self.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
    
  •    self.hud_item_ids[player_name] = {
    
  •        arrow_inv_img = {},
    
  •        stack_count = {}
    
  •    }
    
  • else
  •    self.hud_item_ids[player_name] = {
    
  •        arrow_inv_img = {},
    
  •        stack_count = {}
    
  •    }
    
  • end
    +end

+---@todo implement hud_change?
+---Update or create quiver HUD
+---@param self XBowsQuiver
+---@param player ObjectRef
+---@param inv_list ItemStack[]
+---@param idx? number
+---@return nil
+function XBowsQuiver.udate_or_create_hud(self, player, inv_list, idx)

  • local _idx = idx or 1
  • local player_name = player:get_player_name()
  • local selected_bg_added = false
  • local is_arrow = #inv_list == 1
  • local item_def = minetest.registered_items['x_bows:quiver']
  • local is_no_ammo = false
  • if is_arrow then
  •    item_def = minetest.registered_items[inv_list[1]:get_name()]
    
  •    is_no_ammo = inv_list[1]:get_name() == 'x_bows:no_ammo'
    
  • end
  • if is_no_ammo then
  •    item_def = {
    
  •        inventory_image = 'x_bows_arrow_slot.png',
    
  •        short_description = S('No Ammo') .. '!'
    
  •    }
    
  • end
  • if not item_def then
  •    return
    
  • end
  • ---cancel previous timeouts and reset
  • if self.after_job[player_name] then
  •    for _, v in pairs(self.after_job[player_name]) do
    
  •        v:cancel()
    
  •    end
    
  •    self.after_job[player_name] = {}
    
  • else
  •    self.after_job[player_name] = {}
    
  • end
  • self:remove_hud(player)
  • ---title image
  • self.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 = item_def.inventory_image,
    
  •    scale = { x = 4, y = 4 },
    
  •    alignment = 0,
    
  • })
  • ---title copy
  • self.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 = item_def.short_description,
    
  •    alignment = 0,
    
  •    scale = { x = 100, y = 30 },
    
  •    number = 0xFFFFFF,
    
  • })
  • ---hotbar bg
  • self.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 = is_arrow and 'x_bows_single_hotbar.png' or '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 is_no_ammo then
    
  •            found_arrow_stack_def = item_def
    
  •        end
    
  •        if not selected_bg_added and j == _idx then
    
  •            selected_bg_added = true
    
  •            ---ui selected bg
    
  •            self.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_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(self.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(self.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 = is_no_ammo and 0 or qst:get_count(),
    
  •                alignment = -1,
    
  •                scale = { x = 50, y = 10 },
    
  •                number = 0xFFFFFF,
    
  •            }))
    
  •        end
    
  •    end
    
  • end
  • ---@param v_player ObjectRef
  • table.insert(self.after_job[player_name], minetest.after(10, function(v_player)
  •    self:remove_hud(v_player)
    
  • end, player))
    +end

+---Get existing detached inventory or create new one
+---@param self XBowsQuiver
+---@param quiver_id string
+---@param player_name string
+---@param quiver_items? string
+---@return InvRef
+function XBowsQuiver.get_or_create_detached_inv(self, 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 self: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 self: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 self: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)
    
  •            self: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)
    
  •            local quiver_inv_st = player:get_inventory():get_stack('x_bows:quiver_inv', 1)
    
  •            if quiver_inv_st and quiver_inv_st:get_meta():get_string('quiver_id') == inv:get_location().name then
    
  •                if inv:is_empty('main') then
    
  •                    self:show_3d_quiver(player, { is_empty = true })
    
  •                else
    
  •                    self:show_3d_quiver(player)
    
  •                end
    
  •            end
    
  •            self: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)
    
  •            local quiver_inv_st = player:get_inventory():get_stack('x_bows:quiver_inv', 1)
    
  •            if quiver_inv_st and quiver_inv_st:get_meta():get_string('quiver_id') == inv:get_location().name then
    
  •                if inv:is_empty('main') then
    
  •                    self:show_3d_quiver(player, { is_empty = true })
    
  •                else
    
  •                    self:show_3d_quiver(player)
    
  •                end
    
  •            end
    
  •            self: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
  •    self:set_string_to_inv(detached_inv, quiver_items)
    
  • end
  • return detached_inv
    +end

+---Create formspec
+---@param self XBowsQuiver
+---@param name string name of the form
+---@return string
+function XBowsQuiver.get_formspec(self, 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]'
    
  • }
  • if minetest.global_exists('default') then
  •    formspec[#formspec + 1] = default.get_hotbar_bg(0, height + 0.85)
    
  • end
  • --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 + 1] = 'image[' .. px .. ',' .. py .. ';1,1;x_bows_arrow_slot.png]'
    
  •    end
    
  •    px = px + 1
    
  • end
  • formspec = table.concat(formspec, '')
  • return formspec
    +end

+---Convert inventory of itemstacks to serialized string
+---@param self XBowsQuiver
+---@param inv InvRef
+---@return {['inv_string']: string, ['content_description']: string}
+function XBowsQuiver.get_string_from_inv(self, 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 '\n' .. S('Empty') or content_description
    
  • }
    +end

+---Set items from serialized string to inventory
+---@param self XBowsQuiver
+---@param inv InvRef inventory to add items to
+---@param str string previously stringified inventory of itemstacks
+---@return nil
+function XBowsQuiver.set_string_to_inv(self, 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 self XBowsQuiver
+---@param inv InvRef
+---@param player ObjectRef
+---@param quiver_is_closed? boolean
+---@return nil
+function XBowsQuiver.save(self, inv, player, quiver_is_closed)

  • local player_inv = player:get_inventory() --@as InvRef
  • local inv_loc = inv:get_location()
  • local quiver_item_name = quiver_is_closed and 'x_bows:quiver' or 'x_bows:quiver_open'
  • local player_quiver_inv_stack = player_inv:get_stack('x_bows:quiver_inv', 1)
  • if not player_quiver_inv_stack:is_empty()
  •    and player_quiver_inv_stack:get_meta():get_string('quiver_id') == inv_loc.name
    
  • then
  •    local st_meta = player_quiver_inv_stack:get_meta()
    
  •    ---save inventory items in quiver item meta
    
  •    local string_from_inventory_result = self:get_string_from_inv(inv)
    
  •    st_meta:set_string('quiver_items', string_from_inventory_result.inv_string)
    
  •    ---update description
    
  •    local new_description = player_quiver_inv_stack:get_short_description() .. '\n' ..
    
  •        string_from_inventory_result.content_description .. '\n'
    
  •    st_meta:set_string('description', new_description)
    
  •    player_inv:set_stack('x_bows:quiver_inv', 1, player_quiver_inv_stack)
    
  • elseif player_inv and player_inv:contains_item('main', quiver_item_name) then
  •    ---find matching quiver item in players inventory with the open formspec name
    
  •    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 = self: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 self XBowsQuiver
+---@param inv InvRef
+---@param player ObjectRef
+---@return boolean
+function XBowsQuiver.quiver_can_allow(self, inv, player)

  • local player_inv = player:get_inventory() --@as InvRef
  • local inv_loc = inv:get_location()
  • local player_quiver_inv_stack = player_inv:get_stack('x_bows:quiver_inv', 1)
  • if not player_quiver_inv_stack:is_empty()
  •    and player_quiver_inv_stack:get_meta():get_string('quiver_id') == inv_loc.name
    
  • then
  •    ---find quiver in player `quiver_inv` inv list
    
  •    return true
    
  • elseif player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then
  •    ---find quiver in player `main` inv list
    
  •    ---matching quiver item in players inventory with the open formspec name
    
  •    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 self XBows
+---@param itemstack ItemStack
+---@param user ObjectRef
+---@return ItemStack
+function XBows.open_quiver(self, 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() .. '_' .. self.uuid()
    
  •    itemstack_meta:set_string('quiver_id', quiver_id)
    
  • end
  • local quiver_items = itemstack_meta:get_string('quiver_items')
  • XBowsQuiver:get_or_create_detached_inv(quiver_id, pname, quiver_items)
  • ---show open variation of quiver
  • local replace_item = XBowsQuiver: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, XBowsQuiver:get_formspec(quiver_id))
  • return itemstack
    +end

+---Register sfinv page
+---@param self XBowsQuiver
+function XBowsQuiver.sfinv_register_page(self)

  • sfinv.register_page('x_bows:quiver_page', {
  •    title = 'X Bows',
    
  •    get = function(this, player, context)
    
  •        local formspec = {
    
  •            ---arrow
    
  •            'label[0,0;' .. minetest.formspec_escape(S('Arrows')) .. ':]',
    
  •            'list[current_player;x_bows:arrow_inv;0,0.5;1,1;]',
    
  •            'image[0,0.5;1,1;x_bows_arrow_slot.png;]',
    
  •            'listring[current_player;x_bows:arrow_inv]',
    
  •            'listring[current_player;main]',
    
  •            ---quiver
    
  •            'label[3.5,0;' .. minetest.formspec_escape(S('Quiver')) .. ':]',
    
  •            'list[current_player;x_bows:quiver_inv;3.5,0.5;1,1;]',
    
  •            'image[3.5,0.5;1,1;x_bows_quiver_slot.png]',
    
  •            'listring[current_player;x_bows:quiver_inv]',
    
  •            'listring[current_player;main]',
    
  •        }
    
  •        local player_inv = player:get_inventory() --[[@as InvRef]]
    
  •        context._itemstack_arrow = player_inv:get_stack('x_bows:arrow_inv', 1)
    
  •        context._itemstack_quiver = player_inv:get_stack('x_bows:quiver_inv', 1)
    
  •        if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then
    
  •            local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()]
    
  •            local short_description = context._itemstack_arrow:get_short_description()
    
  •            if x_bows_registered_arrow_def and short_description then
    
  •                formspec[#formspec + 1] = 'label[0,1.5;' ..
    
  •                    minetest.formspec_escape(short_description) .. '\n' ..
    
  •                    minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']'
    
  •            end
    
  •        end
    
  •        if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then
    
  •            local st_meta = context._itemstack_quiver:get_meta()
    
  •            local quiver_id = st_meta:get_string('quiver_id')
    
  •            local short_description = context._itemstack_quiver:get_short_description()
    
  •            ---description
    
  •            if short_description then
    
  •                formspec[#formspec + 1] = 'label[3.5,1.5;' ..
    
  •                    minetest.formspec_escape(short_description) .. ']'
    
  •            end
    
  •            formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;4.5,0.5;3,1;]'
    
  •            formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]'
    
  •            formspec[#formspec + 1] = 'listring[current_player;main]'
    
  •        end
    
  •        return sfinv.make_formspec(player, context, table.concat(formspec, ''), true)
    
  •    end
    
  • })
    +end

+---Register i3 page
+function XBowsQuiver.i3_register_page(self)

  • i3.new_tab('x_bows:quiver_page', {
  •    description = 'X Bows',
    
  •    formspec = function(player, data, fs)
    
  •        local formspec = {
    
  •            ---arrow
    
  •            'label[0.5,1;' .. minetest.formspec_escape(S('Arrows')) .. ':]',
    
  •            'list[current_player;x_bows:arrow_inv;0.5,1.5;1,1;]',
    
  •            'listring[current_player;x_bows:arrow_inv]',
    
  •            'listring[current_player;main]',
    
  •            ---quiver
    
  •            'label[5,1;' .. minetest.formspec_escape(S('Quiver')) .. ':]',
    
  •            'list[current_player;x_bows:quiver_inv;5,1.5;1,1;]',
    
  •            'listring[current_player;x_bows:quiver_inv]',
    
  •            'listring[current_player;main]',
    
  •            ---main
    
  •            'background9[0,0;10.23,12;i3_bg_full.png;false;12]',
    
  •            'listcolors[#bababa50;#bababa99]',
    
  •            'style_type[box;colors=#77777710,#77777710,#777,#777]',
    
  •            'box[0.22,6.9;1,1;]',
    
  •            'box[1.32,6.9;1,1;]',
    
  •            'box[2.42,6.9;1,1;]',
    
  •            'box[3.52,6.9;1,1;]',
    
  •            'box[4.62,6.9;1,1;]',
    
  •            'box[5.72,6.9;1,1;]',
    
  •            'box[6.82,6.9;1,1;]',
    
  •            'box[7.92,6.9;1,1;]',
    
  •            'box[9.02,6.9;1,1;]',
    
  •            'style_type[list;size=1;spacing=0.1]',
    
  •            'list[current_player;main;0.22,6.9;9,1;]',
    
  •            'style_type[list;size=1;spacing=0.1,0.1]',
    
  •            'list[current_player;main;0.22,8.05;9,4;9]',
    
  •            'style_type[list;size=1;spacing=0.15]',
    
  •            'listring[current_player;craft]listring[current_player;main]'
    
  •        }
    
  •        local context = {}
    
  •        local player_inv = player:get_inventory()
    
  •        context._itemstack_arrow = player_inv:get_stack('x_bows:arrow_inv', 1)
    
  •        context._itemstack_quiver = player_inv:get_stack('x_bows:quiver_inv', 1)
    
  •        if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then
    
  •            local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()]
    
  •            if x_bows_registered_arrow_def then
    
  •                formspec[#formspec + 1] = 'label[0.5,3;' ..
    
  •                    minetest.formspec_escape(context._itemstack_arrow:get_short_description()) .. '\n' ..
    
  •                    minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']'
    
  •            end
    
  •        end
    
  •        if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then
    
  •            local st_meta = context._itemstack_quiver:get_meta()
    
  •            local quiver_id = st_meta:get_string('quiver_id')
    
  •            ---description
    
  •            formspec[#formspec + 1] = 'label[5,3;' ..
    
  •                minetest.formspec_escape(context._itemstack_quiver:get_short_description()) .. ']'
    
  •            formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;6.3,1.5;3,1;]'
    
  •            formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]'
    
  •            formspec[#formspec + 1] = 'listring[current_player;main]'
    
  •        end
    
  •        formspec = table.concat(formspec, '')
    
  •        fs(formspec)
    
  •    end
    
  • })
    +end

+---Register i3 page
+function XBowsQuiver.ui_register_page(self)

  • unified_inventory.register_page('x_bows:quiver_page', {
  •    get_formspec = function(player, data, fs)
    
  •        local formspec = {
    
  •            unified_inventory.style_full.standard_inv_bg,
    
  •            'listcolors[#00000000;#00000000]',
    
  •            ---arrow
    
  •            'label[0.5,0.5;' .. minetest.formspec_escape(S('Arrows')) .. ':]',
    
  •            unified_inventory.single_slot(0.4, 0.9),
    
  •            'list[current_player;x_bows:arrow_inv;0.5,1;1,1;]',
    
  •            'listring[current_player;x_bows:arrow_inv]',
    
  •            'listring[current_player;main]',
    
  •            ---quiver
    
  •            'label[5,0.5;' .. minetest.formspec_escape(S('Quiver')) .. ':]',
    
  •            unified_inventory.single_slot(4.9, 0.9),
    
  •            'list[current_player;x_bows:quiver_inv;5,1;1,1;]',
    
  •            'listring[current_player;x_bows:quiver_inv]',
    
  •            'listring[current_player;main]',
    
  •        }
    
  •        local context = {}
    
  •        context._itemstack_arrow = player:get_inventory():get_stack('x_bows:arrow_inv', 1)
    
  •        context._itemstack_quiver = player:get_inventory():get_stack('x_bows:quiver_inv', 1)
    
  •        if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then
    
  •            local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()]
    
  •            if x_bows_registered_arrow_def then
    
  •                formspec[#formspec + 1] = 'label[0.5,2.5;' ..
    
  •                    minetest.formspec_escape(context._itemstack_arrow:get_short_description()) .. '\n' ..
    
  •                    minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']'
    
  •            end
    
  •        end
    
  •        if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then
    
  •            local st_meta = context._itemstack_quiver:get_meta()
    
  •            local quiver_id = st_meta:get_string('quiver_id')
    
  •            ---description
    
  •            formspec[#formspec + 1] = 'label[5,2.5;' ..
    
  •                minetest.formspec_escape(context._itemstack_quiver:get_short_description()) .. ']'
    
  •            formspec[#formspec + 1] = unified_inventory.single_slot(6.4, 0.9)
    
  •            formspec[#formspec + 1] = unified_inventory.single_slot(7.65, 0.9)
    
  •            formspec[#formspec + 1] = unified_inventory.single_slot(8.9, 0.9)
    
  •            formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;6.5,1;3,1;]'
    
  •            formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]'
    
  •            formspec[#formspec + 1] = 'listring[current_player;main]'
    
  •        end
    
  •        return {
    
  •            formspec = table.concat(formspec, '')
    
  •        }
    
  •    end
    
  • })
  • unified_inventory.register_button('x_bows:quiver_page', {
  •    type = 'image',
    
  •    image = "x_bows_bow_wood_charged.png",
    
  •    tooltip = 'X Bows',
    
  • })
    +end

+function XBowsQuiver.show_3d_quiver(self, player, props)

  • if not XBows.settings.x_bows_show_3d_quiver or not XBows.player_api then
  •    return
    
  • end
  • local _props = props or {}
  • local p_name = player:get_player_name()
  • local quiver_texture = 'x_bows_quiver_mesh.png'
  • local player_textures
  • if _props.is_empty then
  •    quiver_texture = 'x_bows_quiver_empty_mesh.png'
    
  • end
  • if self.skinsdb then
  •    minetest.after(1, function()
    
  •        local textures = player_api.get_textures(player)
    
  •        ---cleanup
    
  •        for index, value in ipairs(textures) do
    
  •            if value == 'x_bows_quiver_blank_mesh.png' or value == 'x_bows_quiver_mesh.png'
    
  •                or value == 'x_bows_quiver_empty_mesh.png'
    
  •            then
    
  •                table.remove(textures, index)
    
  •            end
    
  •        end
    
  •        table.insert(textures, quiver_texture)
    
  •        player_textures = textures
    
  •        if player_textures then
    
  •            if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then
    
  •                self.quiver_empty_state[player:get_player_name()] = true
    
  •                player_api.set_textures(player, player_textures)
    
  •            elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then
    
  •                self.quiver_empty_state[player:get_player_name()] = false
    
  •                player_api.set_textures(player, player_textures)
    
  •            end
    
  •        end
    
  •    end)
    
  •    return
    
  • elseif self._3d_armor then
  •    minetest.after(0.1, function()
    
  •        player_textures = {
    
  •            armor.textures[p_name].skin,
    
  •            armor.textures[p_name].armor,
    
  •            armor.textures[p_name].wielditem,
    
  •            quiver_texture
    
  •        }
    
  •        if player_textures then
    
  •            if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then
    
  •                self.quiver_empty_state[player:get_player_name()] = true
    
  •                player_api.set_textures(player, player_textures)
    
  •            elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then
    
  •                self.quiver_empty_state[player:get_player_name()] = false
    
  •                player_api.set_textures(player, player_textures)
    
  •            end
    
  •        end
    
  •    end)
    
  •    return
    
  • elseif self.u_skins then
  •    local u_skin_texture = u_skins.u_skins[p_name]
    
  •    player_textures = {
    
  •        u_skin_texture .. '.png',
    
  •        quiver_texture
    
  •    }
    
  • elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins[p_name] then
  •    player_textures = {
    
  •        wardrobe.playerSkins[p_name],
    
  •        quiver_texture
    
  •    }
    
  • else
  •    local textures = player_api.get_textures(player)
    
  •    ---cleanup
    
  •    for index, value in ipairs(textures) do
    
  •        if value == 'x_bows_quiver_blank_mesh.png' or value == 'x_bows_quiver_mesh.png'
    
  •            or value == 'x_bows_quiver_empty_mesh.png'
    
  •        then
    
  •            table.remove(textures, index)
    
  •        end
    
  •    end
    
  •    table.insert(textures, quiver_texture)
    
  •    player_textures = textures
    
  • end
  • if player_textures then
  •    if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then
    
  •        self.quiver_empty_state[player:get_player_name()] = true
    
  •        player_api.set_textures(player, player_textures)
    
  •    elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then
    
  •        self.quiver_empty_state[player:get_player_name()] = false
    
  •        player_api.set_textures(player, player_textures)
    
  •    end
    
  • end
    +end

+function XBowsQuiver.hide_3d_quiver(self, player)

  • if not XBows.settings.x_bows_show_3d_quiver or not XBows.player_api then
  •    return
    
  • end
  • local p_name = player:get_player_name()
  • local player_textures
  • if self.skinsdb then
  •    minetest.after(1, function()
    
  •        local textures = player_api.get_textures(player)
    
  •        ---cleanup
    
  •        for index, value in ipairs(textures) do
    
  •            if value == 'x_bows_quiver_mesh.png' or value == 'x_bows_quiver_blank_mesh.png'
    
  •                or value == 'x_bows_quiver_empty_mesh.png'
    
  •            then
    
  •                table.remove(textures, index)
    
  •            end
    
  •        end
    
  •        table.insert(textures, 'x_bows_quiver_blank_mesh.png')
    
  •        player_textures = textures
    
  •        if player_textures then
    
  •            player_api.set_textures(player, player_textures)
    
  •        end
    
  •    end)
    
  •    return
    
  • elseif self._3d_armor then
  •    minetest.after(0.1, function()
    
  •        player_textures = {
    
  •            armor.textures[p_name].skin,
    
  •            armor.textures[p_name].armor,
    
  •            armor.textures[p_name].wielditem,
    
  •            'x_bows_quiver_blank_mesh.png'
    
  •        }
    
  •        if player_textures then
    
  •            player_api.set_textures(player, player_textures)
    
  •        end
    
  •    end)
    
  •    return
    
  • elseif self.u_skins then
  •    local u_skin_texture = u_skins.u_skins[p_name]
    
  •    player_textures = {
    
  •        u_skin_texture .. '.png',
    
  •        'x_bows_quiver_blank_mesh.png'
    
  •    }
    
  • elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins[p_name] then
  •    player_textures = {
    
  •        wardrobe.playerSkins[p_name],
    
  •        'x_bows_quiver_blank_mesh.png'
    
  •    }
    
  • else
  •    local textures = player_api.get_textures(player)
    
  •    ---cleanup
    
  •    for index, value in ipairs(textures) do
    
  •        if value == 'x_bows_quiver_mesh.png' or value == 'x_bows_quiver_blank_mesh.png'
    
  •            or value == 'x_bows_quiver_empty_mesh.png'
    
  •        then
    
  •            table.remove(textures, index)
    
  •        end
    
  •    end
    
  •    table.insert(textures, 'x_bows_quiver_blank_mesh.png')
    
  •    player_textures = textures
    
  • end
  • if player_textures then
  •    player_api.set_textures(player, player_textures)
    
  • end
    +end

+---string split to characters
+---@param str string
+---@return string[] | nil
+local function split(str)

  • if #str > 0 then
  •    return str:sub(1, 1), split(str:sub(2))
    
  • end
    +end

+function XBows.show_damage_numbers(self, pos, damage, is_crit)

  • if not pos or not self.settings.x_bows_show_damage_numbers then
  •    return
    
  • end
  • ---get damage texture
  • local dmgstr = tostring(math.round(damage))
  • local results = { split(dmgstr) }
  • local texture = ''
  • local dmg_nr_offset = 0
  • for i, value in ipairs(results) do
  •    if i == 1 then
    
  •        texture = texture .. '[combine:' .. 7 * #results .. 'x' .. 9 * #results .. ':0,0=x_bows_dmg_' .. value .. '.png'
    
  •    else
    
  •        texture = texture .. ':' .. dmg_nr_offset .. ',0=x_bows_dmg_' .. value .. '.png'
    
  •    end
    
  •    dmg_nr_offset = dmg_nr_offset + 7
    
  • end
  • if texture and texture ~= '' then
  •    local size = 7
    
  •    if is_crit then
    
  •        size = 14
    
  •        texture = texture .. '^[colorize:#FF0000:255'
    
  •    else
    
  •        texture = texture .. '^[colorize:#FFFF00:127'
    
  •    end
    
  •    ---show damage texture
    
  •    minetest.add_particlespawner({
    
  •        amount = 1,
    
  •        time = 0.01,
    
  •        minpos = { x = pos.x, y = pos.y + 1, z = pos.z },
    
  •        maxpos = { x = pos.x, y = pos.y + 2, z = pos.z },
    
  •        minvel = { x = math.random(-1, 1), y = 5, z = math.random(-1, 1) },
    
  •        maxvel = { x = math.random(-1, 1), y = 5, z = math.random(-1, 1) },
    
  •        minacc = { x = math.random(-1, 1), y = -7, z = math.random(-1, 1) },
    
  •        maxacc = { x = math.random(-1, 1), y = -7, z = math.random(-1, 1) },
    
  •        minexptime = 2,
    
  •        maxexptime = 2,
    
  •        minsize = size,
    
  •        maxsize = size,
    
  •        texture = texture,
    
  •        collisiondetection = true,
    
  •        glow = 10
    
  •    })
    
  • end
    +end
    diff --git a/arrow.lua b/arrow.lua
    index 9f4e158..e2b4000 100644
    --- a/arrow.lua
    +++ b/arrow.lua
    @@ -1,520 +1,31 @@
    --- Gets total armor level from 3d armor
    -local function get_3d_armor_armor(player)
  • 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
  • armor_total = armor.def[player:get_player_name()].level
  • return armor_total
    -end

--- Limits number x between min and max values
-local function limit(x, min, max)

  • return math.min(math.max(x, min), max)
    -end

--- Gets ObjectRef collision box
-local function get_obj_box(obj)

  • 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
  • return box
    -end

--- Poison Arrow Effects
-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
  • target_obj:set_properties({damage_texture_modifier = '^[colorize:#00FF0050'})
  • 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 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()
    
  •   -- if not lua_ent then
    
  •   -- 	return
    
  •   -- end
    
  •   -- 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
  • 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()
    
  •   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 = 0.5
    
  • },
  • on_activate = function(self, staticdata)
  •   if not self or not staticdata or staticdata == '' then
    
  •   	self.object:remove()
    
  •   	return
    
  •   end
    
  •   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
    
  •   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,
  • 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,
  • 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
    
  •   -- 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
    
  •   -- 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
    
  •   	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
    
  •   -- 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
    
  •   	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}})
    
  •   		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
    
  •   	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
    
  •   		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
    
  •   		-- crits
    
  •   		if self._is_critical_hit then
    
  •   			_damage = _damage * 2
    
  •   		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
    
  •   		)
    
  •   		pointed_thing.ref:add_velocity({
    
  •   			x = dir.x * knockback * -1,
    
  •   			y = 7,
    
  •   			z = dir.z * knockback * -1
    
  •   		})
    
  •   		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
    
  •   			}
    
  •   		)
    
  •   		-- already dead (entity)
    
  •   		if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then
    
  •   			self.object:remove()
    
  •   			return
    
  •   		end
    
  •   		-- 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
    
  •   		-- attach arrow prepare
    
  •   		local rotation = {x = 0, y = 0, z = 0}
    
  •   		local position = {x = 0, y = 0, z = 0}
    
  •   		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 = 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 = 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 = 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 = 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 = 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 = math.random(ymin, ymax) / 10
    
  •   			position.z = zmin / 10
    
  •   		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 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
    
  •   				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
    
  •   		if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then
    
  •   			self.object:remove()
    
  •   			return
    
  •   		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
    
  •   		local children = pointed_thing.ref:get_children()
    
  •   		-- remove last arrow when too many already attached
    
  •   		if #children >= 5 then
    
  •   			children[1]:remove()
    
  •   		end
    
  •   		return
    
  •   	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 not node_def then
    
  •   			return
    
  •   		end
    
  •   		self._velocity = self.object:get_velocity()
    
  •   		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})
    
  •   			x_bows.particle_effect(self._old_pos, 'bubble')
    
  •   		elseif self._is_drowning then
    
  •   			self._is_drowning = false
    
  •   			if self._velocity then
    
  •   				self.object:set_velocity(self._velocity)
    
  •   			end
    
  •   			self.object:set_acceleration({x = 0, y = -9.81, z = 0})
    
  •   		end
    
  •   		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
    
  •   			-- 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
    
  •   		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}})
    
  •   			-- remove last arrow when too many already attached
    
  •   			local children = {}
    
  •   			for k, 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 #children >= 5 then
    
  •   				children[#children]:remove()
    
  •   			end
    
  •   			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,
    -})
    \ No newline at end of file
    +--[[
  • X Bows. Adds bow and arrows with API.
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+XBows:register_entity('arrow_entity', {

  • initial_properties = {
  •    visual = 'mesh',
    
  •    mesh = 'x_bows_arrow.b3d',
    
  •    textures = { 'x_bows_arrow_mesh.png' },
    
  • },
  • _custom = {
  •    animations = {
    
  •        idle = { { x = 41, y = 42 }, 0, 0, false },
    
  •        on_hit_node = { { x = 1, y = 40 }, 40, 0, false }
    
  •    }
    
  • }
    +})
    diff --git a/assets/skinsdb_3d_armor_character_5.blend b/assets/skinsdb_3d_armor_character_5.blend
    new file mode 100644
    index 0000000..067cd74
    Binary files /dev/null and b/assets/skinsdb_3d_armor_character_5.blend differ
    diff --git a/assets/x_bows_3d_armor_character.blend b/assets/x_bows_3d_armor_character.blend
    new file mode 100644
    index 0000000..fb7d4ef
    Binary files /dev/null and b/assets/x_bows_3d_armor_character.blend differ
    diff --git a/assets/x_bows_arrow.blend b/assets/x_bows_arrow.blend
    new file mode 100644
    index 0000000..01acca7
    Binary files /dev/null and b/assets/x_bows_arrow.blend differ
    diff --git a/assets/x_bows_character.blend b/assets/x_bows_character.blend
    new file mode 100644
    index 0000000..da9824a
    Binary files /dev/null and b/assets/x_bows_character.blend differ
    diff --git a/bin/lua-language-server-3.5.6-linux-x64.tar.gz b/bin/lua-language-server-3.5.6-linux-x64.tar.gz
    new file mode 100644
    index 0000000..9b23cf5
    Binary files /dev/null and b/bin/lua-language-server-3.5.6-linux-x64.tar.gz differ
    diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml
    new file mode 100644
    index 0000000..70b139a
    --- /dev/null
    +++ b/bitbucket-pipelines.yml
    @@ -0,0 +1,77 @@
    +image: atlassian/default-image:3

+pipelines:

  • pull-requests:
  •    "**":
    
  •        - step:
    
  •            name: Install Node Dependencies
    
  •            caches:
    
  •                - node-modules
    
  •                - npm
    
  •                - nvm
    
  •            script:
    
  •                - nvm install v17.2.0
    
  •                - npm i -g npm@8
    
  •                - npm ci
    
  •        - parallel:
    
  •            - step:
    
  •                name: Lua Check
    
  •                script:
    
  •                    - apt-get update
    
  •                    - apt-get -y install lua5.1
    
  •                    - apt-get -y install luarocks
    
  •                    - luarocks install luacheck
    
  •                    - luacheck .
    
  •            - step:
    
  •                name: Lua Diagnostics
    
  •                caches:
    
  •                    - node-modules
    
  •                    - npm
    
  •                    - nvm
    
  •                script:
    
  •                    - nvm use v17.2.0
    
  •                    - npm run lua-diagnostics
    
  • tags:
  •    "*":
    
  •        - step:
    
  •            name: Install Node Dependencies
    
  •            caches:
    
  •                - node-modules
    
  •                - npm
    
  •                - nvm
    
  •            script:
    
  •                - nvm install v17.2.0
    
  •                - npm i -g npm@8
    
  •                - npm ci
    
  •        - parallel:
    
  •            - step:
    
  •                name: Lua Check
    
  •                script:
    
  •                    - apt-get update
    
  •                    - apt-get -y install lua5.1
    
  •                    - apt-get -y install luarocks
    
  •                    - luarocks install luacheck
    
  •                    - luacheck .
    
  •            - step:
    
  •                name: Lua Diagnostics
    
  •                caches:
    
  •                    - node-modules
    
  •                    - npm
    
  •                    - nvm
    
  •                script:
    
  •                    - nvm use v17.2.0
    
  •                    - npm run lua-diagnostics
    
  •        - step:
    
  •            name: Deploy to ContentDB
    
  •            caches:
    
  •                - node-modules
    
  •                - npm
    
  •                - nvm
    
  •            script:
    
  •                - nvm use v17.2.0
    
  •                - npm run push:ci -- --token=$CONTENT_DB_X_BOWS_TOKEN --title=$BITBUCKET_TAG
    

+definitions:

  • caches:
  •    node-modules: ./node_modules
    
  •    npm: ~/.npm
    
  •    nvm: ~/.nvm
    

diff --git a/config.ld b/config.ld
new file mode 100644
index 0000000..9477936
--- /dev/null
+++ b/config.ld
@@ -0,0 +1,8 @@
+file = {"docs"}
+title = "x_bows API documentation"
+description = "Minetest mod"
+format = "markdown"
+dir = "docs/build"
+readme = "README.md"
+project = "x_bows"
+ext = "html"
diff --git a/docs/x_bows_api.lua b/docs/x_bows_api.lua
new file mode 100644
index 0000000..f0a656a
--- /dev/null
+++ b/docs/x_bows_api.lua
@@ -0,0 +1,126 @@
+----
+-- Base XBows class
+-- @author SaKeL
+-- @license LGPL-2.1-or-later
+-- @classmod XBows
+XBows = {

  • --- enable_pvp setting, default false
  • pvp = false,
  • --- creative_mode setting, default false
  • creative = false,
  • --- mesecons check if MOD enabled or exists
  • mesecons = false,
  • --- playerphysics check if MOD enabled or exists
  • playerphysics = false,
  • --- player_monoids check if MOD enabled or exists
  • player_monoids = false,
  • --- table with key/value pairs, key is the item name (e.g. x_bows:bow_wood), value is the definition passed to XBows register method
  • registered_bows = {},
  • --- table with key/value pairs, key is the item name (e.g. x_bows:arrow_wood), value is the definition passed to XBows register method
  • registered_arrows = {},
  • --- table with key/value pairs, key is the item name (e.g. x_bows:quiver), value is the definition passed to XBows register method
  • registered_quivers = {},
  • --- registered particle spawners for internal use
  • registered_particle_spawners = {},
  • --- sneaking players when bow is charged
  • player_bow_sneak = {},
  • --- Settings from minetest
  • settings = {
  •    --- `x_bows_attach_arrows_to_entities` setting, default: `false`
    
  •    x_bows_attach_arrows_to_entities = false
    
  • },
  • --- table of after jobs
  • charge_sound_after_job = {}
    +}

+---Check if creative is enabled or if player has creative priv
+-- @param self XBows
+-- @param name string
+-- @return boolean
+function XBows.is_creative(self, name) end
+
+---Updates allowed_ammunition definition on already registered item, so MODs can add new ammunitions to this list.
+-- @param self XBows
+-- @param name string
+-- @param allowed_ammunition string[]
+-- @return nil
+function XBows.update_bow_allowed_ammunition(self, name, allowed_ammunition) end
+
+---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
+-- @param self XBows
+-- @param player ObjectRef Player Ref
+-- @param includeWielded? boolean Will include reset for wielded bow also. default: false
+-- @return nil
+function XBows.reset_charged_bow(self, player, includeWielded) end
+
+---Register bows
+-- @param self XBows
+-- @param name string
+-- @param def ItemDef | BowItemDefCustom
+-- @param override? boolean MOD everride
+-- @return boolean|nil
+function XBows.register_bow(self, name, def, override) end
+
+---Register arrows
+-- @param self XBows
+-- @param name string
+-- @param def ItemDef | ArrowItemDefCustom
+-- @return boolean|nil
+function XBows.register_arrow(self, name, def) end
+
+---Register quivers
+-- @param self XBows
+-- @param name string
+-- @param def ItemDef | QuiverItemDefCustom
+-- @return boolean|nil
+function XBows.register_quiver(self, name, def) end
+
+---Load bow
+-- @param self XBows
+-- @param itemstack ItemStack
+-- @param user ObjectRef
+-- @param pointed_thing PointedThingDef
+-- @return ItemStack
+function XBows.load(self, itemstack, user, pointed_thing) end
+
+---Shoot bow
+-- @param self XBows
+-- @param itemstack ItemStack
+-- @param user ObjectRef
+-- @param pointed_thing? PointedThingDef
+-- @return ItemStack
+function XBows.shoot(self, itemstack, user, pointed_thing) end
+
+---Add new particle to XBow registration
+-- @param self XBows
+-- @param name string
+-- @param def ParticlespawnerDef|ParticlespawnerDefCustom
+-- @return nil
+function XBows.register_particle_effect(self, name, def) end
+
+---Get particle effect from registered spawners table
+-- @param self XBows
+-- @param name string
+-- @param pos Vector
+-- @return number|boolean
+function XBows.get_particle_effect_for_arrow(self, name, pos) end
+
+---Check if ammunition is allowed to charge this weapon
+-- @param self XBows
+-- @param weapon_name string
+-- @param ammo_name string
+-- @return boolean
+function XBows.is_allowed_ammunition(self, weapon_name, ammo_name) end
+
+---Register new projectile entity
+-- @param self XBows
+-- @param name string
+-- @param def XBowsEntityDef
+function XBows.register_entity(self, name, def) end
+
+---Open quiver
+-- @param self XBows
+-- @param itemstack ItemStack
+-- @param user ObjectRef
+-- @return ItemStack
+function XBows.open_quiver(self, itemstack, user) end
diff --git a/docs/x_bows_quiver_api.lua b/docs/x_bows_quiver_api.lua
new file mode 100644
index 0000000..2daeb4d
--- /dev/null
+++ b/docs/x_bows_quiver_api.lua
@@ -0,0 +1,88 @@
+----
+-- XBowsQuiver class extended from XBows
+-- @author SaKeL
+-- @license LGPL-2.1-or-later
+-- @classmod XBowsQuiver
+XBowsQuiver = {

  • --- IDs of added HUDs
  • hud_item_ids = {},
  • --- after job tables
  • after_job = {}
    +}

+---Close one or all open quivers in players inventory
+-- @param self XBowsQuiver
+-- @param player ObjectRef
+-- @param quiver_id? string If nil then all open quivers will be closed
+-- @return nil
+function XBowsQuiver.close_quiver(self, player, quiver_id) end
+
+---Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta.
+-- @param self XBowsQuiver
+-- @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 XBowsQuiver.get_replacement_item(self, from_stack, to_item_name) end
+
+---Gets arrow from quiver
+-- @param self XBowsQuiver
+-- @param player ObjectRef
+-- @return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number}
+function XBowsQuiver.get_itemstack_arrow_from_quiver(self, player) end
+
+---Remove all added HUDs
+-- @param self XBowsQuiver
+-- @param player ObjectRef
+-- @return nil
+function XBowsQuiver.remove_hud(self, player) end
+
+---Update or create quiver HUD
+-- @param self XBowsQuiver
+-- @param player ObjectRef
+-- @param inv_list ItemStack[]
+-- @param idx? number
+-- @return nil
+-- @todo implement hud_change?
+function XBowsQuiver.udate_or_create_hud(self, player, inv_list, idx) end
+
+---Get existing detached inventory or create new one
+-- @param self XBowsQuiver
+-- @param quiver_id string
+-- @param player_name string
+-- @param quiver_items? string
+-- @return InvRef
+function XBowsQuiver.get_or_create_detached_inv(self, quiver_id, player_name, quiver_items) end
+
+---Create formspec
+-- @param self XBowsQuiver
+-- @param name string name of the form
+-- @return string
+function XBowsQuiver.get_formspec(self, name) end
+
+---Convert inventory of itemstacks to serialized string
+-- @param self XBowsQuiver
+-- @param inv InvRef
+-- @return {['inv_string']: string, ['content_description']: string}
+function XBowsQuiver.get_string_from_inv(self, inv) end
+
+---Set items from serialized string to inventory
+-- @param self XBowsQuiver
+-- @param inv InvRef inventory to add items to
+-- @param str string previously stringified inventory of itemstacks
+-- @return nil
+function XBowsQuiver.set_string_to_inv(self, inv, str) end
+
+---Save quiver inventory to itemstack meta
+-- @param self XBowsQuiver
+-- @param inv InvRef
+-- @param player ObjectRef
+-- @param quiver_is_closed? boolean
+-- @return nil
+function XBowsQuiver.save(self, inv, player, quiver_is_closed) end
+
+---Check if we are allowing actions in the correct quiver inventory
+-- @param self XBowsQuiver
+-- @param inv InvRef
+-- @param player ObjectRef
+-- @return boolean
+function XBowsQuiver.quiver_can_allow(self, inv, player) end
diff --git a/i18n.py b/i18n.py
new file mode 100755
index 0000000..da1c825
--- /dev/null
+++ b/i18n.py
@@ -0,0 +1,476 @@
+#!/usr/bin/env python3
+# -- coding: utf-8 --
+#
+# Script to generate the template file and update the translation files.
+# Copy the script into the mod or modpack root folder and run it there.
+#
+# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
+# LGPLv2.1+
+#
+# See https://github.com/minetest-tools/update_translations for
+# potential future updates to this script.
+
+from future import print_function
+import os, fnmatch, re, shutil, errno
+from sys import argv as _argv
+from sys import stderr as _stderr
+
+# Running params
+params = {"recursive": False,

  • "help": False,
  • "mods": False,
  • "verbose": False,
  • "folders": [],
  • "no-old-file": False,
  • "break-long-lines": False,
  • "sort": False,
  • "print-source": False,
  • "truncate-unused": False,
    +}
    +# Available CLI options
    +options = {"recursive": ['--recursive', '-r'],
  • "help": ['--help', '-h'],
  • "mods": ['--installed-mods', '-m'],
  • "verbose": ['--verbose', '-v'],
  • "no-old-file": ['--no-old-file', '-O'],
  • "break-long-lines": ['--break-long-lines', '-b'],
  • "sort": ['--sort', '-s'],
  • "print-source": ['--print-source', '-p'],
  • "truncate-unused": ['--truncate-unused', '-t'],
    +}

+# Strings longer than this will have extra space added between
+# them in the translation files to make it easier to distinguish their
+# beginnings and endings at a glance
+doublespace_threshold = 80
+
+def set_params_folders(tab: list):

  • '''Initialize params["folders"] from CLI arguments.'''
  • Discarding argument 0 (tool name)

  • for param in tab[1:]:
  •    stop_param = False
    
  •    for option in options:
    
  •        if param in options[option]:
    
  •            stop_param = True
    
  •            break
    
  •    if not stop_param:
    
  •        params["folders"].append(os.path.abspath(param))
    

+def set_params(tab: list):

  • '''Initialize params from CLI arguments.'''
  • for option in options:
  •    for option_name in options[option]:
    
  •        if option_name in tab:
    
  •            params[option] = True
    
  •            break
    

+def print_help(name):

  • '''Prints some help message.'''
  • print(f'''SYNOPSIS
  • {name} [OPTIONS] [PATHS...]
    +DESCRIPTION
  • {', '.join(options["help"])}
  •    prints this help message
    
  • {', '.join(options["recursive"])}
  •    run on all subfolders of paths given
    
  • {', '.join(options["mods"])}
  •    run on locally installed modules
    
  • {', '.join(options["no-old-file"])}
  •    do not create *.old files
    
  • {', '.join(options["sort"])}
  •    sort output strings alphabetically
    
  • {', '.join(options["break-long-lines"])}
  •    add extra line breaks before and after long strings
    
  • {', '.join(options["print-source"])}
  • add comments denoting the source file
  • {', '.join(options["verbose"])}
  •    add output information
    
  • {', '.join(options["truncate-unused"])}
  •    delete unused strings from files
    

+''')
+
+
+def main():

  • '''Main function'''
  • set_params(_argv)
  • set_params_folders(_argv)
  • if params["help"]:
  •    print_help(_argv[0])
    
  • elif params["recursive"] and params["mods"]:
  •    print("Option --installed-mods is incompatible with --recursive")
    
  • else:
  •    # Add recursivity message
    
  •    print("Running ", end='')
    
  •    if params["recursive"]:
    
  •        print("recursively ", end='')
    
  •    # Running
    
  •    if params["mods"]:
    
  •        print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}")
    
  •        run_all_subfolders(os.path.expanduser("~/.minetest/mods"))
    
  •    elif len(params["folders"]) >= 2:
    
  •        print("on folder list:", params["folders"])
    
  •        for f in params["folders"]:
    
  •            if params["recursive"]:
    
  •                run_all_subfolders(f)
    
  •            else:
    
  •                update_folder(f)
    
  •    elif len(params["folders"]) == 1:
    
  •        print("on folder", params["folders"][0])
    
  •        if params["recursive"]:
    
  •            run_all_subfolders(params["folders"][0])
    
  •        else:
    
  •            update_folder(params["folders"][0])
    
  •    else:
    
  •        print("on folder", os.path.abspath("./"))
    
  •        if params["recursive"]:
    
  •            run_all_subfolders(os.path.abspath("./"))
    
  •        else:
    
  •            update_folder(os.path.abspath("./"))
    

+#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
+#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
+pattern_lua_s = re.compile(r'[.=^\t,{\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,]', re.DOTALL)
+pattern_lua_fs = re.compile(r'[.=^\t,{\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,]', re.DOTALL)
+pattern_lua_bracketed_s = re.compile(r'[.=^\t,{\s]N?S\(\s*\[\[(.*?)\]\][\s,]', re.DOTALL)
+pattern_lua_bracketed_fs = re.compile(r'[.=^\t,{\s]N?FS\(\s*\[\[(.*?)\]\][\s,]', re.DOTALL)
+
+# Handles "concatenation" .. " of strings"
+pattern_concat = re.compile(r'["'][\s]..[\s]["']', re.DOTALL)
+
+pattern_tr = re.compile(r'(.?[^@])=(.)')
+pattern_name = re.compile(r'^name[ ]=[ ]([^ \n])')
+pattern_tr_filename = re.compile(r'.tr$')
+pattern_po_language_code = re.compile(r'(.
).po$')
+
+#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure
+def get_modname(folder):

  • try:
  •    with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
    
  •        for line in mod_conf:
    
  •            match = pattern_name.match(line)
    
  •            if match:
    
  •                return match.group(1)
    
  • except FileNotFoundError:
  •    if not os.path.isfile(os.path.join(folder, "modpack.txt")):
    
  •        folder_name = os.path.basename(folder)
    
  •        # Special case when run in Minetest's builtin directory
    
  •        if folder_name == "builtin":
    
  •            return "__builtin"
    
  •        else:
    
  •            return folder_name
    
  •    else:
    
  •        return None
    
  • return None

+#If there are already .tr files in /locale, returns a list of their names
+def get_existing_tr_files(folder):

  • out = []
  • for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
  •    for name in files:
    
  •        if pattern_tr_filename.search(name):
    
  •            out.append(name)
    
  • return out

+# A series of search and replaces that massage a .po file's contents into
+# a .tr file's equivalent
+def process_po_file(text):

  • The first three items are for unused matches

  • text = re.sub(r'#~ msgid "', "", text)
  • text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
  • text = re.sub(r'"\n#~ msgstr "', "=", text)
  • comment lines

  • text = re.sub(r'#.*\n', "", text)
  • converting msg pairs into "=" pairs

  • text = re.sub(r'msgid "', "", text)
  • text = re.sub(r'"\nmsgstr ""\n"', "=", text)
  • text = re.sub(r'"\nmsgstr "', "=", text)
  • various line breaks and escape codes

  • text = re.sub(r'"\n"', "", text)
  • text = re.sub(r'"\n', "\n", text)
  • text = re.sub(r'\"', '"', text)
  • text = re.sub(r'\n', '@n', text)
  • remove header text

  • text = re.sub(r'=Project-Id-Version:.*\n', "", text)
  • remove double-spaced lines

  • text = re.sub(r'\n\n', '\n', text)
  • return text

+# Go through existing .po files and, if a .tr file for that language
+# doesn't exist, convert it and create it.
+# The .tr file that results will subsequently be reprocessed so
+# any "no longer used" strings will be preserved.
+# Note that "fuzzy" tags will be lost in this process.
+def process_po_files(folder, modname):

  • for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
  •    for name in files:
    
  •        code_match = pattern_po_language_code.match(name)
    
  •        if code_match == None:
    
  •            continue
    
  •        language_code = code_match.group(1)
    
  •        tr_name = f'{modname}.{language_code}.tr'
    
  •        tr_file = os.path.join(root, tr_name)
    
  •        if os.path.exists(tr_file):
    
  •            if params["verbose"]:
    
  •                print(f"{tr_name} already exists, ignoring {name}")
    
  •            continue
    
  •        fname = os.path.join(root, name)
    
  •        with open(fname, "r", encoding='utf-8') as po_file:
    
  •            if params["verbose"]:
    
  •                print(f"Importing translations from {name}")
    
  •            text = process_po_file(po_file.read())
    
  •            with open(tr_file, "wt", encoding='utf-8') as tr_out:
    
  •                tr_out.write(text)
    

+# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
+# Creates a directory if it doesn't exist, silently does
+# nothing if it already exists
+def mkdir_p(path):

  • try:
  •    os.makedirs(path)
    
  • except OSError as exc: # Python >2.5
  •    if exc.errno == errno.EEXIST and os.path.isdir(path):
    
  •        pass
    
  •    else: raise
    

+# Converts the template dictionary to a text to be written as a file
+# dKeyStrings is a dictionary of localized string to source file sets
+# dOld is a dictionary of existing translations and comments from
+# the previous version of this text
+def strings_to_text(dkeyStrings, dOld, mod_name, header_comments):

  • lOut = [f"# textdomain: {mod_name}"]
  • if header_comments is not None:
  •    lOut.append(header_comments)
    
  • dGroupedBySource = {}
  • for key in dkeyStrings:
  •    sourceList = list(dkeyStrings[key])
    
  •    if params["sort"]:
    
  •        sourceList.sort()
    
  •    sourceString = "\n".join(sourceList)
    
  •    listForSource = dGroupedBySource.get(sourceString, [])
    
  •    listForSource.append(key)
    
  •    dGroupedBySource[sourceString] = listForSource
    
  • lSourceKeys = list(dGroupedBySource.keys())
  • lSourceKeys.sort()
  • for source in lSourceKeys:
  •    localizedStrings = dGroupedBySource[source]
    
  •    if params["sort"]:
    
  •        localizedStrings.sort()
    
  •    if params["print-source"]:
    
  •        if lOut[-1] != "":
    
  •            lOut.append("")
    
  •        lOut.append(source)
    
  •    for localizedString in localizedStrings:
    
  •        val = dOld.get(localizedString, {})
    
  •        translation = val.get("translation", "")
    
  •        comment = val.get("comment")
    
  •        if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "":
    
  •            lOut.append("")
    
  •        if comment != None and comment != "" and not comment.startswith("# textdomain:"):
    
  •            lOut.append(comment)
    
  •        lOut.append(f"{localizedString}={translation}")
    
  •        if params["break-long-lines"] and len(localizedString) > doublespace_threshold:
    
  •            lOut.append("")
    
  • unusedExist = False
  • if not params["truncate-unused"]:
  •    for key in dOld:
    
  •        if key not in dkeyStrings:
    
  •            val = dOld[key]
    
  •            translation = val.get("translation")
    
  •            comment = val.get("comment")
    
  •            # only keep an unused translation if there was translated
    
  •            # text or a comment associated with it
    
  •            if translation != None and (translation != "" or comment):
    
  •                if not unusedExist:
    
  •                    unusedExist = True
    
  •                    lOut.append("\n\n##### not used anymore #####\n")
    
  •                if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "":
    
  •                    lOut.append("")
    
  •                if comment != None:
    
  •                    lOut.append(comment)
    
  •                lOut.append(f"{key}={translation}")
    
  •                if params["break-long-lines"] and len(key) > doublespace_threshold:
    
  •                    lOut.append("")
    
  • return "\n".join(lOut) + '\n'

+# Writes a template.txt file
+# dkeyStrings is the dictionary returned by generate_template
+def write_template(templ_file, dkeyStrings, mod_name):

  • read existing template file to preserve comments

  • existing_template = import_tr_file(templ_file)
  • text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2])
  • mkdir_p(os.path.dirname(templ_file))
  • with open(templ_file, "wt", encoding='utf-8') as template_file:
  •    template_file.write(text)
    

+# Gets all translatable strings from a lua file
+def read_lua_file_strings(lua_file):

  • lOut = []
  • with open(lua_file, encoding='utf-8') as text_file:
  •    text = text_file.read()
    
  •    #TODO remove comments here
    
  •    text = re.sub(pattern_concat, "", text)
    
  •    strings = []
    
  •    for s in pattern_lua_s.findall(text):
    
  •        strings.append(s[1])
    
  •    for s in pattern_lua_bracketed_s.findall(text):
    
  •        strings.append(s)
    
  •    for s in pattern_lua_fs.findall(text):
    
  •        strings.append(s[1])
    
  •    for s in pattern_lua_bracketed_fs.findall(text):
    
  •        strings.append(s)
    
  •    for s in strings:
    
  •        s = re.sub(r'"\.\.\s+"', "", s)
    
  •        s = re.sub("@[^@=0-9]", "@@", s)
    
  •        s = s.replace('\\"', '"')
    
  •        s = s.replace("\\'", "'")
    
  •        s = s.replace("\n", "@n")
    
  •        s = s.replace("\\n", "@n")
    
  •        s = s.replace("=", "@=")
    
  •        lOut.append(s)
    
  • return lOut

+# Gets strings from an existing translation file
+# returns both a dictionary of translations
+# and the full original source text so that the new text
+# can be compared to it for changes.
+# Returns also header comments in the third return value.
+def import_tr_file(tr_file):

  • dOut = {}
  • text = None
  • header_comment = None
  • if os.path.exists(tr_file):
  •    with open(tr_file, "r", encoding='utf-8') as existing_file :
    
  •        # save the full text to allow for comparison
    
  •        # of the old version with the new output
    
  •        text = existing_file.read()
    
  •        existing_file.seek(0)
    
  •        # a running record of the current comment block
    
  •        # we're inside, to allow preceeding multi-line comments
    
  •        # to be retained for a translation line
    
  •        latest_comment_block = None
    
  •        for line in existing_file.readlines():
    
  •            line = line.rstrip('\n')
    
  •            if line.startswith("###"):
    
  •                if header_comment is None and not latest_comment_block is None:
    
  •                    # Save header comments
    
  •                    header_comment = latest_comment_block
    
  •                    # Strip textdomain line
    
  •                    tmp_h_c = ""
    
  •                    for l in header_comment.split('\n'):
    
  •                        if not l.startswith("# textdomain:"):
    
  •                            tmp_h_c += l + '\n'
    
  •                    header_comment = tmp_h_c
    
  •                # Reset comment block if we hit a header
    
  •                latest_comment_block = None
    
  •                continue
    
  •            elif line.startswith("#"):
    
  •                # Save the comment we're inside
    
  •                if not latest_comment_block:
    
  •                    latest_comment_block = line
    
  •                else:
    
  •                    latest_comment_block = latest_comment_block + "\n" + line
    
  •                continue
    
  •            match = pattern_tr.match(line)
    
  •            if match:
    
  •                # this line is a translated line
    
  •                outval = {}
    
  •                outval["translation"] = match.group(2)
    
  •                if latest_comment_block:
    
  •                    # if there was a comment, record that.
    
  •                    outval["comment"] = latest_comment_block
    
  •                latest_comment_block = None
    
  •                dOut[match.group(1)] = outval
    
  • return (dOut, text, header_comment)

+# Walks all lua files in the mod folder, collects translatable strings,
+# and writes it to a template.txt file
+# Returns a dictionary of localized strings to source file sets
+# that can be used with the strings_to_text function.
+def generate_template(folder, mod_name):

  • dOut = {}
  • for root, dirs, files in os.walk(folder):
  •    for name in files:
    
  •        if fnmatch.fnmatch(name, "*.lua"):
    
  •            fname = os.path.join(root, name)
    
  •            found = read_lua_file_strings(fname)
    
  •            if params["verbose"]:
    
  •                print(f"{fname}: {str(len(found))} translatable strings")
    
  •            for s in found:
    
  •                sources = dOut.get(s, set())
    
  •                sources.add(f"### {os.path.basename(fname)} ###")
    
  •                dOut[s] = sources
    
  • if len(dOut) == 0:
  •    return None
    
  • templ_file = os.path.join(folder, "locale/template.txt")
  • write_template(templ_file, dOut, mod_name)
  • return dOut

+# Updates an existing .tr file, copying the old one to a ".old" file
+# if any changes have happened
+# dNew is the data used to generate the template, it has all the
+# currently-existing localized strings
+def update_tr_file(dNew, mod_name, tr_file):

  • if params["verbose"]:
  •    print(f"updating {tr_file}")
    
  • tr_import = import_tr_file(tr_file)
  • dOld = tr_import[0]
  • textOld = tr_import[1]
  • textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2])
  • if textOld and textOld != textNew:
  •    print(f"{tr_file} has changed.")
    
  •    if not params["no-old-file"]:
    
  •        shutil.copyfile(tr_file, f"{tr_file}.old")
    
  • with open(tr_file, "w", encoding='utf-8') as new_tr_file:
  •    new_tr_file.write(textNew)
    

+# Updates translation files for the mod in the given folder
+def update_mod(folder):

  • modname = get_modname(folder)
  • if modname is not None:
  •    process_po_files(folder, modname)
    
  •    print(f"Updating translations for {modname}")
    
  •    data = generate_template(folder, modname)
    
  •    if data == None:
    
  •        print(f"No translatable strings found in {modname}")
    
  •    else:
    
  •        for tr_file in get_existing_tr_files(folder):
    
  •            update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
    
  • else:
  •    print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr)
    
  •    exit(1)
    

+# Determines if the folder being pointed to is a mod or a mod pack
+# and then runs update_mod accordingly
+def update_folder(folder):

  • is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
  • if is_modpack:
  •    subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]
    
  •    for subfolder in subfolders:
    
  •        update_mod(subfolder)
    
  • else:
  •    update_mod(folder)
    
  • print("Done.")

+def run_all_subfolders(folder):

  • for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]:
  •    update_folder(modfolder)
    

+main()
diff --git a/init.lua b/init.lua
index 991574c..e68f8d1 100644
--- a/init.lua
+++ b/init.lua
@@ -1,360 +1,363 @@
+--[[

  • X Bows. Adds bow and arrows with API.
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+minetest = minetest.global_exists('minetest') and minetest --@as Minetest
+ItemStack = minetest.global_exists('ItemStack') and ItemStack --@as ItemStack
+vector = minetest.global_exists('vector') and vector --@as Vector
+default = minetest.global_exists('default') and default --@as MtgDefault
+sfinv = minetest.global_exists('sfinv') and sfinv --@as Sfinv
+unified_inventory = minetest.global_exists('unified_inventory') and unified_inventory --@as UnifiedInventory
+player_api = minetest.global_exists('player_api') and player_api --@as MtgPlayerApi
+
+math.randomseed(tonumber(tostring(os.time()):reverse():sub(1, 9))--@as number )
+
+local path = minetest.get_modpath('x_bows')
local mod_start_time = minetest.get_us_time()
local bow_charged_timer = 0

-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'),
  • 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)
    
  • }
    -}

-function x_bows.is_creative(name)

  • return x_bows.creative or minetest.check_player_privs(name, {creative = true})
    -end
    +dofile(path .. '/api.lua')
    +dofile(path .. '/particle_effects.lua')
    +dofile(path .. '/nodes.lua')
    +dofile(path .. '/arrow.lua')
    +dofile(path .. '/items.lua')

-function x_bows.register_bow(name, def)

  • 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
  • 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
    
  • })
  • -- 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},
    
  • })
  • -- recipes
  • if def.recipe then
  •   minetest.register_craft({
    
  •   	output = def.name,
    
  •   	recipe = def.recipe
    
  •   })
    
  • end
    +if XBows.i3 then
  • XBowsQuiver:i3_register_page()
    +elseif XBows.unified_inventory then
  • XBowsQuiver:ui_register_page()
    +else
  • XBowsQuiver:sfinv_register_page()
    end

-function x_bows.register_arrow(name, def)

  • if name == nil or name == '' then
  •   return false
    
  • end
  • def.name = 'x_bows:' .. name
  • def.description = def.description or name
  • 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}
    
  • })
  • -- recipes
  • if def.craft then
  •   minetest.register_craft({
    
  •   	output = def.name ..' ' .. (def.craft_count or 4),
    
  •   	recipe = def.craft
    
  •   })
    
  • end
    -end
    +minetest.register_on_joinplayer(function(player)
  • local inv_quiver = player:get_inventory() --@as InvRef
  • local inv_arrow = player:get_inventory() --@as InvRef
  • if XBows.settings.x_bows_show_3d_quiver and XBows.player_api then
  •    ---Order matters here
    
  •    if XBows.skinsdb then
    
  •        player_api.set_model(player, 'skinsdb_3d_armor_character_5.b3d')
    
  •    elseif XBows._3d_armor then
    
  •        player_api.set_model(player, 'x_bows_3d_armor_character.b3d')
    
  •    else
    
  •        player_api.set_model(player, 'x_bows_character.b3d')
    
  •    end
    
  • end
  • inv_quiver:set_size('x_bows:quiver_inv', 1 * 1)
  • inv_arrow:set_size('x_bows:arrow_inv', 1 * 1)
  • local quiver_stack = player:get_inventory():get_stack('x_bows:quiver_inv', 1)
  • if quiver_stack and not quiver_stack:is_empty() then
  •    local st_meta = quiver_stack:get_meta()
    
  •    local quiver_id = st_meta:get_string('quiver_id')
    
  •    ---create detached inventory
    
  •    local detached_inv = XBowsQuiver:get_or_create_detached_inv(
    
  •        quiver_id,
    
  •        player:get_player_name(),
    
  •        st_meta:get_string('quiver_items')
    
  •    )
    
  •    ---set model textures
    
  •    if detached_inv:is_empty('main') then
    
  •        XBowsQuiver.quiver_empty_state[player:get_player_name()] = false
    
  •        XBowsQuiver:show_3d_quiver(player, { is_empty = true })
    
  •    else
    
  •        XBowsQuiver.quiver_empty_state[player:get_player_name()] = true
    
  •        XBowsQuiver:show_3d_quiver(player)
    
  •    end
    
  • else
  •    ---set model textures
    
  •    XBowsQuiver:hide_3d_quiver(player)
    
  • end
  • XBows:reset_charged_bow(player, true)
  • XBowsQuiver:close_quiver(player)
    +end)

-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 = {}
  • 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
    
  •   	node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing)
    
  •   	return
    
  •   end
    
  • end
  • for k, 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
  • -- take 1st found arrow in the list
  • local itemstack_arrow = itemstack_arrows[1]
  • if itemstack_arrow and bow_def then
  •   local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities
    
  •   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()
    
  •   	if wielded_item_name == v_bow_name then
    
  •   		local meta = wielded_item:get_meta()
    
  •   		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 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)
    
  •   -- 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_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)
    
  •   minetest.sound_play('x_bows_bow_load', {
    
  •   	to_player = user:get_player_name(),
    
  •   	gain = 0.6
    
  •   })
    
  •   return itemstack
    
  • end
    +if XBows.settings.x_bows_show_3d_quiver and XBows.player_api then
  • local model_name = 'x_bows_character.b3d'
  • if XBows.skinsdb then
  •    ---skinsdb
    
  •    model_name = 'skinsdb_3d_armor_character_5.b3d'
    
  • elseif XBows._3d_armor then
  •    ---3d armor
    
  •    model_name = 'x_bows_3d_armor_character.b3d'
    
  • end
  • player_api.register_model(model_name, {
  •    animation_speed = 30,
    
  •    textures = { 'character.png' },
    
  •    animations = {
    
  •        -- Standard animations.
    
  •        stand = { x = 0, y = 79 },
    
  •        lay = { x = 162, y = 166, eye_height = 0.3, override_local = true,
    
  •        collisionbox = { -0.6, 0.0, -0.6, 0.6, 0.3, 0.6 } },
    
  •        walk = { x = 168, y = 187 },
    
  •        mine = { x = 189, y = 198 },
    
  •        walk_mine = { x = 200, y = 219 },
    
  •        sit = { x = 81, y = 160, eye_height = 0.8, override_local = true,
    
  •        collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.0, 0.3 } }
    
  •    },
    
  •    collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.7, 0.3 },
    
  •    stepheight = 0.6,
    
  •    eye_height = 1.47
    
  • })
    end

-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
  • if not x_bows.registered_arrows[meta_arrow] then
  •   return itemstack
    
  • 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
  • local staticdata = {
  •   arrow = meta_arrow,
    
  •   user_name = user:get_player_name(),
    
  •   is_critical_hit = false,
    
  •   _tool_capabilities = _tool_capabilities,
    
  •   _tflp = tflp,
    
  • }
  • -- 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 sound_name = 'x_bows_bow_shoot'
  • if staticdata.is_critical_hit then
  •   sound_name = 'x_bows_bow_shoot_crit'
    
  • end
  • meta:set_string('arrow', '')
  • itemstack:set_name(bow_name)
  • 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))
  • if not obj then
  •   return itemstack
    
  • end
  • local lua_ent = obj:get_luaentity()
  • local strength_multiplier = tflp
  • if strength_multiplier > _tool_capabilities.full_punch_interval then
  •   strength_multiplier = 1
    
  • end
  • 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
    +---formspec callbacks
    +minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info)
  • ---arrow inventory
  • if action == 'move' and inventory_info.to_list == 'x_bows:arrow_inv' then
  •    local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
    
  •    if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 then
    
  •        return inventory_info.count
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'move' and inventory_info.from_list == 'x_bows:arrow_inv' then
  •    local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
    
  •    if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 then
    
  •        return inventory_info.count
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'put' and inventory_info.listname == 'x_bows:arrow_inv' then
  •    if minetest.get_item_group(inventory_info.stack:get_name(), 'arrow') ~= 0 then
    
  •        return inventory_info.stack:get_count()
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'take' and inventory_info.listname == 'x_bows:arrow_inv' then
  •    if minetest.get_item_group(inventory_info.stack:get_name(), 'arrow') ~= 0 then
    
  •        return inventory_info.stack:get_count()
    
  •    else
    
  •        return 0
    
  •    end
    
  • end
  • ---quiver inventory
  • if action == 'move' and inventory_info.to_list == 'x_bows:quiver_inv' then
  •    local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
    
  •    if minetest.get_item_group(stack:get_name(), 'quiver') ~= 0 then
    
  •        return inventory_info.count
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'move' and inventory_info.from_list == 'x_bows:quiver_inv' then
  •    local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
    
  •    if minetest.get_item_group(stack:get_name(), 'quiver') ~= 0 then
    
  •        return inventory_info.count
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'put' and inventory_info.listname == 'x_bows:quiver_inv' then
  •    if minetest.get_item_group(inventory_info.stack:get_name(), 'quiver') ~= 0 then
    
  •        return inventory_info.stack:get_count()
    
  •    else
    
  •        return 0
    
  •    end
    
  • elseif action == 'take' and inventory_info.listname == 'x_bows:quiver_inv' then
  •    if minetest.get_item_group(inventory_info.stack:get_name(), 'quiver') ~= 0 then
    
  •        return inventory_info.stack:get_count()
    
  •    else
    
  •        return 0
    
  •    end
    
  • end
  • return inventory_info.count or inventory_info.stack:get_count()
    +end)

-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
    -end
    +minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
  • ---arrow
  • if action == 'move' and inventory_info.to_list == 'x_bows:arrow_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  • elseif action == 'move' and inventory_info.from_list == 'x_bows:arrow_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  • elseif action == 'put' and inventory_info.listname == 'x_bows:arrow_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  • elseif action == 'take' and inventory_info.listname == 'x_bows:arrow_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  • end
  • ---quiver
  • if action == 'move' and inventory_info.to_list == 'x_bows:quiver_inv' then
  •    local stack = inventory:get_stack(inventory_info.to_list, inventory_info.to_index)
    
  •    ---init detached inventory if not already
    
  •    local st_meta = stack:get_meta()
    
  •    local quiver_id = st_meta:get_string('quiver_id')
    
  •    if quiver_id == '' then
    
  •        quiver_id = stack:get_name() .. '_' .. XBows.uuid()
    
  •        st_meta:set_string('quiver_id', quiver_id)
    
  •        inventory:set_stack(inventory_info.to_list, inventory_info.to_index, stack)
    
  •    end
    
  •    local detached_inv = XBowsQuiver:get_or_create_detached_inv(
    
  •        quiver_id,
    
  •        player:get_player_name(),
    
  •        st_meta:get_string('quiver_items')
    
  •    )
    
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  •    ---set player visual
    
  •    if detached_inv:is_empty('main') then
    
  •        XBowsQuiver.quiver_empty_state[player:get_player_name()] = false
    
  •        XBowsQuiver:show_3d_quiver(player, { is_empty = true })
    
  •    else
    
  •        XBowsQuiver.quiver_empty_state[player:get_player_name()] = true
    
  •        XBowsQuiver:show_3d_quiver(player)
    
  •    end
    
  • elseif action == 'move' and inventory_info.from_list == 'x_bows:quiver_inv' then
  •    local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
    
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  •    ---set player visual
    
  •    if stack:is_empty() then
    
  •        XBowsQuiver:hide_3d_quiver(player)
    
  •    end
    
  • elseif action == 'put' and inventory_info.listname == 'x_bows:quiver_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  • elseif action == 'take' and inventory_info.listname == 'x_bows:quiver_inv' then
  •    if XBows.i3 then
    
  •        i3.set_fs(player)
    
  •    elseif XBows.unified_inventory then
    
  •        unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page')
    
  •    else
    
  •        sfinv.set_player_inventory_formspec(player)
    
  •    end
    
  •    ---set player visual
    
  •    if inventory:is_empty(inventory_info.listname) then
    
  •        XBowsQuiver:hide_3d_quiver(player)
    
  •    end
    
  • end
    +end)

--- sneak, fov adjustments when bow is charged
-minetest.register_globalstep(function(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 not item then
    
  •   		return
    
  •   	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 minetest.get_modpath('playerphysics') then
    
  •   			playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25)
    
  •   		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 minetest.get_modpath('playerphysics') then
    
  •   			playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged')
    
  •   		end
    
  •   		x_bows.player_bow_sneak[name].sneak = false
    
  •   		player:set_fov(1, true, 0.4)
    
  •   	end
    
  •   end
    
  •   bow_charged_timer = 0
    
  • end
    +minetest.register_on_player_receive_fields(function(player, formname, fields)
  • if player and fields.quit then
  •    XBowsQuiver:close_quiver(player, formname)
    
  • end
    end)

-local path = minetest.get_modpath('x_bows')
+---backwards compatibility
+minetest.register_alias('x_bows:arrow_diamond_tipped_poison', 'x_bows:arrow_diamond')

-dofile(path .. '/nodes.lua')
-dofile(path .. '/arrow.lua')
-dofile(path .. '/items.lua')
+-- sneak, fov adjustments when bow is charged
+minetest.register_globalstep(function(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 player_name = player:get_player_name()
    
  •        local wielded_stack = player:get_wielded_item()
    
  •        local wielded_stack_name = wielded_stack:get_name()
    
  •        if not wielded_stack_name then
    
  •            return
    
  •        end
    
  •        if not XBows.player_bow_sneak[player_name] then
    
  •            XBows.player_bow_sneak[player_name] = {}
    
  •        end
    
  •        if minetest.get_item_group(wielded_stack_name, 'bow_charged') ~= 0
    
  •            and not XBows.player_bow_sneak[player_name].sneak
    
  •        then
    
  •            --charged weapon
    
  •            if XBows.playerphysics then
    
  •                playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_charged_speed', 0.25)
    
  •            elseif XBows.player_monoids then
    
  •                player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_charged_speed')
    
  •            end
    
  •            XBows.player_bow_sneak[player_name].sneak = true
    
  •            player:set_fov(0.9, true, 0.4)
    
  •        elseif minetest.get_item_group(wielded_stack_name, 'bow_charged') == 0
    
  •            and XBows.player_bow_sneak[player_name].sneak
    
  •        then
    
  •            if XBows.playerphysics then
    
  •                playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_charged_speed')
    
  •            elseif XBows.player_monoids then
    
  •                player_monoids.speed:del_change(player, 'x_bows:bow_charged_speed')
    
  •            end
    
  •            XBows.player_bow_sneak[player_name].sneak = false
    
  •            player:set_fov(0, true, 0.4)
    
  •        end
    
  •        XBows:reset_charged_bow(player)
    
  •    end
    
  •    bow_charged_timer = 0
    
  • end
    +end)

local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000

-print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]')
+print('[Mod] x_bows loaded.. [' .. mod_end_time .. 's]')
diff --git a/items.lua b/items.lua
index 24c2050..13685de 100644
--- a/items.lua
+++ b/items.lua
@@ -1,130 +1,169 @@
-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'},
    
  • }
    -})
    +--[[

-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}
    
  • }
    -})
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+local S = minetest.get_translator(minetest.get_current_modname())

-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}
    
  • }
    +XBows:register_bow('bow_wood', {
  • description = S('Wooden Bow'),
  • short_description = S('Wooden Bow'),
  • custom = {
  •    uses = 385,
    
  •    crit_chance = 10,
    
  •    recipe = {
    
  •        { '', 'default:stick', 'farming:string' },
    
  •        { 'default:stick', '', 'farming:string' },
    
  •        { '', 'default:stick', 'farming:string' }
    
  •    },
    
  •    fuel_burntime = 3,
    
  •    allowed_ammunition = {
    
  •        'x_bows:arrow_wood',
    
  •        'x_bows:arrow_stone',
    
  •        'x_bows:arrow_bronze',
    
  •        'x_bows:arrow_steel',
    
  •        'x_bows:arrow_mese',
    
  •        'x_bows:arrow_diamond'
    
  •    }
    
  • }
    })

-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}
    
  • }
    +XBows:register_arrow('arrow_wood', {
  • description = S('Arrow Wood'),
  • short_description = S('Arrow Wood'),
  • inventory_image = 'x_bows_arrow_wood.png',
  • custom = {
  •    recipe = {
    
  •        { 'default:flint' },
    
  •        { 'group:stick' },
    
  •        { 'group:wool' }
    
  •    },
    
  •    tool_capabilities = {
    
  •        full_punch_interval = 1,
    
  •        max_drop_level = 0,
    
  •        damage_groups = { fleshy = 2 }
    
  •    },
    
  •    fuel_burntime = 1
    
  • }
    })

-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}
    
  • }
    +XBows:register_arrow('arrow_stone', {
  • description = S('Arrow Stone'),
  • short_description = S('Arrow Stone'),
  • inventory_image = 'x_bows_arrow_stone.png',
  • custom = {
  •    recipe = {
    
  •        { '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_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}
    
  • }
    +XBows:register_arrow('arrow_bronze', {
  • description = S('Arrow Bronze'),
  • short_description = S('Arrow Bronze'),
  • inventory_image = 'x_bows_arrow_bronze.png',
  • custom = {
  •    recipe = {
    
  •        { '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_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}
    
  • }
    +XBows:register_arrow('arrow_steel', {
  • description = S('Arrow Steel'),
  • short_description = S('Arrow Steel'),
  • inventory_image = 'x_bows_arrow_steel.png',
  • custom = {
  •    recipe = {
    
  •        { '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_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
    +XBows:register_arrow('arrow_mese', {
  • description = S('Arrow Mese'),
  • short_description = S('Arrow Mese'),
  • inventory_image = 'x_bows_arrow_mese.png',
  • custom = {
  •    recipe = {
    
  •        { 'default:flint' },
    
  •        { 'default:mese_crystal' },
    
  •        { 'group:wool' }
    
  •    },
    
  •    tool_capabilities = {
    
  •        full_punch_interval = 0.7,
    
  •        max_drop_level = 1,
    
  •        damage_groups = { fleshy = 7 }
    
  •    }
    
  • }
    })

-minetest.register_craft({

  • type = 'fuel',
  • recipe = 'x_bows:bow_wood',
  • burntime = 3,
    +XBows:register_arrow('arrow_diamond', {
  • description = S('Arrow Diamond'),
  • short_description = S('Arrow Diamond'),
  • inventory_image = 'x_bows_arrow_diamond.png',
  • custom = {
  •    recipe = {
    
  •        { 'default:flint' },
    
  •        { 'default:diamond' },
    
  •        { 'group:wool' }
    
  •    },
    
  •    tool_capabilities = {
    
  •        full_punch_interval = 0.7,
    
  •        max_drop_level = 1,
    
  •        damage_groups = { fleshy = 8 }
    
  •    }
    
  • }
    })

-minetest.register_craft({

  • type = 'fuel',
  • recipe = 'x_bows:arrow_wood',
  • burntime = 1,
    +XBows:register_quiver('quiver', {
  • description = S('Quiver') .. '\n\n' .. S('Empty') .. '\n',
  • short_description = S('Quiver'),
  • custom = {
  •    description = S('Quiver') .. '\n\n' .. S('Empty') .. '\n',
    
  •    short_description = S('Quiver'),
    
  •    recipe = {
    
  •        { 'group:arrow', 'group:arrow', 'group:arrow' },
    
  •        { 'group:arrow', 'wool:brown', 'group:arrow' },
    
  •        { 'group:arrow', 'group:arrow', 'group:arrow' }
    
  •    },
    
  •    recipe_count = 1,
    
  •    faster_arrows = 5,
    
  •    add_damage = 2,
    
  •    fuel_burntime = 3
    
  • }
    })
    diff --git a/locale/template.txt b/locale/template.txt
    new file mode 100644
    index 0000000..109876a
    --- /dev/null
    +++ b/locale/template.txt
    @@ -0,0 +1,21 @@
    +# textdomain: x_bows
    +Critical Arrow Chance=
    +Strength=
    +Allowed ammunition=
    +none=
    +Damage=
    +Charge Time=
    +Faster Arrows=
    +Arrow Damage=
    +No Ammo=
    +Arrows=
    +Quiver=
    +Empty=
    +Wooden Bow=
    +Arrow Wood=
    +Arrow Stone=
    +Arrow Bronze=
    +Arrow Steel=
    +Arrow Mese=
    +Arrow Diamond=
    +Target=
    diff --git a/locale/x_bows.sk.tr b/locale/x_bows.sk.tr
    new file mode 100644
    index 0000000..be7c233
    --- /dev/null
    +++ b/locale/x_bows.sk.tr
    @@ -0,0 +1,21 @@
    +# textdomain: x_bows
    +Critical Arrow Chance=Šanca kritického šípu
    +Strength=Sila
    +Allowed ammunition=Povolené strelivo
    +none=Žiaden
    +Damage=Poškodenie
    +Charge Time=Doba nabíjania
    +Faster Arrows=Rýchlejšie šípy
    +Arrow Damage=Poškodenie šípom
    +No Ammo=Žiadne strelivo
    +Arrows=Šípy
    +Quiver=Púzdro
    +Empty=Prázdne
    +Wooden Bow=Drevený luk
    +Arrow Wood=Drevený šíp
    +Arrow Stone=Kamenný šíp
    +Arrow Bronze=Bronzový šíp
    +Arrow Steel=Oceľový šíp
    +Arrow Mese=Mese šíp
    +Arrow Diamond=Diamantový šíp
    +Target=Terč
    diff --git a/mod.conf b/mod.conf
    index f5c7e0d..4befe04 100644
    --- a/mod.conf
    +++ b/mod.conf
    @@ -1,5 +1,6 @@
    name = x_bows
    -description = Adds bow and arrows to Minetest.
    +description = Adds bow and arrows with API.
    depends =
    -optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics
    -min_minetest_version = 5.0
    \ No newline at end of file
    +optional_depends = default, farming, 3d_armor, mesecons, playerphysics, player_monoids, wool, i3, unified_inventory, simple_skins, u_skins, wardrobe, sfinv, skinsdb, player_api
    +supported_games = minetest_game
    +min_minetest_version = 5.4
    diff --git a/models/skinsdb_3d_armor_character_5.b3d b/models/skinsdb_3d_armor_character_5.b3d
    new file mode 100644
    index 0000000..52b78f5
    Binary files /dev/null and b/models/skinsdb_3d_armor_character_5.b3d differ
    diff --git a/models/x_bows_3d_armor_character.b3d b/models/x_bows_3d_armor_character.b3d
    new file mode 100644
    index 0000000..ee6d07d
    Binary files /dev/null and b/models/x_bows_3d_armor_character.b3d differ
    diff --git a/models/x_bows_arrow.b3d b/models/x_bows_arrow.b3d
    new file mode 100644
    index 0000000..7d7e286
    Binary files /dev/null and b/models/x_bows_arrow.b3d differ
    diff --git a/models/x_bows_character.b3d b/models/x_bows_character.b3d
    new file mode 100644
    index 0000000..aea1342
    Binary files /dev/null and b/models/x_bows_character.b3d differ
    diff --git a/nodes.lua b/nodes.lua
    index 9c8200c..29a4859 100644
    --- a/nodes.lua
    +++ b/nodes.lua
    @@ -1,54 +1,54 @@
    -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}
    -})
    +--[[
  • X Bows. Adds bow and arrows with API.
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+local S = minetest.get_translator(minetest.get_current_modname())

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 = S('Target'),
  • short_description = S('Target'),
  • tiles = { 'x_bows_target.png' },
  • is_ground_content = false,
  • groups = { snappy = 3, flammable = 4, fall_damage_add_percent = -30 },
  • sounds = minetest.global_exists('default') and default.node_sound_leaves_defaults() or {},
  • mesecons = { receptor = { state = 'off' } },
  • ---@param pos Vector
  • ---@param elapsed number
  • ---@return boolean
  • on_timer = function(pos, elapsed)
  •    if XBows.mesecons then
    
  •        mesecon.receptor_off(pos)
    
  •    end
    
  •    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/package-lock.json b/package-lock.json
    new file mode 100644
    index 0000000..eb73212
    --- /dev/null
    +++ b/package-lock.json
    @@ -0,0 +1,1388 @@
    +{
  • "name": "x_bows",
  • "version": "1.0.0",
  • "lockfileVersion": 2,
  • "requires": true,
  • "packages": {
  •    "": {
    
  •        "name": "x_bows",
    
  •        "version": "1.0.0",
    
  •        "license": "LGPL-2.1-or-later",
    
  •        "devDependencies": {
    
  •            "jaguar": "^6.0.1",
    
  •            "node-fetch": "^3.2.10",
    
  •            "yargs": "^17.6.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=16.0.0",
    
  •            "npm": ">=8.0.0"
    
  •        }
    
  •    },
    
  •    "node_modules/ansi-regex": {
    
  •        "version": "5.0.1",
    
  •        "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
    
  •        "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        }
    
  •    },
    
  •    "node_modules/ansi-styles": {
    
  •        "version": "4.3.0",
    
  •        "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
    
  •        "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "color-convert": "^2.0.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        },
    
  •        "funding": {
    
  •            "url": "https://github.com/chalk/ansi-styles?sponsor=1"
    
  •        }
    
  •    },
    
  •    "node_modules/balanced-match": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
    
  •        "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/base64-js": {
    
  •        "version": "1.5.1",
    
  •        "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
    
  •        "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
    
  •        "dev": true,
    
  •        "funding": [
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://github.com/sponsors/feross"
    
  •            },
    
  •            {
    
  •                "type": "patreon",
    
  •                "url": "https://www.patreon.com/feross"
    
  •            },
    
  •            {
    
  •                "type": "consulting",
    
  •                "url": "https://feross.org/support"
    
  •            }
    
  •        ]
    
  •    },
    
  •    "node_modules/bl": {
    
  •        "version": "4.1.0",
    
  •        "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
    
  •        "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "buffer": "^5.5.0",
    
  •            "inherits": "^2.0.4",
    
  •            "readable-stream": "^3.4.0"
    
  •        }
    
  •    },
    
  •    "node_modules/bl/node_modules/readable-stream": {
    
  •        "version": "3.6.0",
    
  •        "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
    
  •        "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "inherits": "^2.0.3",
    
  •            "string_decoder": "^1.1.1",
    
  •            "util-deprecate": "^1.0.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">= 6"
    
  •        }
    
  •    },
    
  •    "node_modules/brace-expansion": {
    
  •        "version": "1.1.11",
    
  •        "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
    
  •        "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "balanced-match": "^1.0.0",
    
  •            "concat-map": "0.0.1"
    
  •        }
    
  •    },
    
  •    "node_modules/browserify-zlib": {
    
  •        "version": "0.1.4",
    
  •        "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
    
  •        "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "pako": "~0.2.0"
    
  •        }
    
  •    },
    
  •    "node_modules/buffer": {
    
  •        "version": "5.7.1",
    
  •        "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
    
  •        "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
    
  •        "dev": true,
    
  •        "funding": [
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://github.com/sponsors/feross"
    
  •            },
    
  •            {
    
  •                "type": "patreon",
    
  •                "url": "https://www.patreon.com/feross"
    
  •            },
    
  •            {
    
  •                "type": "consulting",
    
  •                "url": "https://feross.org/support"
    
  •            }
    
  •        ],
    
  •        "dependencies": {
    
  •            "base64-js": "^1.3.1",
    
  •            "ieee754": "^1.1.13"
    
  •        }
    
  •    },
    
  •    "node_modules/buffer-from": {
    
  •        "version": "1.1.2",
    
  •        "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
    
  •        "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/chownr": {
    
  •        "version": "1.1.4",
    
  •        "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
    
  •        "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/cliui": {
    
  •        "version": "8.0.1",
    
  •        "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
    
  •        "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "string-width": "^4.2.0",
    
  •            "strip-ansi": "^6.0.1",
    
  •            "wrap-ansi": "^7.0.0"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=12"
    
  •        }
    
  •    },
    
  •    "node_modules/color-convert": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
    
  •        "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "color-name": "~1.1.4"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=7.0.0"
    
  •        }
    
  •    },
    
  •    "node_modules/color-name": {
    
  •        "version": "1.1.4",
    
  •        "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
    
  •        "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/concat-map": {
    
  •        "version": "0.0.1",
    
  •        "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
    
  •        "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/core-util-is": {
    
  •        "version": "1.0.3",
    
  •        "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
    
  •        "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/data-uri-to-buffer": {
    
  •        "version": "4.0.0",
    
  •        "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
    
  •        "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">= 12"
    
  •        }
    
  •    },
    
  •    "node_modules/duplexify": {
    
  •        "version": "3.7.1",
    
  •        "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
    
  •        "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "end-of-stream": "^1.0.0",
    
  •            "inherits": "^2.0.1",
    
  •            "readable-stream": "^2.0.0",
    
  •            "stream-shift": "^1.0.0"
    
  •        }
    
  •    },
    
  •    "node_modules/emoji-regex": {
    
  •        "version": "8.0.0",
    
  •        "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
    
  •        "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/end-of-stream": {
    
  •        "version": "1.4.4",
    
  •        "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
    
  •        "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "once": "^1.4.0"
    
  •        }
    
  •    },
    
  •    "node_modules/escalade": {
    
  •        "version": "3.1.1",
    
  •        "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
    
  •        "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=6"
    
  •        }
    
  •    },
    
  •    "node_modules/fetch-blob": {
    
  •        "version": "3.2.0",
    
  •        "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
    
  •        "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
    
  •        "dev": true,
    
  •        "funding": [
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://github.com/sponsors/jimmywarting"
    
  •            },
    
  •            {
    
  •                "type": "paypal",
    
  •                "url": "https://paypal.me/jimmywarting"
    
  •            }
    
  •        ],
    
  •        "dependencies": {
    
  •            "node-domexception": "^1.0.0",
    
  •            "web-streams-polyfill": "^3.0.3"
    
  •        },
    
  •        "engines": {
    
  •            "node": "^12.20 || >= 14.13"
    
  •        }
    
  •    },
    
  •    "node_modules/findit2": {
    
  •        "version": "2.2.3",
    
  •        "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz",
    
  •        "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=0.8.22"
    
  •        }
    
  •    },
    
  •    "node_modules/formdata-polyfill": {
    
  •        "version": "4.0.10",
    
  •        "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
    
  •        "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "fetch-blob": "^3.1.2"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=12.20.0"
    
  •        }
    
  •    },
    
  •    "node_modules/fs-constants": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
    
  •        "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/fs.realpath": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
    
  •        "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/get-caller-file": {
    
  •        "version": "2.0.5",
    
  •        "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
    
  •        "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": "6.* || 8.* || >= 10.*"
    
  •        }
    
  •    },
    
  •    "node_modules/glob": {
    
  •        "version": "7.2.3",
    
  •        "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
    
  •        "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "fs.realpath": "^1.0.0",
    
  •            "inflight": "^1.0.4",
    
  •            "inherits": "2",
    
  •            "minimatch": "^3.1.1",
    
  •            "once": "^1.3.0",
    
  •            "path-is-absolute": "^1.0.0"
    
  •        },
    
  •        "engines": {
    
  •            "node": "*"
    
  •        },
    
  •        "funding": {
    
  •            "url": "https://github.com/sponsors/isaacs"
    
  •        }
    
  •    },
    
  •    "node_modules/gunzip-maybe": {
    
  •        "version": "1.4.2",
    
  •        "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
    
  •        "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "browserify-zlib": "^0.1.4",
    
  •            "is-deflate": "^1.0.0",
    
  •            "is-gzip": "^1.0.0",
    
  •            "peek-stream": "^1.1.0",
    
  •            "pumpify": "^1.3.3",
    
  •            "through2": "^2.0.3"
    
  •        },
    
  •        "bin": {
    
  •            "gunzip-maybe": "bin.js"
    
  •        }
    
  •    },
    
  •    "node_modules/ieee754": {
    
  •        "version": "1.2.1",
    
  •        "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
    
  •        "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
    
  •        "dev": true,
    
  •        "funding": [
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://github.com/sponsors/feross"
    
  •            },
    
  •            {
    
  •                "type": "patreon",
    
  •                "url": "https://www.patreon.com/feross"
    
  •            },
    
  •            {
    
  •                "type": "consulting",
    
  •                "url": "https://feross.org/support"
    
  •            }
    
  •        ]
    
  •    },
    
  •    "node_modules/inflight": {
    
  •        "version": "1.0.6",
    
  •        "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
    
  •        "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "once": "^1.3.0",
    
  •            "wrappy": "1"
    
  •        }
    
  •    },
    
  •    "node_modules/inherits": {
    
  •        "version": "2.0.4",
    
  •        "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
    
  •        "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/is-deflate": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz",
    
  •        "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/is-fullwidth-code-point": {
    
  •        "version": "3.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
    
  •        "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        }
    
  •    },
    
  •    "node_modules/is-gzip": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz",
    
  •        "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=0.10.0"
    
  •        }
    
  •    },
    
  •    "node_modules/isarray": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
    
  •        "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/jaguar": {
    
  •        "version": "6.0.1",
    
  •        "resolved": "https://registry.npmjs.org/jaguar/-/jaguar-6.0.1.tgz",
    
  •        "integrity": "sha512-WrJrwHHl4lHQa4dVG4fJTLxca19It/pTwtr5cnnUlY0MvWO0y1Y4dWn3ABVCEZzfd5gDbverVqin3tiZ/YloIQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "findit2": "^2.2.3",
    
  •            "glob": "^7.1.0",
    
  •            "gunzip-maybe": "^1.3.1",
    
  •            "minimist": "^1.2.0",
    
  •            "pipe-io": "^4.0.0",
    
  •            "tar-fs": "^2.0.0",
    
  •            "tar-stream": "^2.1.0",
    
  •            "try-to-catch": "^3.0.0"
    
  •        },
    
  •        "bin": {
    
  •            "jaguar": "bin/jaguar.js"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=10.0.0"
    
  •        }
    
  •    },
    
  •    "node_modules/minimatch": {
    
  •        "version": "3.1.2",
    
  •        "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
    
  •        "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "brace-expansion": "^1.1.7"
    
  •        },
    
  •        "engines": {
    
  •            "node": "*"
    
  •        }
    
  •    },
    
  •    "node_modules/minimist": {
    
  •        "version": "1.2.7",
    
  •        "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
    
  •        "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
    
  •        "dev": true,
    
  •        "funding": {
    
  •            "url": "https://github.com/sponsors/ljharb"
    
  •        }
    
  •    },
    
  •    "node_modules/mkdirp-classic": {
    
  •        "version": "0.5.3",
    
  •        "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
    
  •        "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/node-domexception": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
    
  •        "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
    
  •        "dev": true,
    
  •        "funding": [
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://github.com/sponsors/jimmywarting"
    
  •            },
    
  •            {
    
  •                "type": "github",
    
  •                "url": "https://paypal.me/jimmywarting"
    
  •            }
    
  •        ],
    
  •        "engines": {
    
  •            "node": ">=10.5.0"
    
  •        }
    
  •    },
    
  •    "node_modules/node-fetch": {
    
  •        "version": "3.2.10",
    
  •        "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz",
    
  •        "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "data-uri-to-buffer": "^4.0.0",
    
  •            "fetch-blob": "^3.1.4",
    
  •            "formdata-polyfill": "^4.0.10"
    
  •        },
    
  •        "engines": {
    
  •            "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
    
  •        },
    
  •        "funding": {
    
  •            "type": "opencollective",
    
  •            "url": "https://opencollective.com/node-fetch"
    
  •        }
    
  •    },
    
  •    "node_modules/once": {
    
  •        "version": "1.4.0",
    
  •        "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
    
  •        "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "wrappy": "1"
    
  •        }
    
  •    },
    
  •    "node_modules/pako": {
    
  •        "version": "0.2.9",
    
  •        "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
    
  •        "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/path-is-absolute": {
    
  •        "version": "1.0.1",
    
  •        "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
    
  •        "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=0.10.0"
    
  •        }
    
  •    },
    
  •    "node_modules/peek-stream": {
    
  •        "version": "1.1.3",
    
  •        "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz",
    
  •        "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "buffer-from": "^1.0.0",
    
  •            "duplexify": "^3.5.0",
    
  •            "through2": "^2.0.3"
    
  •        }
    
  •    },
    
  •    "node_modules/pipe-io": {
    
  •        "version": "4.0.1",
    
  •        "resolved": "https://registry.npmjs.org/pipe-io/-/pipe-io-4.0.1.tgz",
    
  •        "integrity": "sha512-Wj9G85wJCpIgHq7xd0g4/IDjrA51pxmd+m9AbTiC6zRmWzVC6jOJIUyf92r7/B2+NE6zwqZIz0BZr85xkc3/Sg==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        }
    
  •    },
    
  •    "node_modules/process-nextick-args": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
    
  •        "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/pump": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
    
  •        "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "end-of-stream": "^1.1.0",
    
  •            "once": "^1.3.1"
    
  •        }
    
  •    },
    
  •    "node_modules/pumpify": {
    
  •        "version": "1.5.1",
    
  •        "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
    
  •        "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "duplexify": "^3.6.0",
    
  •            "inherits": "^2.0.3",
    
  •            "pump": "^2.0.0"
    
  •        }
    
  •    },
    
  •    "node_modules/readable-stream": {
    
  •        "version": "2.3.7",
    
  •        "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
    
  •        "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "core-util-is": "~1.0.0",
    
  •            "inherits": "~2.0.3",
    
  •            "isarray": "~1.0.0",
    
  •            "process-nextick-args": "~2.0.0",
    
  •            "safe-buffer": "~5.1.1",
    
  •            "string_decoder": "~1.1.1",
    
  •            "util-deprecate": "~1.0.1"
    
  •        }
    
  •    },
    
  •    "node_modules/require-directory": {
    
  •        "version": "2.1.1",
    
  •        "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
    
  •        "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=0.10.0"
    
  •        }
    
  •    },
    
  •    "node_modules/safe-buffer": {
    
  •        "version": "5.1.2",
    
  •        "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
    
  •        "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/stream-shift": {
    
  •        "version": "1.0.1",
    
  •        "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
    
  •        "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/string_decoder": {
    
  •        "version": "1.1.1",
    
  •        "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    
  •        "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "safe-buffer": "~5.1.0"
    
  •        }
    
  •    },
    
  •    "node_modules/string-width": {
    
  •        "version": "4.2.3",
    
  •        "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
    
  •        "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "emoji-regex": "^8.0.0",
    
  •            "is-fullwidth-code-point": "^3.0.0",
    
  •            "strip-ansi": "^6.0.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        }
    
  •    },
    
  •    "node_modules/strip-ansi": {
    
  •        "version": "6.0.1",
    
  •        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
    
  •        "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "ansi-regex": "^5.0.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=8"
    
  •        }
    
  •    },
    
  •    "node_modules/tar-fs": {
    
  •        "version": "2.1.1",
    
  •        "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
    
  •        "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "chownr": "^1.1.1",
    
  •            "mkdirp-classic": "^0.5.2",
    
  •            "pump": "^3.0.0",
    
  •            "tar-stream": "^2.1.4"
    
  •        }
    
  •    },
    
  •    "node_modules/tar-fs/node_modules/pump": {
    
  •        "version": "3.0.0",
    
  •        "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
    
  •        "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "end-of-stream": "^1.1.0",
    
  •            "once": "^1.3.1"
    
  •        }
    
  •    },
    
  •    "node_modules/tar-stream": {
    
  •        "version": "2.2.0",
    
  •        "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
    
  •        "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "bl": "^4.0.3",
    
  •            "end-of-stream": "^1.4.1",
    
  •            "fs-constants": "^1.0.0",
    
  •            "inherits": "^2.0.3",
    
  •            "readable-stream": "^3.1.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=6"
    
  •        }
    
  •    },
    
  •    "node_modules/tar-stream/node_modules/readable-stream": {
    
  •        "version": "3.6.0",
    
  •        "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
    
  •        "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "inherits": "^2.0.3",
    
  •            "string_decoder": "^1.1.1",
    
  •            "util-deprecate": "^1.0.1"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">= 6"
    
  •        }
    
  •    },
    
  •    "node_modules/through2": {
    
  •        "version": "2.0.5",
    
  •        "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
    
  •        "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "readable-stream": "~2.3.6",
    
  •            "xtend": "~4.0.1"
    
  •        }
    
  •    },
    
  •    "node_modules/try-to-catch": {
    
  •        "version": "3.0.1",
    
  •        "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz",
    
  •        "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=6"
    
  •        }
    
  •    },
    
  •    "node_modules/util-deprecate": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
    
  •        "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/web-streams-polyfill": {
    
  •        "version": "3.2.1",
    
  •        "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
    
  •        "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">= 8"
    
  •        }
    
  •    },
    
  •    "node_modules/wrap-ansi": {
    
  •        "version": "7.0.0",
    
  •        "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
    
  •        "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "ansi-styles": "^4.0.0",
    
  •            "string-width": "^4.1.0",
    
  •            "strip-ansi": "^6.0.0"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=10"
    
  •        },
    
  •        "funding": {
    
  •            "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
    
  •        }
    
  •    },
    
  •    "node_modules/wrappy": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
    
  •        "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node_modules/xtend": {
    
  •        "version": "4.0.2",
    
  •        "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
    
  •        "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=0.4"
    
  •        }
    
  •    },
    
  •    "node_modules/y18n": {
    
  •        "version": "5.0.8",
    
  •        "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
    
  •        "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=10"
    
  •        }
    
  •    },
    
  •    "node_modules/yargs": {
    
  •        "version": "17.6.1",
    
  •        "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.1.tgz",
    
  •        "integrity": "sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg==",
    
  •        "dev": true,
    
  •        "dependencies": {
    
  •            "cliui": "^8.0.1",
    
  •            "escalade": "^3.1.1",
    
  •            "get-caller-file": "^2.0.5",
    
  •            "require-directory": "^2.1.1",
    
  •            "string-width": "^4.2.3",
    
  •            "y18n": "^5.0.5",
    
  •            "yargs-parser": "^21.0.0"
    
  •        },
    
  •        "engines": {
    
  •            "node": ">=12"
    
  •        }
    
  •    },
    
  •    "node_modules/yargs-parser": {
    
  •        "version": "21.1.1",
    
  •        "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
    
  •        "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
    
  •        "dev": true,
    
  •        "engines": {
    
  •            "node": ">=12"
    
  •        }
    
  •    }
    
  • },
  • "dependencies": {
  •    "ansi-regex": {
    
  •        "version": "5.0.1",
    
  •        "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
    
  •        "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
    
  •        "dev": true
    
  •    },
    
  •    "ansi-styles": {
    
  •        "version": "4.3.0",
    
  •        "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
    
  •        "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "color-convert": "^2.0.1"
    
  •        }
    
  •    },
    
  •    "balanced-match": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
    
  •        "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
    
  •        "dev": true
    
  •    },
    
  •    "base64-js": {
    
  •        "version": "1.5.1",
    
  •        "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
    
  •        "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
    
  •        "dev": true
    
  •    },
    
  •    "bl": {
    
  •        "version": "4.1.0",
    
  •        "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
    
  •        "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "buffer": "^5.5.0",
    
  •            "inherits": "^2.0.4",
    
  •            "readable-stream": "^3.4.0"
    
  •        },
    
  •        "dependencies": {
    
  •            "readable-stream": {
    
  •                "version": "3.6.0",
    
  •                "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
    
  •                "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
    
  •                "dev": true,
    
  •                "requires": {
    
  •                    "inherits": "^2.0.3",
    
  •                    "string_decoder": "^1.1.1",
    
  •                    "util-deprecate": "^1.0.1"
    
  •                }
    
  •            }
    
  •        }
    
  •    },
    
  •    "brace-expansion": {
    
  •        "version": "1.1.11",
    
  •        "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
    
  •        "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "balanced-match": "^1.0.0",
    
  •            "concat-map": "0.0.1"
    
  •        }
    
  •    },
    
  •    "browserify-zlib": {
    
  •        "version": "0.1.4",
    
  •        "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
    
  •        "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "pako": "~0.2.0"
    
  •        }
    
  •    },
    
  •    "buffer": {
    
  •        "version": "5.7.1",
    
  •        "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
    
  •        "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "base64-js": "^1.3.1",
    
  •            "ieee754": "^1.1.13"
    
  •        }
    
  •    },
    
  •    "buffer-from": {
    
  •        "version": "1.1.2",
    
  •        "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
    
  •        "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
    
  •        "dev": true
    
  •    },
    
  •    "chownr": {
    
  •        "version": "1.1.4",
    
  •        "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
    
  •        "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
    
  •        "dev": true
    
  •    },
    
  •    "cliui": {
    
  •        "version": "8.0.1",
    
  •        "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
    
  •        "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "string-width": "^4.2.0",
    
  •            "strip-ansi": "^6.0.1",
    
  •            "wrap-ansi": "^7.0.0"
    
  •        }
    
  •    },
    
  •    "color-convert": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
    
  •        "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "color-name": "~1.1.4"
    
  •        }
    
  •    },
    
  •    "color-name": {
    
  •        "version": "1.1.4",
    
  •        "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
    
  •        "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
    
  •        "dev": true
    
  •    },
    
  •    "concat-map": {
    
  •        "version": "0.0.1",
    
  •        "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
    
  •        "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
    
  •        "dev": true
    
  •    },
    
  •    "core-util-is": {
    
  •        "version": "1.0.3",
    
  •        "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
    
  •        "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
    
  •        "dev": true
    
  •    },
    
  •    "data-uri-to-buffer": {
    
  •        "version": "4.0.0",
    
  •        "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
    
  •        "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
    
  •        "dev": true
    
  •    },
    
  •    "duplexify": {
    
  •        "version": "3.7.1",
    
  •        "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
    
  •        "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "end-of-stream": "^1.0.0",
    
  •            "inherits": "^2.0.1",
    
  •            "readable-stream": "^2.0.0",
    
  •            "stream-shift": "^1.0.0"
    
  •        }
    
  •    },
    
  •    "emoji-regex": {
    
  •        "version": "8.0.0",
    
  •        "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
    
  •        "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
    
  •        "dev": true
    
  •    },
    
  •    "end-of-stream": {
    
  •        "version": "1.4.4",
    
  •        "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
    
  •        "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "once": "^1.4.0"
    
  •        }
    
  •    },
    
  •    "escalade": {
    
  •        "version": "3.1.1",
    
  •        "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
    
  •        "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
    
  •        "dev": true
    
  •    },
    
  •    "fetch-blob": {
    
  •        "version": "3.2.0",
    
  •        "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
    
  •        "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "node-domexception": "^1.0.0",
    
  •            "web-streams-polyfill": "^3.0.3"
    
  •        }
    
  •    },
    
  •    "findit2": {
    
  •        "version": "2.2.3",
    
  •        "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz",
    
  •        "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==",
    
  •        "dev": true
    
  •    },
    
  •    "formdata-polyfill": {
    
  •        "version": "4.0.10",
    
  •        "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
    
  •        "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "fetch-blob": "^3.1.2"
    
  •        }
    
  •    },
    
  •    "fs-constants": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
    
  •        "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
    
  •        "dev": true
    
  •    },
    
  •    "fs.realpath": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
    
  •        "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
    
  •        "dev": true
    
  •    },
    
  •    "get-caller-file": {
    
  •        "version": "2.0.5",
    
  •        "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
    
  •        "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
    
  •        "dev": true
    
  •    },
    
  •    "glob": {
    
  •        "version": "7.2.3",
    
  •        "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
    
  •        "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "fs.realpath": "^1.0.0",
    
  •            "inflight": "^1.0.4",
    
  •            "inherits": "2",
    
  •            "minimatch": "^3.1.1",
    
  •            "once": "^1.3.0",
    
  •            "path-is-absolute": "^1.0.0"
    
  •        }
    
  •    },
    
  •    "gunzip-maybe": {
    
  •        "version": "1.4.2",
    
  •        "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
    
  •        "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "browserify-zlib": "^0.1.4",
    
  •            "is-deflate": "^1.0.0",
    
  •            "is-gzip": "^1.0.0",
    
  •            "peek-stream": "^1.1.0",
    
  •            "pumpify": "^1.3.3",
    
  •            "through2": "^2.0.3"
    
  •        }
    
  •    },
    
  •    "ieee754": {
    
  •        "version": "1.2.1",
    
  •        "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
    
  •        "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
    
  •        "dev": true
    
  •    },
    
  •    "inflight": {
    
  •        "version": "1.0.6",
    
  •        "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
    
  •        "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "once": "^1.3.0",
    
  •            "wrappy": "1"
    
  •        }
    
  •    },
    
  •    "inherits": {
    
  •        "version": "2.0.4",
    
  •        "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
    
  •        "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
    
  •        "dev": true
    
  •    },
    
  •    "is-deflate": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz",
    
  •        "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==",
    
  •        "dev": true
    
  •    },
    
  •    "is-fullwidth-code-point": {
    
  •        "version": "3.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
    
  •        "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
    
  •        "dev": true
    
  •    },
    
  •    "is-gzip": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz",
    
  •        "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==",
    
  •        "dev": true
    
  •    },
    
  •    "isarray": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
    
  •        "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
    
  •        "dev": true
    
  •    },
    
  •    "jaguar": {
    
  •        "version": "6.0.1",
    
  •        "resolved": "https://registry.npmjs.org/jaguar/-/jaguar-6.0.1.tgz",
    
  •        "integrity": "sha512-WrJrwHHl4lHQa4dVG4fJTLxca19It/pTwtr5cnnUlY0MvWO0y1Y4dWn3ABVCEZzfd5gDbverVqin3tiZ/YloIQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "findit2": "^2.2.3",
    
  •            "glob": "^7.1.0",
    
  •            "gunzip-maybe": "^1.3.1",
    
  •            "minimist": "^1.2.0",
    
  •            "pipe-io": "^4.0.0",
    
  •            "tar-fs": "^2.0.0",
    
  •            "tar-stream": "^2.1.0",
    
  •            "try-to-catch": "^3.0.0"
    
  •        }
    
  •    },
    
  •    "minimatch": {
    
  •        "version": "3.1.2",
    
  •        "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
    
  •        "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "brace-expansion": "^1.1.7"
    
  •        }
    
  •    },
    
  •    "minimist": {
    
  •        "version": "1.2.7",
    
  •        "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
    
  •        "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
    
  •        "dev": true
    
  •    },
    
  •    "mkdirp-classic": {
    
  •        "version": "0.5.3",
    
  •        "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
    
  •        "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
    
  •        "dev": true
    
  •    },
    
  •    "node-domexception": {
    
  •        "version": "1.0.0",
    
  •        "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
    
  •        "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
    
  •        "dev": true
    
  •    },
    
  •    "node-fetch": {
    
  •        "version": "3.2.10",
    
  •        "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz",
    
  •        "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "data-uri-to-buffer": "^4.0.0",
    
  •            "fetch-blob": "^3.1.4",
    
  •            "formdata-polyfill": "^4.0.10"
    
  •        }
    
  •    },
    
  •    "once": {
    
  •        "version": "1.4.0",
    
  •        "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
    
  •        "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "wrappy": "1"
    
  •        }
    
  •    },
    
  •    "pako": {
    
  •        "version": "0.2.9",
    
  •        "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
    
  •        "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
    
  •        "dev": true
    
  •    },
    
  •    "path-is-absolute": {
    
  •        "version": "1.0.1",
    
  •        "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
    
  •        "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
    
  •        "dev": true
    
  •    },
    
  •    "peek-stream": {
    
  •        "version": "1.1.3",
    
  •        "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz",
    
  •        "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "buffer-from": "^1.0.0",
    
  •            "duplexify": "^3.5.0",
    
  •            "through2": "^2.0.3"
    
  •        }
    
  •    },
    
  •    "pipe-io": {
    
  •        "version": "4.0.1",
    
  •        "resolved": "https://registry.npmjs.org/pipe-io/-/pipe-io-4.0.1.tgz",
    
  •        "integrity": "sha512-Wj9G85wJCpIgHq7xd0g4/IDjrA51pxmd+m9AbTiC6zRmWzVC6jOJIUyf92r7/B2+NE6zwqZIz0BZr85xkc3/Sg==",
    
  •        "dev": true
    
  •    },
    
  •    "process-nextick-args": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
    
  •        "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
    
  •        "dev": true
    
  •    },
    
  •    "pump": {
    
  •        "version": "2.0.1",
    
  •        "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
    
  •        "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "end-of-stream": "^1.1.0",
    
  •            "once": "^1.3.1"
    
  •        }
    
  •    },
    
  •    "pumpify": {
    
  •        "version": "1.5.1",
    
  •        "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
    
  •        "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "duplexify": "^3.6.0",
    
  •            "inherits": "^2.0.3",
    
  •            "pump": "^2.0.0"
    
  •        }
    
  •    },
    
  •    "readable-stream": {
    
  •        "version": "2.3.7",
    
  •        "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
    
  •        "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "core-util-is": "~1.0.0",
    
  •            "inherits": "~2.0.3",
    
  •            "isarray": "~1.0.0",
    
  •            "process-nextick-args": "~2.0.0",
    
  •            "safe-buffer": "~5.1.1",
    
  •            "string_decoder": "~1.1.1",
    
  •            "util-deprecate": "~1.0.1"
    
  •        }
    
  •    },
    
  •    "require-directory": {
    
  •        "version": "2.1.1",
    
  •        "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
    
  •        "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
    
  •        "dev": true
    
  •    },
    
  •    "safe-buffer": {
    
  •        "version": "5.1.2",
    
  •        "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
    
  •        "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
    
  •        "dev": true
    
  •    },
    
  •    "stream-shift": {
    
  •        "version": "1.0.1",
    
  •        "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
    
  •        "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
    
  •        "dev": true
    
  •    },
    
  •    "string_decoder": {
    
  •        "version": "1.1.1",
    
  •        "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    
  •        "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "safe-buffer": "~5.1.0"
    
  •        }
    
  •    },
    
  •    "string-width": {
    
  •        "version": "4.2.3",
    
  •        "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
    
  •        "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "emoji-regex": "^8.0.0",
    
  •            "is-fullwidth-code-point": "^3.0.0",
    
  •            "strip-ansi": "^6.0.1"
    
  •        }
    
  •    },
    
  •    "strip-ansi": {
    
  •        "version": "6.0.1",
    
  •        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
    
  •        "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "ansi-regex": "^5.0.1"
    
  •        }
    
  •    },
    
  •    "tar-fs": {
    
  •        "version": "2.1.1",
    
  •        "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
    
  •        "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "chownr": "^1.1.1",
    
  •            "mkdirp-classic": "^0.5.2",
    
  •            "pump": "^3.0.0",
    
  •            "tar-stream": "^2.1.4"
    
  •        },
    
  •        "dependencies": {
    
  •            "pump": {
    
  •                "version": "3.0.0",
    
  •                "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
    
  •                "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
    
  •                "dev": true,
    
  •                "requires": {
    
  •                    "end-of-stream": "^1.1.0",
    
  •                    "once": "^1.3.1"
    
  •                }
    
  •            }
    
  •        }
    
  •    },
    
  •    "tar-stream": {
    
  •        "version": "2.2.0",
    
  •        "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
    
  •        "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "bl": "^4.0.3",
    
  •            "end-of-stream": "^1.4.1",
    
  •            "fs-constants": "^1.0.0",
    
  •            "inherits": "^2.0.3",
    
  •            "readable-stream": "^3.1.1"
    
  •        },
    
  •        "dependencies": {
    
  •            "readable-stream": {
    
  •                "version": "3.6.0",
    
  •                "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
    
  •                "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
    
  •                "dev": true,
    
  •                "requires": {
    
  •                    "inherits": "^2.0.3",
    
  •                    "string_decoder": "^1.1.1",
    
  •                    "util-deprecate": "^1.0.1"
    
  •                }
    
  •            }
    
  •        }
    
  •    },
    
  •    "through2": {
    
  •        "version": "2.0.5",
    
  •        "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
    
  •        "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "readable-stream": "~2.3.6",
    
  •            "xtend": "~4.0.1"
    
  •        }
    
  •    },
    
  •    "try-to-catch": {
    
  •        "version": "3.0.1",
    
  •        "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz",
    
  •        "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==",
    
  •        "dev": true
    
  •    },
    
  •    "util-deprecate": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
    
  •        "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
    
  •        "dev": true
    
  •    },
    
  •    "web-streams-polyfill": {
    
  •        "version": "3.2.1",
    
  •        "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
    
  •        "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
    
  •        "dev": true
    
  •    },
    
  •    "wrap-ansi": {
    
  •        "version": "7.0.0",
    
  •        "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
    
  •        "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "ansi-styles": "^4.0.0",
    
  •            "string-width": "^4.1.0",
    
  •            "strip-ansi": "^6.0.0"
    
  •        }
    
  •    },
    
  •    "wrappy": {
    
  •        "version": "1.0.2",
    
  •        "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
    
  •        "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
    
  •        "dev": true
    
  •    },
    
  •    "xtend": {
    
  •        "version": "4.0.2",
    
  •        "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
    
  •        "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
    
  •        "dev": true
    
  •    },
    
  •    "y18n": {
    
  •        "version": "5.0.8",
    
  •        "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
    
  •        "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
    
  •        "dev": true
    
  •    },
    
  •    "yargs": {
    
  •        "version": "17.6.1",
    
  •        "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.1.tgz",
    
  •        "integrity": "sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg==",
    
  •        "dev": true,
    
  •        "requires": {
    
  •            "cliui": "^8.0.1",
    
  •            "escalade": "^3.1.1",
    
  •            "get-caller-file": "^2.0.5",
    
  •            "require-directory": "^2.1.1",
    
  •            "string-width": "^4.2.3",
    
  •            "y18n": "^5.0.5",
    
  •            "yargs-parser": "^21.0.0"
    
  •        }
    
  •    },
    
  •    "yargs-parser": {
    
  •        "version": "21.1.1",
    
  •        "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
    
  •        "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
    
  •        "dev": true
    
  •    }
    
  • }
    +}
    diff --git a/package.json b/package.json
    new file mode 100644
    index 0000000..c251c60
    --- /dev/null
    +++ b/package.json
    @@ -0,0 +1,34 @@
    +{
  • "name": "x_bows",
  • "version": "1.0.0",
  • "description": "Adds bow and arrows to Minetest.",
  • "main": "index.js",
  • "type": "module",
  • "directories": {
  •    "doc": "docs"
    
  • },
  • "engines": {
  •    "node": ">=16.0.0",
    
  •    "npm": ">=8.0.0"
    
  • },
  • "scripts": {
  •    "test": "echo \"Error: no test specified\" && exit 1",
    
  •    "push:ci": "node ./scripts/deploy",
    
  •    "lua-diagnostics": "node ./scripts/lls-check"
    
  • },
  • "repository": {
  •    "type": "git",
    
  •    "url": "git+https://juraj_vajda@bitbucket.org/minetest_gamers/x_bows.git"
    
  • },
  • "author": "SaKeL",
  • "license": "LGPL-2.1-or-later",
  • "bugs": {
  •    "url": "https://bitbucket.org/minetest_gamers/x_bows/issues"
    
  • },
  • "homepage": "https://bitbucket.org/minetest_gamers/x_bows#readme",
  • "devDependencies": {
  •    "jaguar": "^6.0.1",
    
  •    "node-fetch": "^3.2.10",
    
  •    "yargs": "^17.6.1"
    
  • }
    +}
    diff --git a/particle_effects.lua b/particle_effects.lua
    new file mode 100644
    index 0000000..a0621f7
    --- /dev/null
    +++ b/particle_effects.lua
    @@ -0,0 +1,94 @@
    +--[[
  • X Bows. Adds bow and arrows with API.
  • Copyright (C) 2022 SaKeL juraj.vajda@gmail.com
  • This library is free software; you can redistribute it and/or
  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.
  • This library is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  • Lesser General Public License for more details.
  • You should have received a copy of the GNU Lesser General Public
  • License along with this library; if not, write to juraj.vajda@gmail.com
    +--]]

+XBows:register_particle_effect('arrow', {

  • amount = 1,
  • time = 0.1,
  • minexptime = 0.5,
  • maxexptime = 0.5,
  • minsize = 2,
  • maxsize = 2,
  • texture = 'x_bows_arrow_particle.png',
  • animation = {
  •    type = 'vertical_frames',
    
  •    aspect_w = 8,
    
  •    aspect_h = 8,
    
  •    length = 1,
    
  • },
  • glow = 1,
  • minvel = { x = 0, y = -0.1, z = 0 },
  • maxvel = { x = 0, y = -0.1, z = 0 },
  • minacc = { x = 0, y = -0.1, z = 0 },
  • maxacc = { x = 0, y = -0.1, z = 0 }
    +})

+XBows:register_particle_effect('arrow_crit', {

  • amount = 1,
  • time = 0.1,
  • 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,
  • minvel = { x = 0, y = -0.1, z = 0 },
  • maxvel = { x = 0, y = -0.1, z = 0 },
  • minacc = { x = 0, y = -0.1, z = 0 },
  • maxacc = { x = 0, y = -0.1, z = 0 }
    +})

+XBows:register_particle_effect('arrow_fast', {

  • amount = 1,
  • time = 0.1,
  • 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,
  • minvel = { x = 0, y = -0.1, z = 0 },
  • maxvel = { x = 0, y = -0.1, z = 0 },
  • minacc = { x = 0, y = -0.1, z = 0 },
  • maxacc = { x = 0, y = -0.1, z = 0 }
    +})

+XBows:register_particle_effect('bubble', {

  • amount = 1,
  • time = 1,
  • 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'
    +})
    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.4.png b/screenshot.4.png
    new file mode 100644
    index 0000000..db15ea8
    Binary files /dev/null and b/screenshot.4.png differ
    diff --git a/screenshot.5.png b/screenshot.5.png
    new file mode 100644
    index 0000000..a2bd87a
    Binary files /dev/null and b/screenshot.5.png differ
    diff --git a/screenshot.png b/screenshot.png
    index c5fd447..a6bf611 100644
    Binary files a/screenshot.png and b/screenshot.png differ
    diff --git a/scripts/deploy.js b/scripts/deploy.js
    new file mode 100644
    index 0000000..2faa964
    --- /dev/null
    +++ b/scripts/deploy.js
    @@ -0,0 +1,50 @@
    +/**
    • Deploy code to CDB
    • This library is free software; you can redistribute it and/or
    • modify it under the terms of the GNU Lesser General Public
    • License as published by the Free Software Foundation; either
    • version 2.1 of the License, or (at your option) any later version.
    • This library is distributed in the hope that it will be useful,
    • but WITHOUT ANY WARRANTY; without even the implied warranty of
    • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    • Lesser General Public License for more details.
    • You should have received a copy of the GNU Lesser General Public
  • */

+import fetch from 'node-fetch'
+import yargs from 'yargs/yargs'
+import {hideBin} from 'yargs/helpers'
+
+const argv = yargs(hideBin(process.argv)).argv
+
+try {

  • const body = {
  •    method: 'git',
    
  •    title: argv.title,
    
  •    ref: 'master'
    
  • }
  • const response = await fetch('https://content.minetest.net/api/packages/SaKeL/x_bows/releases/new/', {
  •    method: 'POST',
    
  •    body: JSON.stringify(body),
    
  •    headers: {
    
  •        'Content-Type': 'application/json',
    
  •        Authorization: `Bearer ${argv.token}`
    
  •    }
    
  • })
  • const data = await response.json()
  • console.log(data)
  • if (!data.success) {
  •    process.exit(1)
    
  • }
    +} catch (error) {
  • console.log(error)
  • process.exit(1)
    +}
    diff --git a/scripts/lls-check.js b/scripts/lls-check.js
    new file mode 100644
    index 0000000..d4a74d3
    --- /dev/null
    +++ b/scripts/lls-check.js
    @@ -0,0 +1,111 @@
    +/**
    • Run LUA diagnostics in continuous integration
    • This library is free software; you can redistribute it and/or
    • modify it under the terms of the GNU Lesser General Public
    • License as published by the Free Software Foundation; either
    • version 2.1 of the License, or (at your option) any later version.
    • This library is distributed in the hope that it will be useful,
    • but WITHOUT ANY WARRANTY; without even the implied warranty of
    • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    • Lesser General Public License for more details.
    • You should have received a copy of the GNU Lesser General Public
  • */

+import * as path from 'node:path'
+import * as fs from 'node:fs'
+import {exec} from 'node:child_process'
+import yargs from 'yargs/yargs'
+import {hideBin} from 'yargs/helpers'
+import jaguar from 'jaguar'
+
+const argv = yargs(hideBin(process.argv)).argv
+const cwd = process.cwd()
+const logPath = path.join(cwd, 'logs')
+
+// Extract lua language server
+const from = path.join(cwd, 'bin/lua-language-server-3.5.6-linux-x64.tar.gz');
+const to = path.join(cwd, 'bin', 'lua-language-server-3.5.6-linux-x64');
+const extract = jaguar.extract(from, to)
+
+// extract.on('file', (name) => {
+// console.log(name)
+// })
+
+extract.on('start', () => {

  • console.log('Extracting...')
    +})

+// extract.on('progress', (percent) => {
+// console.log(percent + '%')
+// })
+
+extract.on('error', (error) => {

  • console.error(error)
  • process.exit(1)
    +})

+extract.on('end', () => {

  • console.log('Extracting: Done')
  • // Delete directory recursively
  • try {
  •    fs.rmSync(logPath, { recursive: true, force: true })
    
  •    console.log(`Removed folder: ${logPath}`)
    
  • } catch (err) {
  •    console.error(`Error while deleting ${logPath}.`)
    
  •    console.error(err)
    
  • }
  • let command = './bin/lua-language-server-3.5.6-linux-x64/bin/lua-language-server'
  • if (argv.local) {
  •    command = 'lua-language-server'
    
  • }
  • exec(${command} --logpath "${logPath}" --check "${cwd}", (error, stdout, stderr) => {
  •    if (error) {
    
  •        console.log(`error: ${error.message}`)
    
  •        process.exit(1)
    
  •    }
    
  •    if (stderr) {
    
  •        console.log(`stderr: ${stderr}`)
    
  •        process.exit(1)
    
  •    }
    
  •    console.log(`\n${stdout}`)
    
  •    if (fs.existsSync('./logs/check.json')) {
    
  •        const rawdata = fs.readFileSync('./logs/check.json')
    
  •        const diagnosticsJson = JSON.parse(rawdata)
    
  •        Object.keys(diagnosticsJson).forEach((key) => {
    
  •            console.log(key)
    
  •            diagnosticsJson[key].forEach((errObj) => {
    
  •                console.log(`line: ${errObj.range.start.line} - ${errObj.message}`)
    
  •            })
    
  •        })
    
  •        console.error('Fix the errors/warnings above.')
    
  •        process.exit(1)
    
  •    }
    
  •    // Delete directory recursively
    
  •    const llsFolder = path.join(cwd, 'bin', 'lua-language-server-3.5.6-linux-x64')
    
  •    try {
    
  •        fs.rmSync(llsFolder, { recursive: true, force: true })
    
  •        console.log(`Removed folder: ${llsFolder}`)
    
  •    } catch (err) {
    
  •        console.error(`Error while deleting ${llsFolder}.`)
    
  •        console.error(err)
    
  •        process.exit(1)
    
  •    }
    
  • })
    +})
    diff --git a/settingtypes.txt b/settingtypes.txt
    index f4842e4..42a2a45 100644
    --- a/settingtypes.txt
    +++ b/settingtypes.txt
    @@ -1,2 +1,9 @@

Disabled per default due to inconsistent mob models scaling (visual_size). This will scale the arrows unproportionally and looks bad. If you have mobs with no scaling you can enable this setting.

x_bows_attach_arrows_to_entities (Attach arrows to entities) bool false
+
+# Shows caused damage number flying out from the hit object/player.
+x_bows_show_damage_numbers (Show damage caused) bool false
+
+
+# Shows 3d quiver in 3rd person view (3d armor, and some skins MODs supported - see optional dependencies)
+x_bows_show_3d_quiver (Show 3D Quiver) bool true
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/smoke_puff.png b/textures/smoke_puff.png
new file mode 100644
index 0000000..88c5d39
Binary files /dev/null and b/textures/smoke_puff.png differ
diff --git a/textures/x_bows_arrow_diamond_poison.png b/textures/x_bows_arrow_diamond_poison.png
deleted file mode 100644
index 53f35c9..0000000
Binary files a/textures/x_bows_arrow_diamond_poison.png and /dev/null differ
diff --git a/textures/x_bows_arrow_mesh.png b/textures/x_bows_arrow_mesh.png
new file mode 100644
index 0000000..21aae46
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..045d9d7
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_arrow_tipped_particle.png b/textures/x_bows_arrow_tipped_particle.png
deleted file mode 100644
index 1a17ed6..0000000
Binary files a/textures/x_bows_arrow_tipped_particle.png and /dev/null differ
diff --git a/textures/x_bows_dmg_0.png b/textures/x_bows_dmg_0.png
new file mode 100644
index 0000000..a19cd00
Binary files /dev/null and b/textures/x_bows_dmg_0.png differ
diff --git a/textures/x_bows_dmg_1.png b/textures/x_bows_dmg_1.png
new file mode 100644
index 0000000..6702c8d
Binary files /dev/null and b/textures/x_bows_dmg_1.png differ
diff --git a/textures/x_bows_dmg_2.png b/textures/x_bows_dmg_2.png
new file mode 100644
index 0000000..8b35f55
Binary files /dev/null and b/textures/x_bows_dmg_2.png differ
diff --git a/textures/x_bows_dmg_3.png b/textures/x_bows_dmg_3.png
new file mode 100644
index 0000000..dc24c16
Binary files /dev/null and b/textures/x_bows_dmg_3.png differ
diff --git a/textures/x_bows_dmg_4.png b/textures/x_bows_dmg_4.png
new file mode 100644
index 0000000..f603d7c
Binary files /dev/null and b/textures/x_bows_dmg_4.png differ
diff --git a/textures/x_bows_dmg_5.png b/textures/x_bows_dmg_5.png
new file mode 100644
index 0000000..3300e98
Binary files /dev/null and b/textures/x_bows_dmg_5.png differ
diff --git a/textures/x_bows_dmg_6.png b/textures/x_bows_dmg_6.png
new file mode 100644
index 0000000..8da46e5
Binary files /dev/null and b/textures/x_bows_dmg_6.png differ
diff --git a/textures/x_bows_dmg_7.png b/textures/x_bows_dmg_7.png
new file mode 100644
index 0000000..d01881f
Binary files /dev/null and b/textures/x_bows_dmg_7.png differ
diff --git a/textures/x_bows_dmg_8.png b/textures/x_bows_dmg_8.png
new file mode 100644
index 0000000..6cd88e5
Binary files /dev/null and b/textures/x_bows_dmg_8.png differ
diff --git a/textures/x_bows_dmg_9.png b/textures/x_bows_dmg_9.png
new file mode 100644
index 0000000..72feaa1
Binary files /dev/null and b/textures/x_bows_dmg_9.png differ
diff --git a/textures/x_bows_hotbar_selected.png b/textures/x_bows_hotbar_selected.png
new file mode 100644
index 0000000..0666240
Binary files /dev/null and b/textures/x_bows_hotbar_selected.png 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_blank_mesh.png b/textures/x_bows_quiver_blank_mesh.png
new file mode 100644
index 0000000..9e7ec19
Binary files /dev/null and b/textures/x_bows_quiver_blank_mesh.png differ
diff --git a/textures/x_bows_quiver_empty_mesh.png b/textures/x_bows_quiver_empty_mesh.png
new file mode 100644
index 0000000..65fb025
Binary files /dev/null and b/textures/x_bows_quiver_empty_mesh.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_mesh.png b/textures/x_bows_quiver_mesh.png
new file mode 100644
index 0000000..d128775
Binary files /dev/null and b/textures/x_bows_quiver_mesh.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/textures/x_bows_quiver_slot.png b/textures/x_bows_quiver_slot.png
new file mode 100644
index 0000000..1acc67a
Binary files /dev/null and b/textures/x_bows_quiver_slot.png differ
diff --git a/textures/x_bows_single_hotbar.png b/textures/x_bows_single_hotbar.png
new file mode 100644
index 0000000..4aa7104
Binary files /dev/null and b/textures/x_bows_single_hotbar.png differ
diff --git a/types/colors.type.lua b/types/colors.type.lua
new file mode 100644
index 0000000..6ae5ef2
--- /dev/null
+++ b/types/colors.type.lua
@@ -0,0 +1,12 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---@alias ColorSpec string|ColorSpecTable A ColorSpec specifies a 32-bit color. It can be written in any of the following forms: colorspec = {a=255, r=0, g=255, b=0}, numerical form: The raw integer value of an ARGB8 quad: colorspec = 0xFF00FF00, string form: A ColorString (defined above): colorspec = "green"
+---@alias ColorString string #RGB defines a color in hexadecimal format. #RGBA defines a color in hexadecimal format and alpha channel. #RRGGBB defines a color in hexadecimal format. #RRGGBBAA defines a color in hexadecimal format and alpha channel. Named colors are also supported and are equivalent to CSS Color Module Level 4. To specify the value of the alpha channel, append #A or #AA to the end of the color name (e.g. colorname#08).
+
+---A ColorSpec table form: Each element ranging from 0..255 (a, if absent, defaults to 255):
+---@class ColorSpecTable
+---@field a number
+---@field r number
+---@field g number
+---@field b number
diff --git a/types/craft-recipe.type.lua b/types/craft-recipe.type.lua
new file mode 100644
index 0000000..b2e2bd6
--- /dev/null
+++ b/types/craft-recipe.type.lua
@@ -0,0 +1,12 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---Crafting recipes
+---@class CraftRecipeDef
+---@field type string (optional) specifies recipe type as shaped, e.g. "shaped", "shapeless", "toolrepair", "cooking", "fuel", default: "shaped"
+---@field output string Itemstring of output itemstack (item counts >= 1 are allowed)
+---@field recipe table<integer|number, string>[]|string A 2-dimensional matrix of items, with a width w and height h. w and h are chosen by you, they don't have to be equal but must be at least 1. The matrix is specified as a table containing tables containing itemnames. The inner tables are the rows. There must be h tables, specified from the top to the bottom row. Values inside of the inner table are the columns. Each inner table must contain a list of w items, specified from left to right. Empty slots must be filled with the empty string.
+---@field replacements string[] (optional) Allows you to replace input items with some other items when something is crafted. Provided as a list of item pairs of the form { old_item, new_item } where old_item is the input item to replace (same syntax as for a regular input slot; groups are allowed) and new_item is an itemstring for the item stack it will become. When the output is crafted, Minetest iterates through the list of input items if the crafting grid. For each input item stack, it checks if it matches with an old_item in the item pair list. If it matches, the item will be replaced. Also, this item pair will not be applied again for the remaining items. If it does not match, the item is consumed (reduced by 1) normally. The new_item will appear in one of 3 places: Crafting grid, if the input stack size was exactly 1, Player inventory, if input stack size was larger, Drops as item entity, if it fits neither in craft grid or inventory.
+---@field additional_wear number|integer For {type = "toolrepair"} only. Adds a shapeless recipe for every tool that doesn't have the disable_repair=1 group. If this recipe is used, repairing is possible with any crafting grid with at least 2 slots. The player can put 2 equal tools in the craft grid to get one "repaired" tool back. The wear of the output is determined by the wear of both tools, plus a 'repair bonus' given by additional_wear. To reduce the wear (i.e. 'repair'), you want additional_wear to be negative. The formula used to calculate the resulting wear is: 65536 * (1 - ( (1 - tool_1_wear) + (1 - tool_2_wear) + additional_wear )) The result is rounded and can't be lower than 0. If the result is 65536 or higher, no crafting is possible.
+---@field cooktime number|integer For {type = "cooking"} only. (optional) Time it takes to cook this item, in seconds. A floating-point number. (default: 3.0)
+---@field burntime number|integer For {type = "fuel"} only. (optional) Burning time this item provides, in seconds. A floating-point number. (default: 1.0)
diff --git a/types/decoration.type.lua b/types/decoration.type.lua
new file mode 100644
index 0000000..501bd7c
--- /dev/null
+++ b/types/decoration.type.lua
@@ -0,0 +1,9 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---The varying types of decorations that can be placed.
+---@class DecorationDef
+---@field deco_type 'simple'|'schematic' simple: Creates a 1 times H times 1 column of a specified node (or a random node from a list, if a decoration list is specified). Can specify a certain node it must spawn next to, such as water or lava, for example. Can also generate a decoration of random height between a specified lower and upper bound. This type of decoration is intended for placement of grass, flowers, cacti, papyri, waterlilies and so on. schematic: Copies a box of MapNodes from a specified schematic file (or raw description). Can specify a probability of a node randomly appearing when placed. This decoration type is intended to be used for multi-node sized discrete structures, such as trees, cave spikes, rocks, and so on.
+---@field biomes any List of biomes in which this decoration occurs. Occurs in all biomes if this is omitted, and ignored if the Mapgen being used does not support biomes. Can be a list of (or a single) biome names, IDs, or definitions.
+---@field decoration string| string[] The node name used as the decoration. If instead a list of strings, a randomly selected node from the list is placed as the decoration.
+---@field place_on string| string[] Node (or list of nodes) that the decoration can be placed on
diff --git a/types/entity.type.lua b/types/entity.type.lua
new file mode 100644
index 0000000..37e8092
--- /dev/null
+++ b/types/entity.type.lua
@@ -0,0 +1,17 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---Entity definition
+---@class EntityDef
+---@field initial_properties ObjectProperties A table of object properties. The properties in this table are applied to the object once when it is spawned. dtime_s is the time passed since the object was unloaded, which can be used for updating the entity state.
+---@field on_activate fun(self: table, staticdata: string, dtime_s: integer|number): nil Function receive a "luaentity" table as self. Called when the object is instantiated.
+---@field on_deactivate fun(self: table, removal: boolean): nil Function receive a "luaentity" table as self. Called when the object is about to get removed or unloaded. removal: boolean indicating whether the object is about to get removed. Calling object:remove() on an active object will call this with removal=true. The mapblock the entity resides in being unloaded will call this with removal=false. Note that this won't be called if the object hasn't been activated in the first place. In particular, minetest.clear_objects({mode = "full"}) won't call this, whereas minetest.clear_objects({mode = "quick"}) might call this.
+---@field on_step fun(self: table, dtime: integer|number, moveresult?: table): nil Function receive a "luaentity" table as self. Called on every server tick, after movement and collision processing. dtime: elapsed time since last call. moveresult: table with collision info (only available if physical=true).
+---@field on_punch fun(self: table, puncher: ObjectRef|nil, time_from_last_punch: number|integer|nil, tool_capabilities: ToolCapabilitiesDef|nil, dir: Vector, damage: number|integer): boolean|nil Function receive a "luaentity" table as self. Called when somebody punches the object. Note that you probably want to handle most punches using the automatic armor group system. Can return true to prevent the default damage mechanism.
+---@field on_death fun(self: table, killer: ObjectRef|nil): nil Function receive a "luaentity" table as self. Called when the object dies.
+---@field on_rightclick fun(self: table, clicker: ObjectRef): nil Function receive a "luaentity" table as self. Called when clicker pressed the 'place/use' key while pointing to the object (not neccessarily an actual rightclick). clicker: an ObjectRef (may or may not be a player)
+---@field on_attach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as self. child: an ObjectRef of the child that attaches
+---@field on_detach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as self. child: an ObjectRef of the child that detaches
+---@field on_detach fun(self: table, parent: ObjectRef|nil): nil Function receive a "luaentity" table as self. parent: an ObjectRef (can be nil) from where it got detached. This happens before the parent object is removed from the world.
+---@field get_staticdata fun(self: table) Function receive a "luaentity" table as self. Should return a string that will be passed to on_activate when the object is instantiated the next time.
+---@field drops table Custom for mob drops
diff --git a/types/generic.type.lua b/types/generic.type.lua
new file mode 100644
index 0000000..97aba40
--- /dev/null
+++ b/types/generic.type.lua
@@ -0,0 +1,4 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---@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
new file mode 100644
index 0000000..6a6dd6b
--- /dev/null
+++ b/types/inventory.type.lua
@@ -0,0 +1,21 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+--An InvRef is a reference to an inventory.
+---@class InvRef
+---@field add_item fun(self: InvRef, listname: string, stack: string|ItemStack): ItemStack Add item somewhere in list, returns leftover ItemStack.
+---@field contains_item fun(self: InvRef, listname: string, stack: string|ItemStack, match_meta?: boolean): boolean Returns true if the stack of items can be fully taken from the list. If match_meta is false, only the items' names are compared, default: false
+---@field get_list fun(self: InvRef, listname: string): ItemStack[] Return full list, list of ItemStacks
+---@field room_for_item fun(self: InvRef, listname: string, stack: string|ItemStack): boolean Returns true if the stack of items can be fully added to the list
+---@field set_stack fun(self: InvRef, listname: string, i: integer, stack: string|ItemStack): nil Copy stack to index i in list
+---@field is_empty fun(self: InvRef, listname: string): boolean Return true if list is empty
+---@field get_size fun(self: InvRef, listname: string): integer Get size of a list
+---@field set_size fun(self: InvRef, listname: string, size: integer): boolean Set size of a list, returns false on error, e.g. invalid listname or size
+---@field get_width fun(self: InvRef, listname: string): boolean Get width of a list
+---@field set_width fun(self: InvRef, listname: string, width: integer): nil Set width of list; currently used for crafting
+---@field get_stack fun(self: InvRef, listname: string, i: integer): ItemStack Get a copy of stack index i in list
+---@field set_list fun(self: InvRef, listname: string, list: ItemStack[]): nil Set full list, size will not change
+---@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(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/item.type.lua b/types/item.type.lua
new file mode 100644
index 0000000..0ec0b11
--- /dev/null
+++ b/types/item.type.lua
@@ -0,0 +1,55 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---Minetest item definition. Used by minetest.register_node, minetest.register_craftitem, and minetest.register_tool.
+---Add your own custom fields. By convention, all custom field names. Should start with _ to avoid naming collisions with future engine usage.
+---@class ItemDef
+---@field description string Can contain new lines. "\n" has to be used as new line character.
+---@field short_description string|nil Must not contain new lines. Defaults to nil.
+---@field groups table<string, string|number|integer|boolean> key = name, value = rating; rating = . If rating not applicable, use 1. e.g. {wool = 1, fluffy = 3} {soil = 2, outerspace = 1, crumbly = 1} {bendy = 2, snappy = 1} {hard = 1, metal = 1, spikes = 1}
+---@field inventory_image string Texture shown in the inventory GUI. Defaults to a 3D rendering of the node if left empty.
+---@field inventory_overlay string An overlay texture which is not affected by colorization
+---@field wield_image string Texture shown when item is held in hand. Defaults to a 3D rendering of the node if left empty.
+---@field wield_overlay string Like inventory_overlay but only used in the same situation as wield_image
+---@field wield_scale table<string, number|integer> Scale for the item when held in hand
+---@field palette string An image file containing the palette of a node. You can set the currently used color as the "palette_index" field of the item stack metadata. The palette is always stretched to fit indices between 0 and 255, to ensure compatibility with "colorfacedir" (and similar) nodes.
+---@field color string Color the item is colorized with. The palette overrides this.
+---@field stack_max integer|number Maximum amount of items that can be in a single stack.
+---@field range integer|number Range of node and object pointing that is possible with this item held.
+---@field liquids_pointable boolean If true, item can point to all liquid nodes (liquidtype ~= "none"), even those for which pointable = false
+---@field light_source integer|number When used for nodes: Defines amount of light emitted by node. Otherwise: Defines texture glow when viewed as a dropped item. To set the maximum (14), use the value 'minetest.LIGHT_MAX'. A value outside the range 0 to minetest.LIGHT_MAX causes undefined behavior.
+---@field tool_capabilities ToolCapabilitiesDef
+---@field node_placement_prediction string|nil If nil and item is node, prediction is made automatically. If nil and item is not a node, no prediction is made. If "" and item is anything, no prediction is made. Otherwise should be name of node which the client immediately places on ground when the player places the item. Server will always update with actual result shortly.
+---@field node_dig_prediction string if "", no prediction is made. if "air", node is removed. Otherwise should be name of node which the client immediately places upon digging. Server will always update with actual result shortly.
+---@field sound ItemSoundDef
+---@field on_place fun(itemstack: ItemStack, placer: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil When the 'place' key was pressed with the item in hand and a node was pointed at. Shall place item and return the leftover itemstack or nil to not modify the inventory. The placer may be any ObjectRef or nil. default: minetest.item_place
+---@field on_secondary_use fun(itemstack: ItemStack, user: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil Same as on_place but called when not pointing at a node. Function must return either nil if inventory shall not be modified, or an itemstack to replace the original itemstack. The user may be any ObjectRef or nil. default: nil
+---@field on_drop fun(itemstack: ItemStack, dropper: ObjectRef|nil, pos: Vector): ItemStack|nil Shall drop item and return the leftover itemstack. The dropper may be any ObjectRef or nil. default: minetest.item_drop
+---@field on_pickup fun(itemstack: ItemStack, picker: ObjectRef|nil, pointed_thing?: PointedThingDef, time_from_last_punch?: number|integer, rest?: any): ItemStack|nil Called when a dropped item is punched by a player. Shall pick-up the item and return the leftover itemstack or nil to not modify the dropped item. rest are other parameters from luaentity:on_punch. default: minetest.item_pickup
+---@field on_use fun(itemstack: ItemStack, user: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil default: nil. When user pressed the 'punch/mine' key with the item in hand. Function must return either nil if inventory shall not be modified, or an itemstack to replace the original itemstack. e.g. itemstack:take_item(); return itemstack. Otherwise, the function is free to do what it wants. The user may be any ObjectRef or nil. The default functions handle regular use cases.
+---@field after_use fun(itemstack: ItemStack, user: ObjectRef|nil, node: NodeDef, digparams: DigParamsDef): ItemStack|nil default: nil. If defined, should return an itemstack and will be called instead of wearing out the item (if tool). If returns nil, does nothing.
+---@field soil table Only for farming
+
+---Tool capabilities definition
+---@class ToolCapabilitiesDef
+---@field full_punch_interval number|integer
+---@field max_drop_level number|integer
+---@field groupcaps GroupCapsDef
+---@field damage_groups table<string, number|integer> Damage values must be between -32768 and 32767 (2^15)
+---@field punch_attack_uses number|integer|nil Amount of uses this tool has for attacking players and entities by punching them (0 = infinite uses). For compatibility, this is automatically set from the first suitable groupcap using the forumla "uses * 3^(maxlevel - 1)". It is recommend to set this explicitly instead of relying on the fallback behavior.
+
+---Known damage and digging time defining groups
+---@class GroupCapsDef
+---@field crumbly number|GroupCapsItemDef dirt, sand
+---@field cracky number|GroupCapsItemDef tough but crackable stuff like stone.
+---@field snappy number|GroupCapsItemDef something that can be cut using things like scissors, shears, bolt cutters and the like, e.g. leaves, small plants, wire, sheets of metal
+---@field choppy number|GroupCapsItemDef something that can be cut using force; e.g. trees, wooden planks
+---@field fleshy number|GroupCapsItemDef Living things like animals and the player. This could imply some blood effects when hitting.
+---@field explody number|GroupCapsItemDef Especially prone to explosions
+---@field oddly_breakable_by_hand number|GroupCapsItemDef Can be added to nodes that shouldn't logically be breakable by the hand but are. Somewhat similar to dig_immediate, but times are more like {[1]=3.50,[2]=2.00,[3]=0.70} and this does not override the digging speed of an item if it can dig at a faster speed than this suggests for the hand.
+
+---Known damage and digging time defining groups
+---@class GroupCapsItemDef
+---@field maxlevel number|integer Tells what is the maximum level of a node of this group that the item will be able to dig.
+---@field uses number|integer Tools only. Determines how many uses the tool has when it is used for digging a node, of this group, of the maximum level. The maximum supported number of uses is 65535. The special number 0 is used for infinite uses. For lower leveled nodes, the use count is multiplied by 3^leveldiff. leveldiff is the difference of the tool's maxlevel groupcaps and the node's level group. The node cannot be dug if leveldiff is less than zero.
+---@field times table<number|integer, number|integer> List of digging times for different ratings of the group, for nodes of the maximum level. For example, as a Lua table, times={[2]=2.00, [3]=0.70}. This would result in the item to be able to dig nodes that have a rating of 2 or 3 for this group, and unable to dig the rating 1, which is the toughest. Unless there is a matching group that enables digging otherwise. If the result digging time is 0, a delay of 0.15 seconds is added between digging nodes; If the player releases LMB after digging, this delay is set to 0, i.e. players can more quickly click the nodes away instead of holding LMB.
diff --git a/types/itemstack.type.lua b/types/itemstack.type.lua
new file mode 100644
index 0000000..e8b1e1b
--- /dev/null
+++ b/types/itemstack.type.lua
@@ -0,0 +1,36 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---A native C++ format with many helper methods. Useful for converting between formats.
+---An ItemStack is a stack of items.
+---It can be created via ItemStack(x), where x is an ItemStack, an itemstring, a table or nil.
+---@class ItemStack
+---@field is_empty fun(): boolean Returns true if stack is empty.
+---@field get_name fun(): string returns item name (e.g. "default:stone").
+---@field set_name fun(self: ItemStack, item_name: string): boolean Returns a boolean indicating whether the item was cleared.
+---@field get_count fun(): integer Returns number of items on the stack.
+---@field set_count fun(self: ItemStack, count: integer): boolean Returns a boolean indicating whether the item was cleared
+---@field get_wear fun(): integer Returns tool wear (0-65535), 0 for non-tools.
+---@field set_wear fun(self: ItemStack, wear: integer): boolean Returns boolean indicating whether item was cleared
+---@field get_meta fun(): ItemStackMetaRef Returns ItemStackMetaRef.
+---@field get_description fun(): string Returns the description shown in inventory list tooltips. The engine uses this when showing item descriptions in tooltips. Fields for finding the description, in order: description in item metadata.
+---@field get_short_description fun(): string|nil Returns the short description or nil. Unlike the description, this does not include new lines. Fields for finding the short description, in order: short_description in item metadata. Returns nil if none of the above are set.
+---@field clear fun(): nil Removes all items from the stack, making it empty.
+---@field replace fun(self: ItemStack, item: string|table): replace the contents of this stack. itemcan also be an itemstring or table. +---@field to_string fun(): string Returns the stack in itemstring form. +---@field to_table fun(): table Returns the stack in Lua table form. +---@field get_stack_max fun(): integer Returns the maximum size of the stack (depends on the item). +---@field get_free_space fun(): integer Returnsget_stack_max() - get_count(). +---@field is_known fun(): boolean Returns trueif the item name refers to a defined item type. +---@field get_definition fun(): table Returns the item definition table. +---@field get_tool_capabilities fun(): table Returns the digging properties of the item, or those of the hand if none are defined for this item type +---@field add_wear fun(self: ItemStack, amount: integer|number): nil Increases wear byamountif the item is a tool, otherwise does nothing. Validamountrange is [0,65536]amount: number, integer +---@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_usestimes. Validmax_usesrange is [0,65536] Does nothing if item is not a tool or ifmax_usesis 0 +---@field add_item fun(self: ItemStack, item: string|table): ItemStack Returns leftoverItemStackPut some item or stack onto this stack +---@field item_fits fun(self: ItemStack, item: string|table): boolean Returnstrueif item or stack can be fully added to this one. +---@field take_item fun(self: ItemStack, n?: integer|number): ItemStack Returns takenItemStackTake (and remove) up tonitems from this stackn: number, default: 1+---@field peek_item fun(self: ItemStack, n: integer|number): ItemStack Returns takenItemStackCopy (don't remove) up tonitems from this stackn: number, default: 1+---@field name string +---@field count integer +---@field wear string +---@field metadata string diff --git a/types/mapgen-aliases.type.lua b/types/mapgen-aliases.type.lua new file mode 100644 index 0000000..c955426 --- /dev/null +++ b/types/mapgen-aliases.type.lua @@ -0,0 +1,39 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +--- In a game, a certain number of these must be set to tell core mapgens which of the game's nodes are to be used for core mapgen generation. +---@alias MapgenAliasesNonV6 +---| '"mapgen_stone"' +---| '"mapgen_water_source"' +---| '"mapgen_river_water_source"' +---| '"mapgen_river_water_source"' # is required for mapgens with sloping rivers where it is necessary to have a river liquid node with a shortliquid_rangeandliquid_renewable = falseto avoid flooding. +---| '"mapgen_lava_source"' # Optional, Fallback lava node used if cave liquids are not defined in biome definitions. Deprecated, define cave liquids in biome definitions instead. +---| '"mapgen_cobble"' # Optional, Fallback node used if dungeon nodes are not defined in biome definitions. Deprecated, define dungeon nodes in biome definitions instead. + +--- In a game, a certain number of these must be set to tell core mapgens which of the game's nodes are to be used for core mapgen generation. +---@alias MapgenAliasesV6 +---| '"mapgen_stone"' +---| '"mapgen_water_source"' +---| '"mapgen_lava_source"' +---| '"mapgen_dirt"' +---| '"mapgen_dirt_with_grass"' +---| '"mapgen_sand"' +---| '"mapgen_tree"' +---| '"mapgen_leaves"' +---| '"mapgen_apple"' +---| '"mapgen_cobble"' +---| '"mapgen_gravel"' # Optional, (falls back to stone) +---| '"mapgen_desert_stone"' # Optional, (falls back to stone) +---| '"mapgen_desert_sand"' # Optional, (falls back to sand) +---| '"mapgen_dirt_with_snow"' # Optional, (falls back to dirt_with_grass) +---| '"mapgen_snowblock"' # Optional, (falls back to dirt_with_grass) +---| '"mapgen_snow"' # Optional, (not placed if missing) +---| '"mapgen_ice"' # Optional, (falls back to water_source) +---| '"mapgen_jungletree"' # Optional, (falls back to tree) +---| '"mapgen_jungleleaves"' # Optional, (falls back to leaves) +---| '"mapgen_junglegrass"' # Optional, (not placed if missing) +---| '"mapgen_pine_tree"' # Optional, (falls back to tree) +---| '"mapgen_pine_needles"' # Optional, (falls back to leaves) +---| '"mapgen_stair_cobble"' # Optional, (falls back to cobble) +---| '"mapgen_mossycobble"' # Optional, (falls back to cobble) +---| '"mapgen_stair_desert_stone"' # Optional, (falls backto desert_stone) diff --git a/types/math.type.lua b/types/math.type.lua new file mode 100644 index 0000000..c0078f9 --- /dev/null +++ b/types/math.type.lua @@ -0,0 +1,8 @@ +---@diagnostic disable: codestyle-check, duplicate-doc-alias +---https://github.com/sumneko/lua-language-server/wiki + +---@alias mathlib mathlib|MathAbstract + +---Math helpers +---@class MathAbstract +---@field round fun(x: number): number Returnsx rounded to the nearest integer. At a multiple of 0.5, rounds away from zero. diff --git a/types/meta.type.lua b/types/meta.type.lua new file mode 100644 index 0000000..1a9e54e --- /dev/null +++ b/types/meta.type.lua @@ -0,0 +1,36 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias ItemStackMetaRef MetaDataRef|ItemStackMetaRefAbstract +---@alias NodeMetaRef MetaDataRef|NodeMetaRefAbstract +---@alias PlayerMetaRef MetaDataRef|PlayerMetaRefAbstract +---@alias StorageRef MetaDataRef|StorageRefAbstract + +---Base class used by [StorageRef], [NodeMetaRef], [ItemStackMetaRef], and [PlayerMetaRef]. +---@class MetaDataRef +---@field contains fun(self: MetaDataRef, key: string): boolean|nil Returns true if key present, otherwise false. Returns nilwhen the MetaData is inexistent. +---@field get fun(self: MetaDataRef, key: string): string|nil Returnsnilif key not present, else the stored string. +---@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): integer|number Returns0if key not present. +---@field set_float fun(self: MetaDataRef, key: string, value: number): nil +---@field get_float fun(self: MetaDataRef, key): integer|number Returns0if key not present. +---@field to_table fun(): nil Returnsnilor a table with keys:fields: key-value storage inventory: {list1 = {}, ...}}(NodeMetaRef only) +---@field from_table fun(self: MetaDataRef, t: nil|table): boolean Any non-table value will clear the metadata. Returnstrueon success +---@field equals fun(self: MetaDataRef, other: any): boolean Returnstrueif this metadata has the same key-value pairs asother+ +---ItemStack metadata: reference extra data and functionality stored in a stack. Can be obtained viaitem:get_meta(). +---@class ItemStackMetaRefAbstract +---@field set_tool_capabilities fun(self: ItemStackMetaRef, tool_capabilities?: table): nil Overrides the item's tool capabilities. A nil value will clear the override data and restore the original behavior. + +---Node metadata: reference extra data and functionality stored in a node. Can be obtained via minetest.get_meta(pos). +---@class NodeMetaRefAbstract +---@field get_inventory fun(self: NodeMetaRef): InvRef +---@field mark_as_private fun(self: NodeMetaRef, name: string | table<string[]>) Mark specific vars as private This will prevent them from being sent to the client. Note that the "private" status will only be remembered if an associated key-value pair exists, meaning it's best to call this when initializing all other meta (e.g. on_construct). + +---Player metadata. Uses the same method of storage as the deprecated player attribute API, so data there will also be in player meta. Can be obtained using player:get_meta(). +---@class PlayerMetaRefAbstract + +---Mod metadata: per mod metadata, saved automatically. Can be obtained via minetest.get_mod_storage()during load time. WARNING: This storage backend is incapable of saving raw binary data due to restrictions of JSON. +---@class StorageRefAbstract diff --git a/types/minetest.type.lua b/types/minetest.type.lua new file mode 100644 index 0000000..1f9e885 --- /dev/null +++ b/types/minetest.type.lua @@ -0,0 +1,168 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest globals +---@class Minetest +---@field item_drop fun(itemstack: string|ItemStack, dropper: ObjectRef, pos: Vector): ItemStack Drop the item, returns the leftover itemstack +---@field get_us_time fun(): integer|number Returns time with microsecond precision. May not return wall time. +---@field get_modpath fun(modname: string): string|nil Returns the directory path for a mod, e.g."/home/user/.minetest/usermods/modname". Returns nil if the mod is not enabled or does not exist (not installed). Works regardless of whether the mod has been loaded yet. Useful for loading additional .luamodules or static data from a mod, or checking if a mod is enabled. +---@field check_player_privs fun(player_or_name: ObjectRef|string, privs: table|string[]): boolean Returnsbool, missing_privs. A quickhand for checking privileges. player_or_name: Either a Player object or the name of a player. privsis either a list of strings, e.g."priva", "privb"or a table, e.g.{ priva = true, privb = true }. +---@field register_on_joinplayer fun(f: fun(player: ObjectRef, last_login: number|integer|nil)): nil Called when a player joins the game. last_login: The timestamp of the previous login, or nil if player is new +---@field register_tool fun(name: string, item_definition: ItemDef): nil Registers the item in the engine +---@field colorize fun(color: string, message: string): nil +---@field register_craft fun(recipe: CraftRecipeDef): nil +---@field register_craftitem fun(name: string, item_definition: ItemDef): nil +---@field add_entity fun(pos: Vector, name: string, staticdata?: string): ObjectRef|nil Spawn Lua-defined entity at position. Returns ObjectRef, or nilif failed. +---@field get_node fun(pos: Vector): NodeDef Returns the node at the given position as table in the format{name="node_name", param1=0, param2=0}, returns {name="ignore", param1=0, param2=0}for unloaded areas. +---@field registered_nodes table<string, NodeDef|ItemDef> Map of registered node definitions, indexed by name +---@field after fun(time: number|integer, func: fun(...), ...): JobTable Call the functionfuncaftertimeseconds, may be fractional. Optional: Variable number of arguments that are passed tofunc. +---@field sound_play fun(spec: SimpleSoundSpec|string, parameters: SoundParamDef, ephemeral?: boolean): any Returns a handle. Ephemeral sounds will not return a handle and can't be stopped or faded. It is recommend to use this for short sounds that happen in response to player actions (e.g. door closing). +---@field add_particlespawner fun(particlespawner_definition: ParticlespawnerDef): number|integer Add a ParticleSpawner, an object that spawns an amount of particles over timeseconds. Returns anid, and -1 if adding didn't succeed. +---@field register_globalstep fun(func: fun(dtime: number|integer)): nil Called every server step, usually interval of 0.1s +---@field get_connected_players fun(): ObjectRef[] Returns list of ObjectRefs+---@field serialize fun(t: table): string Convert a table containing tables, strings, numbers, booleans andnils into string form readable by minetest.deserialize. Example: serialize({foo="bar"}), returns 'return { ["foo"] = "bar" }'. +---@field dir_to_yaw fun(dir: Vector): number|integer Convert a vector into a yaw (angle) +---@field settings MinetestSettings Settings object containing all of the settings from the main config file (minetest.conf). +---@field register_entity fun(name: string, entity_definition: EntityDef): nil +---@field deserialize fun(s: string, safe?: boolean): table Returns a table. Convert a string returned by minetest.serializeinto a tablestringis loaded in an empty sandbox environment. Will load functions if safe is false or omitted. Although these functions cannot directly access the global environment, they could bypass this restriction with maliciously crafted Lua bytecode if mod security is disabled. This function should not be used on untrusted data, regardless of the value ofsafe. It is fine to serialize then deserialize user-provided data, but directly providing user input to deserialize is always unsafe. +---@field raycast fun(pos1: Vector, pos2: Vector, objects: boolean, liquids: boolean): Raycast pos1: start of the ray, pos2: end of the ray, objects: if false, only nodes will be returned. Default is true. liquids: if false, liquid nodes (liquidtype ~= "none") won't be returned. Default is false. +---@field calculate_knockback fun(player: ObjectRef, hitter: ObjectRef, time_from_last_punch: number|integer, tool_capabilities: ToolCapabilitiesDef, dir: Vector, distance: number|integer, damage: number|integer): integer|number Returns the amount of knockback applied on the punched player. Arguments are equivalent to register_on_punchplayer, except the following: distance: distance between puncher and punched player. This function can be overriden by mods that wish to modify this behaviour. You may want to cache and call the old function to allow multiple mods to change knockback behaviour. +---@field get_player_by_name fun(name: string): ObjectRef Get an ObjectRefto a player +---@field get_node_timer fun(pos: Vector): NodeTimerRef GetNodeTimerRef+---@field get_objects_inside_radius fun(pos: Vector, radius: number|integer): ObjectRef[] Returns a list of ObjectRefs.radius: using an euclidean metric. +---@field register_node fun(name: string, node_definition: NodeDef): nil +---@field get_meta fun(pos: Vector): NodeMetaRef Get a NodeMetaRefat that position +---@field pos_to_string fun(pos: Vector, decimal_places?: number|integer): string returns string"(X,Y,Z)", pos: table {x=X, y=Y, z=Z}. Converts the position posto a human-readable, printable string.decimal_places: number, if specified, the x, y and z values of the position are rounded to the given decimal place. +---@field get_node_light fun(pos: Vector, timeofday: number|integer|nil): number|integer|nil Gets the light value at the given position. Note that the light value "inside" the node at the given position is returned, so you usually want to get the light value of a neighbor. pos: The position where to measure the light. timeofday: nilfor current time,0for night,0.5for day. Returns a number between0and15ornil. nilis returned e.g. when the map isn't loaded atpos. +---@field set_node fun(pos: Vector, node: SetNodeTable): nil 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 place_schematic fun(pos: Vector, schematic, rotation?: '0'|'90'|'180'|'270'|'random', replacements?: table<string, string>, force_placement?: boolean, flags?: string): nil Place the schematic specified by schematic atpos. rotationcan equal"0", "90", "180", "270", or "random". If the rotationparameter is omitted, the schematic is not rotated.replacements={["old_name"] = "convert_to", ...}. force_placementis a boolean indicating whether nodes other thanairandignoreare replaced by the schematic. Returns nil if the schematic could not be loaded. **Warning**: Once you have loaded a schematic from a file, it will be cached. Future calls will always use the cached version and the replacement list defined for it, regardless of whether the file or the replacement list parameter have changed. The only way to load the file anew is to restart the server.flags is a flag field with the available flags: place_center_x, place_center_y, place_center_z +---@field log fun(level?: 'none'|'error'|'warning'|'action'|'info'|'verbose', text: string): nil +---@field get_item_group fun(name: string, group): any returns a rating. Get rating of a group of an item. (0means: not in group) +---@field get_biome_data fun(pos: Vector): BiomeData|nil +---@field get_biome_name fun(biome_id: string|number|integer): string|nil Returns the biome name string for the provided biome id, ornilon failure. If no biomes have been registered, such as in mgv6, returnsdefault. +---@field find_nodes_in_area fun(pos1: Vector, pos2: Vector, nodenames: string|string[], grouped?: boolean): Vector[] pos1andpos2are the min and max positions of the area to search.nodenames: e.g. {"ignore", "group:tree"}or"default:dirt"Ifgroupedis true the return value is a table indexed by node name which contains lists of positions. Ifgroupedis false or absent the return values are as follows: first value: Table with all node positions, second value: Table with the count of each node with the node name as index, Area volume is limited to 4,096,000 nodes +---@field find_nodes_in_area_under_air fun(pos1: Vector, pos2: Vector, nodenames: string|string[]): table returns a list of positions.nodenames: e.g. {"ignore", "group:tree"}or"default:dirt". Return value: Table with all node positions with a node air above. Area volume is limited to 4,096,000 nodes. +---@field registered_decorations table<any, DecorationDef> Map of registered decoration definitions, indexed by the namefield. Ifnameis nil, the key is the object handle returned byminetest.register_schematic. +---@field swap_node fun(pos: Vector, node: NodeDef): nil Set node at position, but don't remove metadata +---@field item_eat fun(hp_change: number, replace_with_item?: string): fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef) Returns function(itemstack, user, pointed_thing)as a function wrapper forminetest.do_item_eat. replace_with_itemis the itemstring which is added to the inventory. If the player is eating a stack, then replace_with_item goes to a different spot. +---@field override_item fun(name: string, redefinition: ItemDef|NodeDef): nil Overrides fields of an item registered with register_node/tool/craftitem. Note: Item must already be defined, (opt)depend on the mod defining it. Example:minetest.override_item("default:mese", {light_source=minetest.LIGHT_MAX})+---@field register_decoration fun(decoration_definition: DecorationDef): number|integer Returns an integer object handle uniquely identifying the registered decoration on success. To get the decoration ID, useminetest.get_decoration_id. The order of decoration registrations determines the order of decoration generation. +---@field find_node_near fun(pos: Vector, radius: number, nodenames: string[], search_center?: boolean): Vector|nil returns pos or nil. radius: using a maximum metric, nodenames: e.g. {"ignore", "group:tree"}or"default:dirt", search_centeris an optional boolean (default:false) If true posis also checked for the nodes +---@field remove_node fun(pos: Vector): nil By default it does the same asminetest.set_node(pos, {name="air"})+---@field get_node_or_nil fun(pos: Vector): NodeDef|nil Same asget_nodebut returnsnilfor unloaded areas. +---@field facedir_to_dir fun(facedir: number): Vector Convert a facedir back into a vector aimed directly out the "back" of a node. +---@field record_protection_violation fun(pos: Vector, name: string): nil This function calls functions registered withminetest.register_on_protection_violation. +---@field dir_to_facedir fun(dir: Vector, is6d?: any): number Convert a vector to a facedir value, used in param2forparamtype2="facedir". passing something non-nil/falsefor the optional second parameter causes it to take the y component into account. +---@field register_lbm fun(lbm_definition: LbmDef): nil +---@field rotate_node fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef): nil callsrotate_and_place()withinfinitestacksset according to the state of the creative mode setting, checks for "sneak" to set theinvert_wallparameter andprevent_after_placeset totrue. +---@field global_exists fun(name: string): nil Checks if a global variable has been set, without triggering a warning. +---@field register_alias fun(alias: string|MapgenAliasesV6|MapgenAliasesNonV6, original_name: string): nil Also use this to set the 'mapgen aliases' needed in a game for the core mapgens. See [Mapgen aliases] section above. +---@field register_alias_force fun(alias: string|MapgenAliasesV6|MapgenAliasesNonV6, original_name: string): nil +---@field add_item fun(pos: Vector, item: ItemStack): ObjectRef|nil Spawn item. Returns ObjectRef, or nilif failed. +---@field registered_items table<string, ItemDef> Map of registered items, indexed by name +---@field add_node fun(pos: Vector, node: SetNodeTable): nil alias tominetest.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): nilplayername: name of player to show formspec, formname: name passed to on_player_receive_fieldscallbacks. 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 playerin a formspec with the givenformname. 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 +---@field dir_to_wallmounted fun(dir: Vector): number Convert a vector to a wallmounted value, used for paramtype2="wallmounted"+---@field item_place_node fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, param2?: string , prevent_after_place?: boolean): Vector|nil Place item as a node,param2overridesfacedirand wallmountedparam2, prevent_after_place: if set to true, after_place_nodeis not called or the newly placed node to prevent a callback and placement loop. returnsitemstack, position, position: the location the node was placed to. nilif nothing was placed. +---@field unregister_item fun(name: string): nil Unregisters the item from the engine, and deletes the entry with keynamefromminetest.registered_itemsand from the associated item table according to its nature:minetest.registered_nodes, etc. +---@field register_allow_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): number): nil Determines how much of a stack may be taken, put or moved to a player inventory. player(typeObjectRef) is the player who modified the inventory, inventory(typeInvRef). List of possible action(string) values and theirinventory_info(table) contents:move: {from_list=string, to_list=string, from_index=number, to_index=number, count=number}, put: {listname=string, index=number, stack=ItemStack}, take: Same as put. Return a numeric value to limit the amount of items to be taken, put or moved. A value of -1fortakewill make the source stack infinite. +---@field register_on_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): nil): nil Called after a take, put or move event from/to/in a player inventory. Function arguments: seeminetest.register_allow_player_inventory_action. Does not accept or handle any return value. +---@field formspec_escape fun(str: string): string returns a string, escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs. +---@field get_translator fun(textdomain: string): any +---@field get_current_modname fun(): string returns the currently loading mod's name, when loading a mod. +---@field get_pointed_thing_position fun(pointed_thing: PointedThingDef, above?: boolean): Vector | nil Returns the position of a pointed_thingornilif thepointed_thingdoes not refer to a node or entity. If the optionalaboveparameter is true and thepointed_thingrefers to a node, then it will return theaboveposition of thepointed_thing. +---@field item_place fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, param2?: string|number): ItemStack Wrapper that calls minetest.item_place_nodeif appropriate. Callson_rightclickofpointed_thing.underif defined instead **Note**: is not called when wielded item overrideson_place, param2overrides facedir and wallmountedparam2, returns itemstack, position, position: the location the node was placed to. nilif nothing was placed. +---@field node_dig fun(pos: Vector, node: NodeDef, digger: ObjectRef): nil Checks if node can be dug, puts item into inventory, removes node. Calls functions registered byminetest.registered_on_dignodes()+---@field delete_particlespawner fun(id: number, player?: ObjectRef): nil DeleteParticleSpawnerwithid(return value fromminetest.add_particlespawner). If playername is specified, only deletes on the player's client, otherwise on all clients. +---@field is_area_protected fun(pos1: Vector, pos2: Vector, player_name: ObjectRef, interval: number): Vector | boolean Returns the position of the first node that player_namemay not modify in the specified cuboid betweenpos1andpos2. Returns falseif no protections were found. Appliesis_protected()to a 3D lattice of points in the defined volume. The points are spaced evenly throughout the volume and have a spacing similar to, but no larger than,interval. All corners and edges of the defined volume are checked. intervaldefaults to 4.intervalshould be carefully chosen and maximised to avoid an excessive number of points being checked. Likeminetest.is_protected, this function may be extended or overwritten by mods to provide a faster implementation to check the cuboid for intersections. +---@field is_protected fun(pos: Vector, name: string): boolean Returning truerestricts the playernamefrom modifying (i.e. digging, placing) the node at positionpos. namewill be""for non-players or unknown players. This function should be overridden by protection mods. It is highly recommended to grant access to players with theprotection_bypassprivilege. Cache and call the old version of this function if the position is not protected by the mod. This will allow using multiple protection mods. +---@field register_on_mods_loaded fun(func: fun(): nil): nil Called after mods have finished loading and before the media is cached or the aliases handled. +---@field register_on_leaveplayer fun(func: fun(player: ObjectRef, timed_out: number): nil): nil Called when a player leaves the gametimed_out: True for timeout, false for other reasons. +---@field place_node fun(pos: Vector, node: SetNodeTable): nil Place node with the same effects that a player would cause +---@field add_particle fun(def: ParticleDef): nil + +---Minetest settings +---@class MinetestSettings +---@field get fun(self: MinetestSettings, key: string): string|number|integer Returns a value +---@field get_bool fun(self: MinetestSettings, key: string, default?: boolean): boolean|nil Returns a boolean. defaultis the value returned ifkeyis not found. Returnsnilifkeyis not found anddefaultnot specified. +---@field get_np_group fun(self: MinetestSettings, key: string): table Returns a NoiseParams table +---@field get_flags fun(self: MinetestSettings, key: string): table Returns{flag = true/false, ...}according to the set flags. Is currently limited to mapgen flagsmg_flagsand mapgen-specific flags likemgv5_spflags. +---@field set fun(self: MinetestSettings, key: string, value: string|integer|number): nil Setting names can't contain whitespace or any of ="{}#. Setting values can't contain the sequence \n""". Setting names starting with "secure." can't be set on the main settings object (minetest.settings). +---@field set_bool fun(self: MinetestSettings, key: string, value: boolean): nil Setting names can't contain whitespace or any of ="{}#. Setting values can't contain the sequence \n""". Setting names starting with "secure." can't be set on the main settings object (minetest.settings). +---@field set_np_group fun(self: MinetestSettings, key: string, value: table): nil value is a NoiseParams table. +---@field remove fun(self: MinetestSettings, key: string): boolean Returns a boolean (truefor success) +---@field get_names fun(): table Returns{key1,...} +---@field write fun(): boolean Returns a boolean (truefor success). Writes changes to file. +---@field to_table fun(): table Returns{[key1]=value1,...}+ +--- Set node table +---@class SetNodeTable +---@field name string +---@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 + +--- Biome data +---@class BiomeData +---@field biome string|number|integer the biome id of the biome at that position +---@field heat string|number|integer the heat at the position +---@field humidity string|number|integer the humidity at the position + +--- LBM (LoadingBlockModifier) definition. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined bynodenames) when a mapblock which contains such nodes gets activated (not loaded!) +---@class LbmDef +---@field label string Descriptive label for profiling purposes (optional). Definitions with identical labels will be listed as one. +---@field name string Identifier of the LBM, should follow the modname:<whatever> convention +---@field nodenames string[] List of node names to trigger the LBM on. Names of non-registered nodes and groups (as group:groupname) will work as well. +---@field run_at_every_load boolean Whether to run the LBM's action every time a block gets activated, and not only the first time the block gets activated after the LBM was introduced. +---@field action fun(pos: Vector, node: NodeDef): nil Function triggered for each qualifying node. + +--- Sound parameters. +--- Looped sounds must either be connected to an object or played locationless to one player using to_player = name. A positional sound will only be heard by players that are within max_hear_distanceof the sound position, at the start of the sound.exclude_player = namecan be applied to locationless, positional and object-bound sounds to exclude a single player from hearing them. +---@class SoundParamDef +---@field to_player string Name +---@field gain number|integer +---@field fade number|integer Change to a value > 0 to fade the sound in +---@field pitch number|integer +---@field loop boolean +---@field pos Vector +---@field max_hear_distance number|integer +---@field object ObjectRef +---@field exclude_player string Name + +---Partcile definition +---@class ParticleDef +---@field pos Vector +---@field velocity Vector +---@field acceleration Vector Spawn particle at pos with velocity and acceleration +---@field expirationtime number Disappears after expirationtime seconds +---@field size number Scales the visual size of the particle texture. Ifnodeis set, size can be set to 0 to spawn a randomly-sized particle (just like actual node dig particles). +---@field collisiondetection boolean If true collides withwalkablenodes and, depending on theobject_collisionfield, objects too. +---@field collision_removal boolean If true particle is removed when it collides. Requires collisiondetection = true to have any effect. +---@field object_collision boolean If true particle collides with objects that are defined asphysical = true,andcollide_with_objects = true,. Requires collisiondetection = true to have any effect. +---@field vertical boolean If true faces player using y axis only +---@field texture string The texture of the particle v5.6.0 and later: also supports the table format described in the following section +---@field playername string | nil Optional, if specified spawns particle only on the player's client +---@field animation TileAnimationDef | nil Optional, specifies how to animate the particle texture +---@field glow number | nil Optional, specify particle self-luminescence in darkness. Values 0-14. +---@field node {["name"]: string, ["param2"]: number} | nil Optional, if specified the particle will have the same appearance as node dig particles for the given node. textureandanimationwill be ignored if this is set. +---@field node_tile number | nil Optional, only valid in combination withnodeIf set to a valid number 1-6, specifies the tile from which the particle texture is picked. Otherwise, the default behavior is used. (currently: any random tile) +---@field drag Vector | nil v5.6.0 and later: Optional drag value, consult the following section +---@field bounce {["min"]: number, ["max"]: number, ["bias"]: number} | nil v5.6.0 and later: Optional bounce range, consult the following section diff --git a/types/mtg-creative.type.lua b/types/mtg-creative.type.lua new file mode 100644 index 0000000..4b4199d --- /dev/null +++ b/types/mtg-creative.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game creative mod +---@class MtgCreative +---@field is_enabled_for fun(name: string): boolean Returningtruemeans that Creative Mode is enabled for playername. namewill be""for non-players or if the player is unknown. By default, this function returnstrueif the settingcreative_modeistrueandfalseotherwise. diff --git a/types/mtg-default.type.lua b/types/mtg-default.type.lua new file mode 100644 index 0000000..0f10ba4 --- /dev/null +++ b/types/mtg-default.type.lua @@ -0,0 +1,40 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game default mod +---@class MtgDefault +---@field LIGHT_MAX number|integer The maximum light level. Maximum light to grow. +---@field can_grow fun(pos: Vector): boolean Grow trees from saplings +---@field grow_new_apple_tree fun(pos: Vector): nil +---@field grow_new_jungle_tree fun(pos: Vector): nil +---@field grow_new_emergent_jungle_tree fun(pos: Vector): nil +---@field grow_new_acacia_tree fun(pos: Vector): nil +---@field grow_new_aspen_tree fun(pos: Vector): nil +---@field grow_new_snowy_pine_tree fun(pos: Vector): nil +---@field grow_new_pine_tree fun(pos: Vector): nil +---@field grow_bush fun(pos: Vector): nil +---@field grow_acacia_bush fun(pos: Vector): nil +---@field grow_pine_bush fun(pos: Vector): nil +---@field grow_blueberry_bush fun(pos: Vector): nil +---@field grow_papyrus fun(pos: Vector, node: NodeDef): nil +---@field grow_large_cactus fun(pos: Vector, node: NodeDef): nil +---@field sapling_on_place fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, sapling_name: string, minp_relative: Vector, maxp_relative: Vector, interval: number): nil Sapling 'on place' function to check protection of node and resulting tree volume +---@field register_leafdecay fun(def: RegisterLeafdecayDef): nil +---@field after_place_leaves fun(pos: Vector, placer: ObjectRef, itemstack?: ItemStack, pointed_thing?: PointedThingDef): nil Prevent decay of placed leaves +---@field node_sound_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_stone_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_dirt_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_sand_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_wood_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_leaves_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_glass_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_metal_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_ice_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_gravel_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 +---@field trunks string[] +---@field leaves string[] +---@field radius number diff --git a/types/mtg-dungeon-loot.type.lua b/types/mtg-dungeon-loot.type.lua new file mode 100644 index 0000000..88faa2b --- /dev/null +++ b/types/mtg-dungeon-loot.type.lua @@ -0,0 +1,15 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game dungeon loot mod API +---@class MtgDungeonLoot +---@field register fun(loot_definition: MtgDungeonLootDef): nil Registers one or more loot items,defCan be a single loot_definition or a list of them. +---@field registered_loot table Table of all registered loot, not to be modified manually + +---Loot definition +---@class MtgDungeonLootDef +---@field name string +---@field chance number chance value from 0.0 to 1.0 that the item will appear in the chest when chosen, Due to an extra step in the selection process, 0.5 does not(!) mean that on average every second chest will have this item +---@field count number[]|nil table with minimum and maximum amounts of this item, optional, defaults to always single item +---@field y number[]|nil table with minimum and maximum heights this item can be found at, optional, defaults to no height restrictions +---@field types string[]|nil table with types of dungeons this item can be found in supported types: "normal" (the cobble/mossycobble one), "sandstone", "desert" and "ice", optional, defaults to no type restrictions diff --git a/types/mtg-farming.type.lua b/types/mtg-farming.type.lua new file mode 100644 index 0000000..62c6638 --- /dev/null +++ b/types/mtg-farming.type.lua @@ -0,0 +1,17 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game farming mod +---@class MtgFarming +---@field hoe_on_use fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef, uses: number): ItemStack | nil +---@field place_seed fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, plantname: string): ItemStack Seed placement +---@field grow_plant fun(pos: Vector, elapsed: number): nil +---@field register_plant fun(name: string, def: table): nil + +----Node definition. Used byminetest.register_node. +---@class NodeDefMtgFarming +---@field fertility string[]|nil Used in default farming mod, defines biome name list where plants can grow +---@field steps number How many steps the plant has to grow, until it can be harvested +---@field minlight number Minimum light to grow +---@field maxlight number Maximum light to grow +---@field on_timer fun(pos: Vector, elapsed: number): boolean default: nil, called by NodeTimers, see minetest.get_node_timer and NodeTimerRef. elapsed is the total time passed since the timer was started. return true to run the timer for another cycle with the same timeout value. diff --git a/types/mtg-player-api.type.lua b/types/mtg-player-api.type.lua new file mode 100644 index 0000000..f9a5cc3 --- /dev/null +++ b/types/mtg-player-api.type.lua @@ -0,0 +1,33 @@ +---@diagnostic disable: codestyle-check +---The player API can register player models and update the player's appearance. +---@class MtgPlayerApi +---@field globalstep fun(dtime: number, ...): nil The function called by the globalstep that controls player animations. You can override this to replace the globalstep with your own implementation. Receives all args that minetest.register_globalstep() passes +---@field register_model fun(name: string, def: MtgPlayerApiModelDef): nil Register a new model to be used by players, name: model filename such as "character.x", "foo.b3d", etc., def: see [#Model definition] Saved to player_api.registered_models +---@field registered_models string[] Get a model's definition, name: model filename See [#Model definition] +---@field set_model fun(player: ObjectRef, model_name: string): nil Change a player's model, player: PlayerRef, model_name: model registered with player_api.register_model+---@field set_animation fun(player: ObjectRef, anim_name: string, speed: number): nil Applies an animation to a player if speed or anim_name differ from the currently playing animation,player: PlayerRef, anim_name: name of the animation, speed: keyframes per second. If nil, the default from the model def is used +---@field set_textures fun(player: ObjectRef, textures: string[]): nil Sets player textures player: PlayerRef, textures: array of textures. If nil, the default from the model def is used +---@field set_texture fun(player: ObjectRef, index: number, texture: string): nil Sets one of the player textures, player: PlayerRef, index: Index into array of all textures, texture: the texture string +---@field get_textures fun(player: ObjectRef): string[] Returns player textures table +---@field get_animation fun(player: ObjectRef): {["model"]: string | nil, ["textures"]: string[] | nil, ["animation"]: table | nil} Returns a table containing fields model, texturesandanimation. Any of the fields of the returned table may be nil, player: PlayerRef +---@field player_attached table<string, boolean> A table that maps a player name to a boolean. If the value for a given player is set to true, the default player animations (walking, digging, ...) will no longer be updated, and knockback from damage is prevented for that player. Example of usage: A mod sets a player's value to true when attached to a vehicle. + + +---Model Definition +---@class MtgPlayerApiModelDef +---@field animation_speed number Default: 30, animation speed, in keyframes per second +---@field textures string[] Default {"character.png"}, array of textures +---@field animations table<string, MtgPlayerApiAnimationDef> +---@field visual_size {["x"]: number, ["y"]: number} +---@field collisionbox number[] +---@field stepheight number +---@field eye_height number + + +---Model Animation definition +---@class MtgPlayerApiAnimationDef +---@field x number start frame +---@field y number end frame +---@field collisionbox number[] | nil +---@field eye_height number | nil model eye height +---@field override_local boolean | nil suspend client side animations while this one is active (optional) diff --git a/types/mtg-screwdriver.type.lua b/types/mtg-screwdriver.type.lua new file mode 100644 index 0000000..5550b16 --- /dev/null +++ b/types/mtg-screwdriver.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game screwdriver mod +---@class MtgScrewdriver +---@field disallow fun(): boolean Returns falsediff --git a/types/mtg-sfinv.lua b/types/mtg-sfinv.lua new file mode 100644 index 0000000..bec0f9c --- /dev/null +++ b/types/mtg-sfinv.lua @@ -0,0 +1,26 @@ +---@diagnostic disable: codestyle-check +---Sfinv API +---@class Sfinv +---@field register_page fun(name: string, def: SfinvDef): nil Register a page +---@field make_formspec fun(player: ObjectRef, contex: SfinvContext, content: string, show_inv?: boolean, size?: string): nil Adds a theme to a formspec show_inv, defaults to false. Whether to show the player's main inventory size, defaults tosize[8,8.6]if not specified +---@field get_or_create_context fun(player: ObjectRef): SfinvContext Gets the player's context +---@field set_context fun(player: ObjectRef, context: SfinvContext): nil +---@field get_formspec fun(player: ObjectRef, context: SfinvContext): string Builds current page's formspec +---@field set_player_inventory_formspec fun(player: ObjectRef): string (re)builds page formspec and calls set_inventory_formspec(). + + +---Sfinv Definition +---@class SfinvDef +---@field title string Human readable page name (required) +---@field get fun(self: Sfinv, player: ObjectRef, context: SfinvContext): string Returns a formspec string. See formspec variables. (required) +---@field is_in_nav fun(self: Sfinv, player: ObjectRef, context: SfinvContext): boolean Return true to show in the navigation (the tab header, by default) +---@field on_player_receive_fields fun(self: Sfinv, player: ObjectRef, context: SfinvContext, fields: table): nil On formspec submit. +---@field on_enter fun(self: Sfinv, player: ObjectRef, context: SfinvContext): nil Called when the player changes pages, usually using the tabs. +---@field on_leave fun(self: Sfinv, player: ObjectRef, context: SfinvContext): nil When leaving this page to go to another, called before other's on_enter + +---Sfinv Context, including: any thing you want to store, sfinv will clear the stored data on log out / log in +---@class SfinvContext +---@field page string Current page name +---@field nav string[] A list of page names +---@field nav_titles string[] A list of page titles +---@field nav_idx number Current nav index (in nav and nav_titles) diff --git a/types/mtg-stairs.type.lua b/types/mtg-stairs.type.lua new file mode 100644 index 0000000..1984d79 --- /dev/null +++ b/types/mtg-stairs.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game stairs mod +---@class MtgStairs +---@field register_stair_and_slab fun(subname: string, recipeitem?: string, groups: GroupCapsDef, images: NodeTilesDef, desc_stair: string, desc_slab: string, sounds: NodeSoundDef, worldaligntex?: boolean, desc_stair_inner?: string, desc_stair_outer?: string): nilsubname: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_subname", recipeitem: Item used in the craft recipe, e.g. "default:cobble", may be nil, groups: damage and digging time defining groups, worldaligntex: A bool to set all textures world-aligned. Default false. diff --git a/types/node.type.lua b/types/node.type.lua new file mode 100644 index 0000000..d2db139 --- /dev/null +++ b/types/node.type.lua @@ -0,0 +1,53 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias NodeDef NodeDefAbstract | NodeDefMtgFarming + +---Node definition. Used by minetest.register_node. +---@class NodeDefAbstract +---@field name string +---@field next_plant string|nil +---@field liquidtype 'none'|'source'|'flowing' specifies liquid flowing physics, "none": no liquid flowing physics, "source": spawns flowing liquid nodes at all 4 sides and below; recommended drawtype: "liquid". "flowing": spawned from source, spawns more flowing liquid nodes around it until liquid_rangeis reached; will drain out without a source; recommended drawtype: "flowingliquid". If it's "source" or "flowing" andliquid_range > 0, then both liquid_alternative_fields must be specified +---@field on_rightclick fun(pos: Vector, node: NodeDef, clicker: ObjectRef, itemstack: ItemStack, pointed_thing?: PointedThingDef): ItemStack default: nil, Called when clicker (an ObjectRef) used the 'place/build' key not neccessarily an actual rightclick) while pointing at the node at pos with 'node' being the node table. itemstack will hold clicker's wielded item. Shall return the leftover itemstack. Note: pointed_thing can be nil, if a mod calls this function. This function does not get triggered by clients <=0.4.16 if the "formspec" node metadata field is set. +---@field place_param2 number Value for param2 that is set when player places node +---@field param2 number Value for param2 that is set when player places node +---@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 usingmove_resistance) +---@field walkable boolean If true, objects collide with node. +---@field after_dig_node fun(pos: Vector, oldnode: NodeDef, oldmetadata: table, digger: ObjectRef): nil oldmetadata is in table format. Called after destructing node when node was dug using minetest.node_dig / minetest.dig_node., default: nil +---@field paramtype2 string +---@field palette string Image + +---Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length. +---@class NodeTilesDef +---@field name string +---@field animation TileAnimationDef +---@field backface_culling boolean backface culling enabled by default for most nodes +---@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, 16should be equally divisible byscaleor 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._optionaldrawtypes 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 inliquid_alternative_flowing. Use backface_culling = falsefor 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 inliquid_alternative_source. Node textures are defined with special_tileswhere the first tile is for the top and bottom faces and the second tile is for the side faces.tilesis used for the item/inventory/wield image rendering. Usebackface_culling = falsefor 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 intiles. 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, glasslikeandallfacesaccording to the menu setting: Opaque Leaves / Simple Leaves / Fancy Leaves. With 'Simple Leaves' selected, the texture specified inspecial_tilesis used instead, if present. This allows a visually thicker texture to be used to compensate for howglasslikereduces visual thickness. +---| '"torchlike"' # A single vertical texture. Ifparamtype2="[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 tilesand is perpendicular to that node. Ifparamtype2="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 param2Ifpar
+---| '"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 plantlikeextension has a height ofparam2 / 16nodes, otherwise it's the height of 1 node Ifparamtype2="wallmounted", the plantlikeextension will be at one of the corresponding 6 sides of the base cube. Also, the base cube rotates like anormalcube would Theplantlikeextension visually passes through any nodes above the base cube without affecting them. The base cube texture tiles are defined as normal, theplantlikeextension uses the defined special tile, for example:special_tiles = {{name = "default_papyrus.png"}},diff --git a/types/nodetimer.type.lua b/types/nodetimer.type.lua new file mode 100644 index 0000000..7a96ea7 --- /dev/null +++ b/types/nodetimer.type.lua @@ -0,0 +1,12 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Node Timers: a high resolution persistent per-node timer. Can be gotten viaminetest.get_node_timer(pos). +---@class NodeTimerRef +---@field set fun(self: NodeTimerRef, timeout: integer|number, elapsed: integer|number): nil Set a timer's state. timeoutis in seconds, and supports fractional values (0.1 etc).elapsedis in seconds, and supports fractional values (0.1 etc). Will trigger the node'son_timerfunction after(timeout - elapsed)seconds. +---@field start fun(self: NodeTimerRef, timeout: integer|number): nil Start a timer. Equivalent toset(timeout,0). +---@field stop fun(): nil Stops the timer +---@field get_timeout fun(): number|integer Returns current timeout in seconds. +---@field get_elapsed fun(): number|integer Returns current elapsed time in seconds. +---@field is_started fun(): boolean Returns boolean state of timer. Returns trueif timer is started, otherwisefalse. +---@field get_meta fun(pos: Vector): MetaDataRef Get a NodeMetaRefat that position diff --git a/types/object.type.lua b/types/object.type.lua new file mode 100644 index 0000000..3fea64e --- /dev/null +++ b/types/object.type.lua @@ -0,0 +1,89 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias ObjectRef ObjectRefAbstract | ObjectRefLuaEntityRef + +---Moving things in the game are generally these. +---This is basically a reference to a C++ServerActiveObject. +---@class ObjectRefAbstract +---@field get_pos fun(): Vector Position of player +---@field get_inventory fun(): InvRef|nil Returns an InvReffor players, otherwise returnsnil+---@field get_wield_index fun(): integer Returns the index of the wielded item +---@field get_wielded_item fun(): ItemStack Returns anItemStack+---@field set_acceleration fun(self: ObjectRef, acc: Vector): nil +---@field set_yaw fun(self: ObjectRef, yaw: integer|number): nil Sets the yaw in radians (heading). +---@field get_player_name fun(self: ObjectRef): string Returns""if is not a player. +---@field set_fov fun(self: ObjectRef, fov: number|integer, is_multiplier: boolean, transition_time: number|integer): nil Sets player's FOV.fov: FOV value. is_multiplier: Set to trueif the FOV value is a multiplier. Defaults tofalse. transition_time: If defined, enables smooth FOV transition. Interpreted as the time (in seconds) to reach target FOV. If set to 0, FOV change is instantaneous. Defaults to 0. Set fovto 0 to clear FOV override. +---@field get_hp fun(self: ObjectRef): number|integer Returns number of health points +---@field is_player fun(self: ObjectRef): boolean returns true for players, false otherwise +---@field get_luaentity fun(self: ObjectRef): table +---@field get_armor_groups fun(self: ObjectRef): ObjectRefArmorGroups returns a table with the armor group ratings +---@field punch fun(self: ObjectRef, puncher: ObjectRef, time_from_last_punch: integer|number, tool_capabilities: ToolCapabilitiesDef, direction: Vector|nil): nil +---@field add_velocity fun(self: ObjectRef, vel: Vector): nilvelis 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.statsupports 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 trueif successful. +---@field move_to fun(self: ObjectRef, pos: Vector, continuous?: boolean): nil Does an interpolated move for Lua entities for visually smooth transitions. Ifcontinuousis true, the Lua entity will not be moved to the current position before starting the interpolated move. For players this does the same asset_pos,continuousis ignored. +---@field set_hp fun(self: ObjectRef, hp: number, reason: table): nil set number of health points See reason in register_on_player_hpchange Is limited to the range of 0 ... 65535 (2^16 - 1) For players: HP are also limited byhp_maxspecified in object properties +---@field set_animation fun(self: ObjectRef, frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean): nilframe_range: table {x=num, y=num}, default: {x=1, y=1}, frame_speed: number, default: 15.0, frame_blend: number, default: 0.0, frame_loop: boolean, default: true+---@field get_velocity fun(self: ObjectRef): Vector returns the velocity, a vector. +---@field set_rotation fun(self: ObjectRef, rot: Vector): nilrotis a vector (radians). X is pitch (elevation), Y is yaw (heading) and Z is roll (bank). +---@field set_pos fun(self: ObjectRef, pos: Vector): nil + + +---Moving things in the game are generally these. +---This is basically a reference to a C++ServerActiveObject. +---@class ObjectRefLuaEntityRef +---@field set_velocity fun(self: ObjectRef, vel: Vector): nil velis a vector, e.g.{x=0.0, y=2.3, z=1.0}+---@field remove fun(): nil remove object, The object is removed after returning from Lua. However theObjectRefitself instantly becomes unusable with all further method calls having no effect and returningnil. +---@field get_rotation fun(self: ObjectRef): Vector returns the rotation, a vector (radians) +---@field get_attach fun(self: ObjectRef): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. +---@field set_attach fun(self: ObjectRef, parent: ObjectRef, bone?: string, position?: Vector, rotation?: Vector, forced_visible?: boolean): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. + +---ObjectRefarmor groups +---@class ObjectRefArmorGroups +---@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 byObjectRefmethods. 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 tominetest.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 walkablenodes. +---@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. fromminetest.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 zis not specified,xwill 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 awalkablenode, if the height difference is withinstepheight. +---@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 falsewill 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/particlespawner.type.lua b/types/particlespawner.type.lua new file mode 100644 index 0000000..73d2a91 --- /dev/null +++ b/types/particlespawner.type.lua @@ -0,0 +1,50 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +--- ParticleSpawner definition +---@class ParticlespawnerDef +---@field amount number|integer Number of particles spawned over the time periodtime. +---@field time number|integer Lifespan of spawner in seconds. If time is 0 spawner has infinite lifespan and spawns the amounton a per-second basis. +---@field collisiondetection boolean If true collide withwalkablenodes and, depending on theobject_collisionfield, objects too. +---@field collision_removal boolean If true particles are removed when they collide. Requires collisiondetection = true to have any effect. +---@field object_collision boolean If true particles collide with objects that are defined asphysical = true,andcollide_with_objects = true,. Requires collisiondetection = true to have any effect. +---@field attached ObjectRef If defined, particle positions, velocities and accelerations are relative to this object's position and yaw +---@field vertical boolean If true face player using y axis only +---@field texture string The texture of the particle. e,g, "image.png"+---@field playername string Optional, if specified spawns particles only on the player's client +---@field animation TileAnimationDef Optional, specifies how to animate the particles' texture. v5.6.0 and later: set length to -1 to sychronize the length of the animation with the expiration time of individual particles. (-2 causes the animation to be played twice, and so on) +---@field glow number|integer Optional, specify particle self-luminescence in darkness. Values 0-14. +---@field node table<string, string|number|integer> e.g.{name = "ignore", param2 = 0}. Optional, if specified the particles will have the same appearance as node dig particles for the given node. Texture and animation will be ignored if this is set.
+---@field node_tile number|integer Optional, only valid in combination with node. If set to a valid number 1-6, specifies the tile from which the particle texture is picked. Otherwise, the default behavior is used. (currently: any random tile)
+---@field minpos Vector Legacy definition field
+---@field maxpos Vector Legacy definition field
+---@field minvel Vector Legacy definition field
+---@field maxvel Vector Legacy definition field
+---@field minacc Vector Legacy definition field
+---@field maxacc Vector Legacy definition field
+---@field minexptime number|integer Legacy definition field
+---@field maxexptime number|integer Legacy definition field
+---@field minsize number|integer Legacy definition field
+---@field maxsize number|integer Legacy definition field
+---@field pos number|integer|Vector|ParticlespawnerPosDef As number: absolute value - all components of every particle's position vector will be set to this. As Vector: vec3 - all particles will appear at this exact position throughout the lifetime of the particlespawner. As ParticlespawnerPosDef: vec3 range - the particle will appear at a position that is picked at random from within a cubic range.
+
+--- ParticleSpawner pos definition
+---@class ParticlespawnerPosDef
+---@field min Vector The minimum value this property will be set to in particles spawned by the generator.
+---@field max Vector The maximum value this property will be set to in particles spawned by the generator.
+---@field bias number|integer When bias is 0, all random values are exactly as likely as any other. When it is positive, the higher it is, the more likely values will appear towards the minimum end of the allowed spectrum. When it is negative, the lower it is, the more likely values will appear towards the maximum end of the allowed spectrum. The curve is exponential and there is no particular maximum or minimum value.
+---@field pos_tween ParticlespawnerPosTweenDef A tween table should consist of a list of frames in the same form as the untweened pos property above, which the engine will interpolate between, and optionally a number of properties that control how the interpolation takes place. Currently only two frames, the first and the last, are used, but extra frames are accepted for the sake of forward compatibility. Any of the above definition styles can be used here as well in any combination supported by the property type.
+
+--- ParticleSpawner pos_tween definition
+---@class ParticlespawnerPosTweenDef
+---@field style string e.g. "fwd": linear animation from first to last frame (default), "rev": linear animation from last to first frame, "pulse": linear animation from first to last then back to first again, "flicker": like "pulse", but slightly randomized to add a bit of stutter
+---@field reps number|integer Number of times the animation is played over the particle's lifespan
+---@field start number|integer Point in the spawner's lifespan at which the animation begins. 0 is the very beginning, 1 is the very end.
+---@field frames number|integer|Vector|ParticlespawnerPosDef Frames can be defined in a number of different ways, depending on the underlying type of the property. For now, all but the first and last frame are ignored.
+
+--- Tile animation definition
+---@class TileAnimationDef
+---@field type string e.g. "vertical_frames", "sheet_2d"
+---@field aspect_w number|integer Width of a frame in pixels
+---@field aspect_h number|integer Height of a frame in pixels
+---@field length number|integer e.g. 3.0 Full loop length, 0.5 Length of a single frame
diff --git a/types/pointed-thing.type.lua b/types/pointed-thing.type.lua
new file mode 100644
index 0000000..8c21680
--- /dev/null
+++ b/types/pointed-thing.type.lua
@@ -0,0 +1,12 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---Pointed thing definition
+---@class PointedThingDef
+---@field type string e.g. {type="nothing"} {type="node"} {type="object"}
+---@field under Vector Refers to the node position behind the pointed face.
+---@field above Vector Refers to the node position in front of the pointed face.
+---@field ref ObjectRef e.g. {type="object", ref=ObjectRef}
+---@field intersection_point Vector Exact pointing location (currently only Raycast supports these field). The absolute world coordinates of the point on the selection box which is pointed at. May be in the selection box if the pointer is in the box too.
+---@field box_id number|integer Exact pointing location (currently only Raycast supports these field). The ID of the pointed selection box (counting starts from 1).
+---@field intersection_normal Vector Exact pointing location (currently only Raycast supports these field). Unit vector, points outwards of the selected selection box. This specifies which face is pointed at. Is a null vector vector.zero() when the pointer is inside the selection box.
diff --git a/types/raycast.type.lua b/types/raycast.type.lua
new file mode 100644
index 0000000..b0679fe
--- /dev/null
+++ b/types/raycast.type.lua
@@ -0,0 +1,6 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---A raycast on the map. It works with selection boxes. The map is loaded as the ray advances. If the map is modified after the Raycast is created, the changes may or may not have an effect on the object.
+---@class Raycast
+---@field next fun(): PointedThingDef Returns a pointed_thing with exact pointing location. Returns the next thing pointed by the ray or nil.
diff --git a/types/sound.type.lua b/types/sound.type.lua
new file mode 100644
index 0000000..0ea957c
--- /dev/null
+++ b/types/sound.type.lua
@@ -0,0 +1,28 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+---Specifies a sound name, gain (=volume) and pitch. This is either a string or a table. In string form, you just specify the sound name or the empty string for no sound.
+---@class SimpleSoundSpec
+---@field name string Sound name.
+---@field gain number|integer Volume (1.0 = 100%). Optional and default to 1.0.
+---@field pitch number|integer Pitch (1.0 = 100%). Optional and default to 1.0.
+
+--- 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_<groupname>, where <groupname> is the name of the item's digging group with the fastest digging time. In case of a tie, one of the sounds will be played (but we cannot predict which one) Default value: "__group"
+---@field dug SimpleSoundSpec Node was dug
+---@field place SimpleSoundSpec Node was placed. Also played after falling
+---@field place_failed SimpleSoundSpec When node placement failed. Note: This happens if the built-in node placement failed. This sound will still be played if the node is placed in the on_place callback manually.
+---@field fall SimpleSoundSpec When node starts to fall or is detached
+
+---Definition of item sounds to be played at various events. All fields in this table are optional.
+---@class ItemSoundDef
+---@field breaks SimpleSoundSpec|string When tool breaks due to wear. Ignored for non-tools
+---@field eat SimpleSoundSpec|string When item is eaten with minetest.do_item_eat
+---@field punch_use SimpleSoundSpec|string When item is used with the 'punch/mine' key pointing at a node or entity
+---@field punch_use_air SimpleSoundSpec|string When item is used with the 'punch/mine' key pointing at nothing (air)
+
+---Dig params definition.
+---@class DigParamsDef
diff --git a/types/string.type.lua b/types/string.type.lua
new file mode 100644
index 0000000..b86a4fc
--- /dev/null
+++ b/types/string.type.lua
@@ -0,0 +1,8 @@
+---@diagnostic disable: codestyle-check, duplicate-doc-alias
+---https://github.com/sumneko/lua-language-server/wiki
+
+---@alias string string|StringAbstract
+
+---String helpers
+---@class StringAbstract
+---@field split fun(self: string, str: string, separator?: string, include_empty?: boolean, max_splits?: number, sep_is_pattern?: boolean): table separator: string, default: ",", include_empty: boolean, default: false, max_splits: number, if it's negative, splits aren't limited, default: -1, sep_is_pattern: boolean, it specifies whether separator is a plain string or a pattern (regex), default: false. e.g. "a,b":split"," returns {"a","b"}
diff --git a/types/table.type.lua b/types/table.type.lua
new file mode 100644
index 0000000..f8acf47
--- /dev/null
+++ b/types/table.type.lua
@@ -0,0 +1,8 @@
+---@diagnostic disable: codestyle-check, duplicate-doc-alias
+---https://github.com/sumneko/lua-language-server/wiki
+
+---@alias tablelib tablelib|TableAbstract
+
+---Table helpers
+---@class TableAbstract
+---@field copy fun(table: table): table returns a deep copy of table
diff --git a/types/unified_inventory.type.lua b/types/unified_inventory.type.lua
new file mode 100644
index 0000000..3f0baab
--- /dev/null
+++ b/types/unified_inventory.type.lua
@@ -0,0 +1,8 @@
+---@diagnostic disable: codestyle-check
+---Base class Unified Inventory
+---@class UnifiedInventory
+---@field set_inventory_formspec fun(player: ObjectRef, formspecname: string): nil
+---@field register_button fun(name: string, def: table): nil
+---@field single_slot fun(x: number, y: number): nil
+---@field register_page fun(name: string, def: table): nil
+---@field style_full table
diff --git a/types/vector.type.lua b/types/vector.type.lua
new file mode 100644
index 0000000..ceeb286
--- /dev/null
+++ b/types/vector.type.lua
@@ -0,0 +1,17 @@
+---@diagnostic disable: codestyle-check
+---https://github.com/sumneko/lua-language-server/wiki
+
+------All vector.* functions allow vectors {x = X, y = Y, z = Z} without metatables. Returned vectors always have a metatable set.
+---@class Vector
+---@field x integer|number Pitch
+---@field y integer|number Yaw
+---@field z integer|number Roll
+---@field multiply fun(v: Vector, s: number|integer): Vector Returns a scaled vector. Deprecated: If s is a vector: Returns the Schur product.
+---@field subtract fun(v: Vector, x: number|integer|Vector): Vector Returns a vector. If x is a vector: Returns the difference of v subtracted by x. If x is a number: Subtracts x from each component of v.
+---@field add fun(v: Vector, x: number|integer|Vector): Vector Returns a vector. If x is a vector: Returns the sum of v and x. If x is a number: Adds x to each component of v.
+---@field normalize fun(v: Vector): Vector Returns a vector of length 1 with direction of vector v. If v has zero length, returns (0, 0, 0).
+---@field distance fun(p1: Vector, p2: Vector): number|integer Returns zero or a positive number, the distance between p1 and p2.
+---@field round fun(v: Vector): Vector Returns a vector, each dimension rounded to nearest integer. At a multiple of 0.5, rounds away from zero.
+---@field new fun(a, b?, c?): Vector Returns a new vector (a, b, c).
+---@field direction fun(p1: Vector, p2: Vector): Vector Returns a vector of length 1 with direction p1 to p2. If p1 and p2 are identical, returns (0, 0, 0).
+---@field divide fun(v: Vector, s: Vector | number): Vector Returns a scaled vector. Deprecated: If s is a vector: Returns the Schur quotient.
diff --git a/types/xbows.type.lua b/types/xbows.type.lua
new file mode 100644
index 0000000..58ac036
--- /dev/null
+++ b/types/xbows.type.lua
@@ -0,0 +1,210 @@
+---@diagnostic disable: codestyle-check
+---Base class XBows
+---@class XBows
+---@field pvp boolean
+---@field creative boolean
+---@field mesecons string|nil
+---@field playerphysics string|nil
+---@field player_monoids string|nil
+---@field u_skins string|nil
+---@field wardrobe string|nil
+---@field _3d_armor string|nil
+---@field skinsdb string|nil
+---@field player_api string|nil
+---@field registered_bows table<string, ItemDef|BowItemDefCustom>
+---@field registered_arrows table<string, ItemDef|ArrowItemDefCustom>
+---@field registered_quivers table<string, ItemDef|QuiverItemDefCustom>
+---@field registered_particle_spawners table<string, ParticlespawnerDef|ParticlespawnerDefCustom>
+---@field registered_entities table<string, XBowsEntityDef>
+---@field player_bow_sneak table<string, table<string, boolean>>
+---@field settings {["x_bows_attach_arrows_to_entities"]: boolean | nil, ["x_bows_show_damage_numbers"]: boolean | nil, ["x_bows_show_3d_quiver"]: boolean | nil}
+---@field quiver table Quiver class
+---@field charge_sound_after_job table<string, JobTable>
+---@field is_allowed_ammunition fun(self: XBows, weapon_name: string, ammo_name: string): boolean Check if ammunition is allowed to charge this weapon
+---@field is_creative fun(self: XBows, name: string): boolean Check if creative is enabled or if player has creative priv
+---@field get_particle_effect_for_arrow fun(self: XBows, name: string, pos: Vector): number|boolean Get particle effect from registered spawners table
+---@field register_entity fun(self: EntityDef|XBows, name: string, def: XBowsEntityDef): nil Register new projectile entity
+---@field update_bow_allowed_ammunition fun(self: XBows, name: string, def: string[]): nil Updates allowed_ammunition definition on already registered item, so MODs can add new ammunitions to this list.
+---@field reset_charged_bow fun(self: XBows, player: ObjectRef, includeWielded?: boolean): nil Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
+---@field register_bow fun(self: XBows, name: string, def: ItemDef | BowItemDefCustom, mod_override?: boolean): boolean|nil Register new bow/gun.
+---@field register_arrow fun(self: XBows, name: string, def: ItemDef | ArrowItemDefCustom): boolean|nil Register new arrow/projectile.
+---@field register_quiver fun(self: XBows, name: string, def: ItemDef | ArrowItemDefCustom): boolean|nil Register new quiver.
+---@field load fun(self: XBows, itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef): ItemStack Load bow
+---@field shoot fun(self: XBows, itemstack: ItemStack, user: ObjectRef, pointed_thing?: PointedThingDef): ItemStack Shoot bow
+---@field register_particle_effect fun(self: XBows, name: string, def: ParticlespawnerDef|ParticlespawnerDefCustom): nil Add new particle to XBow registration
+---@field open_quiver fun(self: XBowsQuiver, itemstack: ItemStack, user: ObjectRef): ItemStack Open quiver
+---@field uuid fun(): string Creates UUID
+---@field fallback_quiver boolean If no invenotory mod is detected then fallback solution will be used
+---@field show_damage_numbers fun(self: XBows, pos: Vector, damaga: number, is_crit?: boolean): nil Builds textures and shows textures in particle spawner
+
+
+---XBowsQuiver class extended from XBows
+---@alias XBowsQuiver XBowsQuiverBase|XBows
+---@class XBowsQuiverBase
+---@field hud_item_ids table
+---@field after_job table<string, JobTable>
+---@field udate_or_create_hud fun(self: XBowsQuiver, player: ObjectRef, inv_list: ItemStack[], idx?: number): nil Update or create quiver HUD
+---@field get_or_create_detached_inv fun(self: XBowsQuiver, quiver_id: string, player_name: string, quiver_items?: string): InvRef Get existing detached inventory or create new one
+---@field save fun(self: XBowsQuiver, inv: InvRef, player: ObjectRef, quiver_is_closed?: boolean): nil Save quiver inventory to itemstack meta
+---@field close_quiver fun(self: XBowsQuiver, player: ObjectRef, quiver_id?: string): nil Close one or all open quivers in players inventory
+---@field get_replacement_item fun(self: XBowsQuiver, from_stack: ItemStack, to_item_name: string): ItemStack Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta.
+---@field get_itemstack_arrow_from_quiver fun(self: XBowsQuiver, player: ObjectRef, to_item_name: string): {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number} Gets arrow from quiver
+---@field remove_hud fun(self: XBowsQuiver, player: ObjectRef): nil Remove all added HUDs
+---@field get_formspec fun(self: XBowsQuiver, name: string): string Create formspec
+---@field get_string_from_inv fun(self: XBowsQuiver, inv: InvRef): {['inv_string']: string, ['content_description']: string} Convert inventory of itemstacks to serialized string
+---@field set_string_to_inv fun(self: XBowsQuiver, inv: InvRef, str: string): nil Set items from serialized string to inventory
+---@field quiver_can_allow fun(self: XBowsQuiver, inv: InvRef, player: ObjectRef): boolean Check if we are allowing actions in the correct quiver inventory
+---@field show_3d_quiver fun(self: XBowsQuiver, player: ObjectRef, props?: {["is_empty"]: boolean|nil}): nil Applies full/empty quiver textures
+---@field hide_3d_quiver fun(self: XBowsQuiver, player: ObjectRef): nil Applies blank quiver textures - hiding the quiver
+---@field sfinv_register_page fun(): nil register inventoy mod page
+---@field i3_register_page fun(): nil register inventoy mod page
+---@field ui_register_page fun(): nil register inventoy mod page
+
+
+---Custom field in ParticlespawnerDef
+---@class ParticlespawnerDefCustom
+---@field custom ParticlespawnerDefCustomAttr
+
+---Custom field attributes in ParticlespawnerDef
+---@class ParticlespawnerDefCustomAttr
+---@field minpos Vector
+---@field maxpos Vector
+
+---Custom field in ItemDef
+---@class BowItemDefCustom
+---@field custom BowItemDefCustomAttr
+
+---Custom field attributes in ItemDef
+---@class BowItemDefCustomAttr
+---@field crit_chance number crit_chance 10% chance, 5 is 20% chance, (1 / crit_chance) * 100 = % chance
+---@field inventory_image_charged string
+---@field recipe table
+---@field fuel_burntime number
+---@field name_charged string
+---@field name string
+---@field mod_name string
+---@field uses number
+---@field strength number How strong is the bow. Defines how far the arrow will fly.
+---@field strength_min number|nil How strong is the bow. Defines how far the arrow will fly.
+---@field strength_max number|nil How strong is the bow. Defines how far the arrow will fly.
+---@field allowed_ammunition string[]|nil
+---@field wield_image_charged string|nil
+---@field acc_x_min number|nil
+---@field acc_y_min number|nil
+---@field acc_z_min number|nil
+---@field acc_x_max number|nil
+---@field acc_y_max number|nil
+---@field acc_z_max number|nil
+---@field sound_load string
+---@field sound_hit string
+---@field sound_shoot string
+---@field sound_shoot_crit string
+---@field gravity number
+
+---Custom field in ItemDef
+---@class ArrowItemDefCustom
+---@field custom ArrowItemDefCustomAttr
+
+---Custom field attributes in ItemDef
+---@class ArrowItemDefCustomAttr
+---@field tool_capabilities ToolCapabilitiesDef
+---@field craft_count number
+---@field recipe table
+---@field fuel_burntime number
+---@field name string
+---@field mod_name string
+---@field particle_effect string|nil
+---@field particle_effect_crit string|nil
+---@field particle_effect_fast string|nil
+---@field projectile_entity string
+---@field on_hit_node fun(self: table, pointed_thing_ref: table)
+---@field on_hit_entity fun(self: table, pointed_thing_ref: table)
+---@field on_hit_player fun(self: table, pointed_thing_ref: table)
+---@field on_after_activate fun(self: table)
+---@field description_abilities string
+
+
+---Custom field in ItemDef
+---@class QuiverItemDefCustom
+---@field custom QuiverItemDefCustomAttr
+
+---Custom field attributes in ItemDef
+---@class QuiverItemDefCustomAttr
+---@field recipe table
+---@field recipe_count number
+---@field faster_arrows number
+---@field add_damage number
+---@field fuel_burntime number
+---@field inventory_image_open string
+---@field wield_image_open string
+---@field name string
+
+
+---Custom field in EntityDef
+---@alias XBowsEntityDef EntityDef|EntityDefCustom|XBows
+---@class EntityDefCustom
+---@field on_death fun(self: XBowsEntityDef, selfObj: table, killer: ObjectRef|nil): nil Function receive a "luaentity" table as self. Called when the object dies.
+---@field on_punch fun(self: XBowsEntityDef, selfObj: table, puncher: ObjectRef|nil, time_from_last_punch: number|integer|nil, tool_capabilities: ToolCapabilitiesDef|nil, dir: Vector, damage: number|integer): boolean|nil Function receive a "luaentity" table as self. Called when somebody punches the object. Note that you probably want to handle most punches using the automatic armor group system. Can return true to prevent the default damage mechanism.
+---@field _custom EntityDefCustomAttr
+
+---@alias EntityDefCustomAttr EntityDefCustomAttrDef|EntityDef
+---@class EntityDefCustomAttrDef
+---@field name string
+---@field mod_name string
+---@field animations EntityAnimationDef
+---@field rotation_factor number|fun(): number
+
+---Entity animation definition
+---@class EntityAnimationDef
+---@field idle {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean}
+---@field on_hit_node {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean}
+
+---Arrow object and custom attributes
+---@class EnityCustomAttrDef
+---@field object ObjectRef
+---@field _velocity Vector
+---@field _old_pos Vector
+---@field _attached boolean
+---@field _attached_to {["type"]: string, ["pos"]: Vector | nil}
+---@field _has_particles boolean
+---@field _lifetimer number
+---@field _nodechecktimer number
+---@field _is_drowning boolean
+---@field _in_liquid boolean
+---@field _shot_from_pos Vector
+---@field _arrow_name string
+---@field _bow_name string
+---@field _user_name string
+---@field _user ObjectRef
+---@field _tflp number
+---@field _tool_capabilities ToolCapabilitiesDef
+---@field _is_critical_hit boolean
+---@field _faster_arrows_multiplier number
+---@field _add_damage number
+---@field _caused_damage number
+---@field _caused_knockback number
+---@field _arrow_particle_effect string
+---@field _arrow_particle_effect_crit string
+---@field _arrow_particle_effect_fast string
+---@field _sound_hit string
+---@field _player_look_dir Vector
+---@field _acc_x number
+---@field _acc_y number
+---@field _acc_z number
+---@field _strength number
+---@field name string
+---@field _rotation_factor number | fun(): number
+---@field _step_count number
+---@field _x_enchanting table<string, {["value"]: number | nil}>
+
+---Staticdata attributes
+---@class EnityStaticDataAttrDef
+---@field _arrow_name string
+---@field _bow_name string
+---@field _user_name string
+---@field _is_critical_hit boolean
+---@field _tool_capabilities ToolCapabilitiesDef
+---@field _tflp number
+---@field _add_damage number
+---@field _faster_arrows_multiplier number | nil
+---@field _x_enchanting table<string, {["value"]: number | nil}>

</details>
upstream x_bows changes: <details> ```diff diff --git a/.cdb.json b/.cdb.json new file mode 100644 index 0000000..74d5ca1 --- /dev/null +++ b/.cdb.json @@ -0,0 +1,20 @@ +{ + "type": "MOD", + "title": "X Bows", + "name": "x_bows", + "short_description": "Adds bow and arrows with API.", + "dev_state": "MAINTENANCE_ONLY", + "tags": [ + "pvp", + "shooter", + "survival", + "tools" + ], + "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?status=new&status=open", + "forums": 26466, + "video_url": "https://youtu.be/pItpltmUoa8", + "website": "https://bitbucket.org/minetest_gamers/x_bows/wiki/Home" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb53136 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2285905 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Creating an archive + +.* export-ignore +assets export-ignore +scripts export-ignore +bin export-ignore +docs export-ignore +types export-ignore +*.zip export-ignore +bitbucket-pipelines.yml export-ignore +package.json export-ignore +package-lock.json export-ignore +screenshot*.png export-ignore +i18n.py export-ignore +config.ld export-ignore diff --git a/.gitignore b/.gitignore index 496ee2c..3e69eba 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ -.DS_Store \ No newline at end of file +.DS_Store +docs/build +*.blend1 +*.blend2 +*.old +node_modules +*.log +logs diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..227756b --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,86 @@ +unused_args = false +allow_defined_top = true +max_line_length = false + +exclude_files = { + './scripts', + './bin', + './logs', + './node_modules', + './sounds', + './textures', + './models', + './docs', + './locale', + './types', +} + +globals = { + 'XBows', + 'XBowsQuiver', + 'XBowsEntityDefBase', + 'XBowsEntityDefCustom' +} + +read_globals = { + "DIR_DELIM", "INIT", + + "minetest", "core", + "dump", "dump2", + + "Raycast", + "Settings", + "PseudoRandom", + "PerlinNoise", + "VoxelManip", + "SecureRandom", + "VoxelArea", + "PerlinNoiseMap", + "PcgRandom", + "ItemStack", + "AreaStore", + "unpack", + + "vector", + + table = { + fields = { + "copy", + "indexof", + "insert_all", + "key_value_swap", + "shuffle", + } + }, + + string = { + fields = { + "split", + "trim", + } + }, + + math = { + fields = { + "hypot", + "sign", + "factorial", + "round", + } + }, + + "player_monoids", + "playerphysics", + "hb", + "mesecon", + "armor", + "default", + "i3", + "unified_inventory", + "player_api", + "u_skins", + "wardrobe", + "3d_armor", + "skinsdb", + "skins" +} diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..04a89d7 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "Lua 5.1", + "path": [ + "?.lua", + "?/init.lua" + ], + "pathStrict": true + }, + "workspace": { + "maxPreload": 1600, + "preloadFileSize": 1000, + "ignoreDir": [ + "/locale/", + "/libs/", + "/3rd", + "/.vscode", + "/meta", + "/.git", + "/docs", + "/bin" + ], + "checkThirdParty": false + }, + "typeFormat": { + "config": { + "format_line": "false" + } + }, + "type": { + "castNumberToInteger": true + }, + "doc": { + "privateName": [ + "_*" + ] + }, + "diagnostics": { + "disable": [ + "close-non-object" + ], + "groupFileStatus": { + "ambiguity": "Any", + "await": "Any", + "duplicate": "Any", + "global": "Any", + "luadoc": "Any", + "redefined": "Any", + "strict": "Any", + "type-check": "Any", + "unbalanced": "Any", + "unused": "Any" + }, + "ignoredFiles": "Disable", + "libraryFiles": "Disable", + "neededFileStatus": { + "codestyle-check": "Any" + }, + "disableScheme": [ + "git", + "type" + ], + "globals": [ + "DIR_DELIM", + "INIT", + "minetest", + "core", + "dump", + "dump2", + "Raycast", + "Settings", + "PseudoRandom", + "PerlinNoise", + "VoxelManip", + "SecureRandom", + "VoxelArea", + "PerlinNoiseMap", + "PcgRandom", + "ItemStack", + "AreaStore", + "unpack", + "vector", + "player_monoids", + "playerphysics", + "hb", + "mesecon", + "armor", + "default", + "i3", + "unified_inventory", + "player_api", + "u_skins", + "wardrobe", + "3d_armor", + "skinsdb", + "skins" + ] + } +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/LICENSE.txt b/LICENSE.txt index f166cc5..bce588e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,7 @@ GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -55,7 +54,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -111,7 +110,7 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -158,7 +157,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -216,7 +215,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -267,7 +266,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -329,7 +328,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -370,7 +369,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -422,7 +421,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -454,49 +453,3 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - <one line to give the library's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - <signature of Ty Coon>, 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! \ No newline at end of file diff --git a/README.md b/README.md index 683b697..b9de0d7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bow and Arrows [x_bows] -Adds bow and arrows to Minetest. +Adds bow and arrows with API. The goal of this Mod is to make most complete single bow with arrow what will work with MTG damage system, time from last punch as simple as possible. Eventually due to the damage tiers in MTG additional arrows were added. ![screenshot](screenshot.png) @@ -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) @@ -22,8 +23,52 @@ Video: https://youtu.be/pItpltmUoa8 * arrows adjusts pitch when flying * arrows can be picked up again after stuck in solid nodes * 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) +* X Bows API for creating custom shooters and projectiles +* 3d quiver shown in 3rd person view (compatible with 3d armor) +* x_enchanting support + +## 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 arrow/quiver inventory - there should be extra tab in your inventory MOD to show arrow and quiver inventory slots. +Arrows and quivers in the players main inventory don't count and will not be used. +You have to have arrows and/or quiver in dedicated arrow/quiver inventory slots in order to charge the bow. +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 +as it will give the arrow full speed (maximum shooting distance) and chance for critical arrow (double damage). + +There are few indications on how to know when the bow is fully charged: + +* there is a distinct "click" sound +* each arrow has "charge time" in the description +* after shooting, arrow will have particle trail + +There are few indications on how to know when the arrow is a critical arrow: + +* there is a distinct arrow flying sound +* after shooting, arrow will have red particle trail + +If you shoot the arrow before the bow is fully charged the speed/distance will be lower and no arrow particle trail will be shown (also no chance for critical arrow). +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 quiver inventory slot - there should be extra tab in your inventory MOD to show arrow and quiver inventory slots, bow can take arrows from quiver, otherwise arrows outside of the quiver are used to load the bow. +Though, if arrows from quiver are used to load 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 @@ -34,9 +79,18 @@ Video: https://youtu.be/pItpltmUoa8 - default (recipes) - farming (bow and target recipes) - 3d_armor (calculates damage including the armor) -- hbhunger (changes hudbar when poisoned) - mesecons (target can be used to trigger mesecon signal) - playerphysics (force sneak when holding charged bow) +- player_monoids (force sneak when holding charged bow) +- wool (quiver recipe) +- i3 +- unified_inventory +- simple_skins +- u_skins +- wardrobe +- sfinv +- skinsdb +- player_api (shows 3d quiver) ## License: @@ -51,13 +105,7 @@ 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 - x_bows_target.png @@ -68,7 +116,33 @@ Modified by SaKeL: - x_bows_arrow_steel.png - x_bows_arrow_mese.png - x_bows_arrow_diamond.png -- x_bows_arrow_diamond_poison.png + +**CC-BY-SA-3.0, by paramat** + +- x_bows_hotbar_selected.png +- x_bows_quiver_hotbar.png +- x_bows_single_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 +- x_bows_quiver_mesh.png +- x_bows_quiver_empty_mesh.png +- x_bows_quiver_blank_mesh.png +- x_bows_quiver_slot.png +- x_bows_dmg_0.png +- x_bows_dmg_1.png +- x_bows_dmg_2.png +- x_bows_dmg_3.png +- x_bows_dmg_4.png +- x_bows_dmg_5.png +- x_bows_dmg_6.png +- x_bows_dmg_7.png +- x_bows_dmg_8.png +- x_bows_dmg_9.png ### Sounds @@ -100,6 +174,35 @@ 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 +- x_bows_arrow.blend + +**Original model by MirceaKitsune (CC BY-SA 3.0).** +**Various alterations and fixes by kilbith, sofar, xunto, Rogier-5, TeTpaAka, Desour, stujones11, An0n3m0us (CC BY-SA 3.0):** + +Modified by SaKeL (added quiver): + +- x_bows_3d_armor_character.b3d +- x_bows_3d_armor_character.blend +- x_bows_character.b3d +- x_bows_character.blend + ## Installation -see: http://wiki.minetest.com/wiki/Installing_Mods +see: https://wiki.minetest.net/Installing_Mods diff --git a/api.lua b/api.lua new file mode 100644 index 0000000..58ee320 --- /dev/null +++ b/api.lua @@ -0,0 +1,2644 @@ +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +local S = minetest.get_translator(minetest.get_current_modname()) + +sfinv = sfinv --[[@as Sfinv]] + +---Check if table contains value +---@param table table +---@param value string|number +---@return boolean +local function table_contains(table, value) + for _, v in ipairs(table) do + if v == value then + return true + end + end + + return false +end + +---Merge two tables with key/value pair +---@param t1 table +---@param t2 table +---@return table +local function mergeTables(t1, t2) + for k, v in pairs(t2) do t1[k] = v end + return t1 +end + +---@type XBows +XBows = { + pvp = minetest.settings:get_bool('enable_pvp') or false, + creative = minetest.settings:get_bool('creative_mode') or false, + mesecons = minetest.get_modpath('mesecons'), + playerphysics = minetest.get_modpath('playerphysics'), + player_monoids = minetest.get_modpath('player_monoids'), + i3 = minetest.get_modpath('i3'), + unified_inventory = minetest.get_modpath('unified_inventory'), + u_skins = minetest.get_modpath('u_skins'), + wardrobe = minetest.get_modpath('wardrobe'), + _3d_armor = minetest.get_modpath('3d_armor'), + skinsdb = minetest.get_modpath('skinsdb'), + player_api = minetest.get_modpath('player_api'), + registered_bows = {}, + registered_arrows = {}, + registered_quivers = {}, + registered_particle_spawners = {}, + registered_entities = {}, + player_bow_sneak = {}, + settings = { + x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false), + x_bows_show_damage_numbers = minetest.settings:get_bool('x_bows_show_damage_numbers', false), + x_bows_show_3d_quiver = minetest.settings:get_bool('x_bows_show_3d_quiver', true) + }, + charge_sound_after_job = {}, + fallback_quiver = not minetest.global_exists('sfinv') + and not minetest.global_exists('unified_inventory') + and not minetest.global_exists('i3') +} + +XBows.__index = XBows + +---@type XBowsQuiver +XBowsQuiver = { + hud_item_ids = {}, + after_job = {}, + quiver_empty_state = {} +} +XBowsQuiver.__index = XBowsQuiver +setmetatable(XBowsQuiver, XBows) + + +---@type XBowsEntityDef +local XBowsEntityDef = {} +XBowsEntityDef.__index = XBowsEntityDef +setmetatable(XBowsEntityDef, XBows) + +---create UUID +---@return string +function XBows.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 + +---Check if creative is enabled or if player has creative priv +---@param self XBows +---@param name string +---@return boolean +function XBows.is_creative(self, name) + return self.creative or minetest.check_player_privs(name, { creative = true }) +end + +---Updates `allowed_ammunition` definition on already registered item, so MODs can add new ammunitions to this list. +---@param self XBows +---@param name string +---@param allowed_ammunition string[] +---@return nil +function XBows.update_bow_allowed_ammunition(self, name, allowed_ammunition) + local _name = 'x_bows:' .. name + local def = self.registered_bows[_name] + + if not def then + return + end + + local def_copy = table.copy(def) + + minetest.unregister_item(_name) + + for _, v in ipairs(allowed_ammunition) do + table.insert(def_copy.custom.allowed_ammunition, v) + end + + self:register_bow(name, def_copy, true) +end + +---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also +---@param self XBows +---@param player ObjectRef Player Ref +---@param includeWielded? boolean Will include reset for wielded bow also. default: `false` +---@return nil +function XBows.reset_charged_bow(self, player, includeWielded) + local _includeWielded = includeWielded or false + local inv = player:get_inventory() + + if not inv then + return + end + + local inv_list = inv:get_list('main') + + for i, st in ipairs(inv_list) do + local st_name = st:get_name() + local x_bows_registered_bow_def = self.registered_bows[st_name] + local reset = _includeWielded or player:get_wield_index() ~= i + + if not st:is_empty() + and x_bows_registered_bow_def + and reset + and minetest.get_item_group(st_name, 'bow_charged') ~= 0 + then + local item_meta = st:get_meta() + local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string'))) + + --return arrow + if arrow_itemstack and not self: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_bow_def.custom.name, + count = st:get_count(), + wear = st:get_wear() + })) + end + end +end + +---Register bows +---@param self XBows +---@param name string +---@param def ItemDef | BowItemDefCustom +---@param override? boolean MOD everride +---@return boolean|nil +function XBows.register_bow(self, name, def, override) + if name == nil or name == '' then + return false + end + + local mod_name = def.custom.mod_name or 'x_bows' + def.custom.name = mod_name .. ':' .. name + def.custom.name_charged = mod_name .. ':' .. name .. '_charged' + def.short_description = def.short_description + def.description = override and def.short_description or (def.description or name) + def.custom.uses = def.custom.uses or 150 + def.groups = mergeTables({ bow = 1, flammable = 1, enchantability = 1 }, def.groups or {}) + def.custom.groups_charged = mergeTables( + { bow_charged = 1, flammable = 1, not_in_creative_inventory = 1 }, + def.groups or {} + ) + def.custom.strength = def.custom.strength or 30 + def.custom.allowed_ammunition = def.custom.allowed_ammunition or nil + def.custom.sound_load = def.custom.sound_load or 'x_bows_bow_load' + def.custom.sound_hit = def.custom.sound_hit or 'x_bows_arrow_hit' + def.custom.sound_shoot = def.custom.sound_shoot or 'x_bows_bow_shoot' + def.custom.sound_shoot_crit = def.custom.sound_shoot_crit or 'x_bows_bow_shoot_crit' + def.custom.gravity = def.custom.gravity or -10 + + if def.custom.crit_chance then + def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Critical Arrow Chance') .. ': ' + .. (1 / def.custom.crit_chance) * 100 .. '%') + end + + def.description = def.description .. '\n' .. minetest.colorize('#00BFFF', S('Strength') .. ': ' + .. def.custom.strength) + + if def.custom.allowed_ammunition then + local allowed_amm_desc = table.concat(def.custom.allowed_ammunition, '\n') + + if allowed_amm_desc ~= '' then + def.description = def.description .. '\n' .. S('Allowed ammunition') .. ':\n' .. allowed_amm_desc + else + def.description = def.description .. '\n' .. S('Allowed ammunition') .. ': ' .. S('none') + end + end + + self.registered_bows[def.custom.name] = def + self.registered_bows[def.custom.name_charged] = def + + ---not charged bow + minetest.register_tool(override and ':' .. def.custom.name or def.custom.name, { + description = def.description, + inventory_image = def.inventory_image or 'x_bows_bow_wood.png', + wield_image = def.wield_image or def.inventory_image, + groups = def.groups, + wield_scale = { x = 2, y = 2, z = 1.5 }, + ---@param itemstack ItemStack + ---@param placer ObjectRef|nil + ---@param pointed_thing PointedThingDef + ---@return ItemStack|nil + on_place = function(itemstack, placer, pointed_thing) + if placer then + return self:load(itemstack, placer, pointed_thing) + end + end, + ---@param itemstack ItemStack + ---@param user ObjectRef|nil + ---@param pointed_thing PointedThingDef + ---@return ItemStack|nil + on_secondary_use = function(itemstack, user, pointed_thing) + if user then + return self:load(itemstack, user, pointed_thing) + end + end + }) + + ---charged bow + minetest.register_tool(override and ':' .. def.custom.name_charged or def.custom.name_charged, { + description = def.description, + inventory_image = def.custom.inventory_image_charged or 'x_bows_bow_wood_charged.png', + wield_image = def.custom.wield_image_charged or def.custom.inventory_image_charged, + groups = def.custom.groups_charged, + wield_scale = { x = 2, y = 2, z = 1.5 }, + range = 0, + ---@param itemstack ItemStack + ---@param user ObjectRef|nil + ---@param pointed_thing PointedThingDef + ---@return ItemStack|nil + on_use = function(itemstack, user, pointed_thing) + if user then + return self:shoot(itemstack, user, pointed_thing) + end + end, + ---@param itemstack ItemStack + ---@param dropper ObjectRef|nil + ---@param pos Vector + ---@return ItemStack|nil + on_drop = function(itemstack, dropper, pos) + if dropper then + local item_meta = itemstack:get_meta() + local arrow_itemstack = ItemStack(minetest.deserialize(item_meta:get_string('arrow_itemstack_string'))) + + ---return arrow + if arrow_itemstack and not self: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.custom.name) + ---returns leftover itemstack + return minetest.item_drop(itemstack, dropper, pos) + end + end + }) + + ---recipes + if def.custom.recipe then + minetest.register_craft({ + output = def.custom.name, + recipe = def.custom.recipe + }) + end + + ---fuel recipe + if def.custom.fuel_burntime then + minetest.register_craft({ + type = 'fuel', + recipe = def.custom.name, + burntime = def.custom.fuel_burntime, + }) + end +end + +---Register arrows +---@param self XBows +---@param name string +---@param def ItemDef | ArrowItemDefCustom +---@return boolean|nil +function XBows.register_arrow(self, name, def) + if name == nil or name == '' then + return false + end + + local mod_name = def.custom.mod_name or 'x_bows' + def.custom.name = mod_name .. ':' .. name + def.description = def.description or name + def.short_description = def.short_description or name + def.custom.tool_capabilities = def.custom.tool_capabilities or { + full_punch_interval = 1, + max_drop_level = 0, + damage_groups = { fleshy = 2 } + } + def.custom.description_abilities = minetest.colorize('#00FF00', S('Damage') .. ': ' + .. def.custom.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', S('Charge Time') .. ': ' + .. def.custom.tool_capabilities.full_punch_interval .. 's') + def.groups = mergeTables({ arrow = 1, flammable = 1 }, def.groups or {}) + def.custom.particle_effect = def.custom.particle_effect or 'arrow' + def.custom.particle_effect_crit = def.custom.particle_effect_crit or 'arrow_crit' + def.custom.particle_effect_fast = def.custom.particle_effect_fast or 'arrow_fast' + def.custom.projectile_entity = def.custom.projectile_entity or 'x_bows:arrow_entity' + def.custom.on_hit_node = def.custom.on_hit_node or nil + def.custom.on_hit_entity = def.custom.on_hit_entity or nil + def.custom.on_hit_player = def.custom.on_hit_player or nil + def.custom.on_after_activate = def.custom.on_after_activate or nil + + self.registered_arrows[def.custom.name] = def + + minetest.register_craftitem(def.custom.name, { + description = def.description .. '\n' .. def.custom.description_abilities, + short_description = def.short_description, + inventory_image = def.inventory_image, + groups = def.groups + }) + + ---recipes + if def.custom.recipe then + minetest.register_craft({ + output = def.custom.name .. ' ' .. (def.custom.craft_count or 4), + recipe = def.custom.recipe + }) + end + + ---fuel recipe + if def.custom.fuel_burntime then + minetest.register_craft({ + type = 'fuel', + recipe = def.custom.name, + burntime = def.custom.fuel_burntime, + }) + end +end + +---Register quivers +---@param self XBows +---@param name string +---@param def ItemDef | QuiverItemDefCustom +---@return boolean|nil +function XBows.register_quiver(self, name, def) + if name == nil or name == '' then + return false + end + + def.custom.name = 'x_bows:' .. name + def.custom.name_open = 'x_bows:' .. name .. '_open' + def.description = def.description or name + def.short_description = def.short_description or name + def.groups = mergeTables({ quiver = 1, flammable = 1 }, def.groups or {}) + def.custom.groups_charged = mergeTables({ + quiver = 1, quiver_open = 1, flammable = 1, not_in_creative_inventory = 1 + }, + def.groups or {} + ) + + if def.custom.faster_arrows then + def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') .. + ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%') + def.short_description = def.short_description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') .. + ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%') + end + + if def.custom.add_damage then + def.description = def.description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') .. + ': +' .. def.custom.add_damage) + def.short_description = def.short_description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') .. + ': +' .. def.custom.add_damage) + end + + self.registered_quivers[def.custom.name] = def + self.registered_quivers[def.custom.name_open] = def + + ---closed quiver + minetest.register_tool(def.custom.name, { + description = def.description, + short_description = def.short_description, + inventory_image = def.inventory_image or 'x_bows_quiver.png', + wield_image = def.wield_image or 'x_bows_quiver.png', + groups = def.groups, + ---@param itemstack ItemStack + ---@param user ObjectRef|nil + ---@param pointed_thing PointedThingDef + ---@return ItemStack|nil + on_secondary_use = function(itemstack, user, pointed_thing) + if user then + return self:open_quiver(itemstack, user) + end + 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 self:open_quiver(itemstack, placer) + end + }) + + ---open quiver + minetest.register_tool(def.custom.name_open, { + description = def.description, + short_description = def.short_description, + inventory_image = def.custom.inventory_image_open or 'x_bows_quiver_open.png', + wield_image = def.custom.wield_image_open or 'x_bows_quiver_open.png', + groups = def.custom.groups_charged, + ---@param itemstack ItemStack + ---@param dropper ObjectRef|nil + ---@param pos Vector + ---@return ItemStack + on_drop = function(itemstack, dropper, pos) + if not dropper then + return itemstack + end + + local replace_item = XBowsQuiver:get_replacement_item(itemstack, 'x_bows:quiver') + return minetest.item_drop(replace_item, dropper, pos) + end + }) + + ---recipes + if def.custom.recipe then + minetest.register_craft({ + output = def.custom.name, + recipe = def.custom.recipe + }) + end + + ---fuel recipe + if def.custom.fuel_burntime then + minetest.register_craft({ + type = 'fuel', + recipe = def.custom.name, + burntime = def.custom.fuel_burntime, + }) + end +end + +---Load bow +---@param self XBows +---@param itemstack ItemStack +---@param user ObjectRef +---@param pointed_thing PointedThingDef +---@return ItemStack +function XBows.load(self, itemstack, user, pointed_thing) + local player_name = user:get_player_name() + local inv = user:get_inventory() --[[@as InvRef]] + local bow_name = itemstack:get_name() + local bow_def = self.registered_bows[bow_name] + ---@alias ItemStackArrows {["stack"]: ItemStack, ["idx"]: number|integer}[] + ---@type ItemStackArrows + local itemstack_arrows = {} + + ---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 + + ---find itemstack arrow in quiver + local quiver_result = XBowsQuiver:get_itemstack_arrow_from_quiver(user) + local itemstack_arrow = quiver_result.found_arrow_stack + + if itemstack_arrow then + ---we got arrow from quiver + local itemstack_arrow_meta = itemstack_arrow:get_meta() + + itemstack_arrow_meta:set_int('is_arrow_from_quiver', 1) + itemstack_arrow_meta:set_int('found_arrow_stack_idx', quiver_result.found_arrow_stack_idx) + itemstack_arrow_meta:set_string('quiver_name', quiver_result.quiver_name) + itemstack_arrow_meta:set_string('quiver_id', quiver_result.quiver_id) + else + if not inv:is_empty('x_bows:arrow_inv') then + XBowsQuiver:udate_or_create_hud(user, inv:get_list('x_bows:arrow_inv')) + else + ---no ammo (fake stack) + XBowsQuiver:udate_or_create_hud(user, { + ItemStack({ name = 'x_bows:no_ammo' }) + }) + end + + ---find itemstack arrow in players inventory + local arrow_stack = inv:get_stack('x_bows:arrow_inv', 1) + local is_allowed_ammunition = self:is_allowed_ammunition(bow_name, arrow_stack:get_name()) + + if self.registered_arrows[arrow_stack:get_name()] and is_allowed_ammunition then + table.insert(itemstack_arrows, { stack = arrow_stack, idx = 1 }) + end + + ---if everything else fails + if self.fallback_quiver then + local inv_list = inv:get_list('main') + + for i, st in ipairs(inv_list) do + local st_name = st:get_name() + + if not st:is_empty() and self.registered_arrows[st_name] then + local _is_allowed_ammunition = self:is_allowed_ammunition(bow_name, st_name) + + if self.registered_arrows[st_name] and _is_allowed_ammunition then + table.insert(itemstack_arrows, { stack = st, idx = i }) + end + end + end + end + + -- take 1st found arrow in the list + itemstack_arrow = #itemstack_arrows > 0 and itemstack_arrows[1].stack or nil + end + + if itemstack_arrow and bow_def then + local _tool_capabilities = self.registered_arrows[itemstack_arrow:get_name()].custom.tool_capabilities + + ---@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() + + 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() + + 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())) + + wielded_item:set_name(v_bow_name .. '_charged') + v_user:set_wielded_item(wielded_item) + + if not self: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('x_bows:arrow_inv', 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 self.charge_sound_after_job[player_name] then + for _, v in pairs(self.charge_sound_after_job[player_name]) do + v:cancel() + end + + self.charge_sound_after_job[player_name] = {} + else + self.charge_sound_after_job[player_name] = {} + end + + ---sound plays when charge time reaches full punch interval time + table.insert(self.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(bow_def.custom.sound_load, { + to_player = player_name, + gain = 0.6 + }) + + return itemstack + end + + return itemstack +end + +---Shoot bow +---@param self XBows +---@param itemstack ItemStack +---@param user ObjectRef +---@param pointed_thing? PointedThingDef +---@return ItemStack +function XBows.shoot(self, itemstack, user, pointed_thing) + 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 found_arrow_stack_idx = arrow_itemstack_meta:get_int('found_arrow_stack_idx') + local quiver_id = arrow_itemstack_meta:get_string('quiver_id') + local detached_inv = XBowsQuiver:get_or_create_detached_inv( + quiver_id, + user:get_player_name() + ) + + ---Handle HUD and 3d Quiver + if is_arrow_from_quiver == 1 then + XBowsQuiver:udate_or_create_hud(user, detached_inv:get_list('main'), found_arrow_stack_idx) + + if detached_inv:is_empty('main') then + XBowsQuiver:show_3d_quiver(user, { is_empty = true }) + else + XBowsQuiver:show_3d_quiver(user) + end + else + local inv = user:get_inventory() --[[@as InvRef]] + if not inv:is_empty('x_bows:arrow_inv') then + XBowsQuiver:udate_or_create_hud(user, inv:get_list('x_bows:arrow_inv')) + else + ---no ammo (fake stack just for the HUD) + XBowsQuiver:udate_or_create_hud(user, { + ItemStack({ name = 'x_bows:no_ammo' }) + }) + end + end + + local x_bows_registered_arrow_def = self.registered_arrows[arrow_name] + + if not x_bows_registered_arrow_def then + return itemstack + end + + local bow_name_charged = itemstack:get_name() + ---Bow + local x_bows_registered_bow_charged_def = self.registered_bows[bow_name_charged] + local bow_name = x_bows_registered_bow_charged_def.custom.name + local uses = x_bows_registered_bow_charged_def.custom.uses + local crit_chance = x_bows_registered_bow_charged_def.custom.crit_chance + ---Arrow + local projectile_entity = x_bows_registered_arrow_def.custom.projectile_entity + ---Quiver + local x_bows_registered_quiver_def = self.registered_quivers[quiver_name] + + local _tool_capabilities = x_bows_registered_arrow_def.custom.tool_capabilities + local quiver_xbows_def = x_bows_registered_quiver_def + + ---X Enchanting + local x_enchanting = minetest.deserialize(meta:get_string('x_enchanting')) or {} + + ---@type EnityStaticDataAttrDef + local staticdata = { + _arrow_name = arrow_name, + _bow_name = bow_name, + _user_name = user:get_player_name(), + _is_critical_hit = false, + _tool_capabilities = _tool_capabilities, + _tflp = tflp, + _add_damage = 0, + _x_enchanting = x_enchanting + } + + ---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 + + ---speed multiply + if quiver_xbows_def and quiver_xbows_def.custom.faster_arrows and quiver_xbows_def.custom.faster_arrows > 1 then + staticdata._faster_arrows_multiplier = quiver_xbows_def.custom.faster_arrows + end + + ---add quiver damage + if quiver_xbows_def and quiver_xbows_def.custom.add_damage and quiver_xbows_def.custom.add_damage > 0 then + staticdata._add_damage = staticdata._add_damage + quiver_xbows_def.custom.add_damage + end + + ---sound + local sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot + if staticdata._is_critical_hit then + sound_name = x_bows_registered_bow_charged_def.custom.sound_shoot_crit + end + + ---stop punching close objects/nodes when shooting + minetest.after(0.2, function() + local wield_item = user:get_wielded_item() + + if wield_item:get_count() > 0 and wield_item:get_name() == itemstack:get_name() then + local new_stack = ItemStack(mergeTables(itemstack:to_table(), { name = bow_name })) + user:set_wielded_item(new_stack) + end + end) + + local player_pos = user:get_pos() + local obj = minetest.add_entity( + { + x = player_pos.x, + y = player_pos.y + 1.5, + z = player_pos.z + }, + projectile_entity, + minetest.serialize(staticdata) + ) + + if not obj then + return itemstack + end + + minetest.sound_play(sound_name, { + gain = 0.3, + pos = user:get_pos(), + max_hear_distance = 10 + }) + + if not self:is_creative(user:get_player_name()) then + itemstack:add_wear(65535 / uses) + end + + if itemstack:get_count() == 0 then + minetest.sound_play('default_tool_breaks', { + gain = 0.3, + pos = user:get_pos(), + max_hear_distance = 10 + }) + end + + return itemstack +end + +---Add new particle to XBow registration +---@param self XBows +---@param name string +---@param def ParticlespawnerDef|ParticlespawnerDefCustom +---@return nil +function XBows.register_particle_effect(self, name, def) + if self.registered_particle_spawners[name] then + minetest.log('warning', 'Particle effect "' .. name .. '" already exists and will not be overwritten.') + return + end + + self.registered_particle_spawners[name] = def +end + +---Get particle effect from registered spawners table +---@param self XBows +---@param name string +---@param pos Vector +---@return number|boolean +function XBows.get_particle_effect_for_arrow(self, name, pos) + local def = self.registered_particle_spawners[name] + + if not def then + minetest.log('warning', 'Particle effect "' .. name .. '" is not registered.') + return false + end + + def.custom = def.custom or {} + def.minpos = def.custom.minpos and vector.add(pos, def.custom.minpos) or pos + def.maxpos = def.custom.maxpos and vector.add(pos, def.custom.maxpos) or pos + + return minetest.add_particlespawner(def--[[@as ParticlespawnerDef]] ) +end + +---Check if ammunition is allowed to charge this weapon +---@param self XBows +---@param weapon_name string +---@param ammo_name string +---@return boolean +function XBows.is_allowed_ammunition(self, weapon_name, ammo_name) + local x_bows_weapon_def = self.registered_bows[weapon_name] + + if not x_bows_weapon_def then + return false + end + + if not x_bows_weapon_def.custom.allowed_ammunition then + return true + end + + if #x_bows_weapon_def.custom.allowed_ammunition == 0 then + return false + end + + return table_contains(x_bows_weapon_def.custom.allowed_ammunition, ammo_name) +end + +---- +--- ENTITY API +---- + +---Gets total armor level from 3d armor +---@param player ObjectRef +---@return integer +local function get_3d_armor_armor(player) + 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 + + armor_total = armor.def[player:get_player_name()].level + + return armor_total +end + +---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) +end + +---Function receive a "luaentity" table as `self`. Called when the object is instantiated. +---@param self EntityDef|EntityDefCustom|XBows +---@param selfObj EnityCustomAttrDef +---@param staticdata string +---@param dtime_s? integer|number +---@return nil +function XBowsEntityDef.on_activate(self, selfObj, staticdata, dtime_s) + if not selfObj or not staticdata or staticdata == '' then + selfObj.object:remove() + return + end + + local _staticdata = minetest.deserialize(staticdata) --[[@as EnityStaticDataAttrDef]] + + -- set/reset - do not inherit from previous entity table + selfObj._velocity = { x = 0, y = 0, z = 0 } + selfObj._old_pos = nil + selfObj._attached = false + selfObj._attached_to = { + type = '', + pos = nil + } + selfObj._has_particles = false + selfObj._lifetimer = 60 + selfObj._nodechecktimer = 0.5 + selfObj._is_drowning = false + selfObj._in_liquid = false + selfObj._shot_from_pos = selfObj.object:get_pos() + selfObj._arrow_name = _staticdata._arrow_name + selfObj._bow_name = _staticdata._bow_name + selfObj._user_name = _staticdata._user_name + selfObj._user = minetest.get_player_by_name(_staticdata._user_name) + selfObj._tflp = _staticdata._tflp + selfObj._tool_capabilities = _staticdata._tool_capabilities + selfObj._is_critical_hit = _staticdata._is_critical_hit + selfObj._faster_arrows_multiplier = _staticdata._faster_arrows_multiplier + selfObj._add_damage = _staticdata._add_damage + selfObj._caused_damage = 0 + selfObj._caused_knockback = 0 + + local x_bows_registered_arrow_def = self.registered_arrows[selfObj._arrow_name] + selfObj._arrow_particle_effect = x_bows_registered_arrow_def.custom.particle_effect + selfObj._arrow_particle_effect_crit = x_bows_registered_arrow_def.custom.particle_effect_crit + selfObj._arrow_particle_effect_fast = x_bows_registered_arrow_def.custom.particle_effect_fast + + ---Bow Def + local x_bows_registered_bow_def = self.registered_bows[selfObj._bow_name] + selfObj._sound_hit = x_bows_registered_bow_def.custom.sound_hit + local bow_strength = x_bows_registered_bow_def.custom.strength + local acc_x_min = x_bows_registered_bow_def.custom.acc_x_min + local acc_y_min = x_bows_registered_bow_def.custom.acc_y_min + local acc_z_min = x_bows_registered_bow_def.custom.acc_z_min + local acc_x_max = x_bows_registered_bow_def.custom.acc_x_max + local acc_y_max = x_bows_registered_bow_def.custom.acc_y_max + local acc_z_max = x_bows_registered_bow_def.custom.acc_z_max + local gravity = x_bows_registered_bow_def.custom.gravity + local bow_strength_min = x_bows_registered_bow_def.custom.strength_min + local bow_strength_max = x_bows_registered_bow_def.custom.strength_max + + ---X Enchanting + selfObj._x_enchanting = _staticdata._x_enchanting + + ---acceleration + selfObj._player_look_dir = selfObj._user:get_look_dir() + + selfObj._acc_x = selfObj._player_look_dir.x + selfObj._acc_y = gravity + selfObj._acc_z = selfObj._player_look_dir.z + + if acc_x_min and acc_x_max then + selfObj._acc_x = math.random(acc_x_min, acc_x_max) + end + + if acc_y_min and acc_y_max then + selfObj._acc_y = math.random(acc_y_min, acc_y_max) + end + + if acc_z_min and acc_z_max then + selfObj._acc_z = math.random(acc_z_min, acc_z_max) + end + + ---strength + local strength_multiplier = selfObj._tflp + + if strength_multiplier > selfObj._tool_capabilities.full_punch_interval then + strength_multiplier = 1 + + ---faster arrow, only on full punch interval + if selfObj._faster_arrows_multiplier then + strength_multiplier = strength_multiplier + (strength_multiplier / selfObj._faster_arrows_multiplier) + end + end + + if bow_strength_max and bow_strength_min then + bow_strength = math.random(bow_strength_min, bow_strength_max) + end + + selfObj._strength = bow_strength * strength_multiplier + + ---rotation factor + local x_bows_registered_entity_def = self.registered_entities[selfObj.name] + selfObj._rotation_factor = x_bows_registered_entity_def._custom.rotation_factor + + if type(selfObj._rotation_factor) == 'function' then + selfObj._rotation_factor = selfObj._rotation_factor() + end + + ---add infotext + selfObj.object:set_properties({ + infotext = selfObj._arrow_name, + }) + + ---idle animation + if x_bows_registered_entity_def and x_bows_registered_entity_def._custom.animations.idle then + selfObj.object:set_animation(unpack(x_bows_registered_entity_def._custom.animations.idle)--[[@as table]] ) + end + + ---counter, e.g. for initial values set `on_step` + selfObj._step_count = 0 + + ---Callbacks + local on_after_activate_callback = x_bows_registered_arrow_def.custom.on_after_activate + + if on_after_activate_callback then + on_after_activate_callback(selfObj) + end +end + +---Function receive a "luaentity" table as `self`. Called when the object dies. +---@param self XBows +---@param selfObj EnityCustomAttrDef +---@param killer ObjectRef|nil +---@return nil +function XBowsEntityDef.on_death(self, selfObj, killer) + if not selfObj._old_pos then + selfObj.object:remove() + return + end + + -- Infinity enchantment - arrows cannot be retrieved + if selfObj._x_enchanting.infinity and selfObj._x_enchanting.infinity.value > 0 then + return + end + + minetest.item_drop(ItemStack(selfObj._arrow_name), nil, vector.round(selfObj._old_pos)) +end + +--- Function receive a "luaentity" table as `self`. Called on every server tick, after movement and collision processing. +---`dtime`: elapsed time since last call. `moveresult`: table with collision info (only available if physical=true). +---@param self XBows +---@param selfObj EnityCustomAttrDef +---@param dtime number +---@return nil +function XBowsEntityDef.on_step(self, selfObj, dtime) + selfObj._step_count = selfObj._step_count + 1 + + if selfObj._step_count == 1 then + ---initialize + ---this has to be done here for raycast to kick-in asap + selfObj.object:set_velocity(vector.multiply(selfObj._player_look_dir, selfObj._strength)) + selfObj.object:set_acceleration({ x = selfObj._acc_x, y = selfObj._acc_y, z = selfObj._acc_z }) + selfObj.object:set_yaw(minetest.dir_to_yaw(selfObj._player_look_dir)) + end + + local pos = selfObj.object:get_pos() + selfObj._old_pos = selfObj._old_pos or pos + local ray = minetest.raycast(selfObj._old_pos, pos, true, true) + local pointed_thing = ray:next() + + selfObj._lifetimer = selfObj._lifetimer - dtime + selfObj._nodechecktimer = selfObj._nodechecktimer - dtime + + -- adjust pitch when flying + if not selfObj._attached then + local velocity = selfObj.object:get_velocity() + local v_rotation = selfObj.object:get_rotation() + local pitch = math.atan2(velocity.y, math.sqrt(velocity.x ^ 2 + velocity.z ^ 2)) + + selfObj.object:set_rotation({ + x = pitch, + y = v_rotation.y, + z = v_rotation.z + (selfObj._rotation_factor or math.pi / 2) + }) + end + + -- remove attached arrows after lifetime + if selfObj._lifetimer <= 0 then + selfObj.object:remove() + return + end + + -- add particles only when not attached + if not selfObj._attached and not selfObj._in_liquid then + selfObj._has_particles = true + + if selfObj._tflp >= selfObj._tool_capabilities.full_punch_interval then + if selfObj._is_critical_hit then + self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect_crit, selfObj._old_pos) + elseif selfObj._faster_arrows_multiplier then + self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect_fast, selfObj._old_pos) + else + self:get_particle_effect_for_arrow(selfObj._arrow_particle_effect, selfObj._old_pos) + end + end + end + + -- remove attached arrows after object dies + if not selfObj.object:get_attach() and selfObj._attached_to.type == 'object' then + selfObj.object:remove() + return + end + + -- arrow falls down when not attached to node any more + if selfObj._attached_to.type == 'node' and selfObj._attached and selfObj._nodechecktimer <= 0 then + local node = minetest.get_node(selfObj._attached_to.pos) + selfObj._nodechecktimer = 0.5 + + if not node then + return + end + + if node.name == 'air' then + selfObj.object:set_velocity({ x = 0, y = -3, z = 0 }) + selfObj.object:set_acceleration({ x = 0, y = -3, z = 0 }) + -- reset values + selfObj._attached = false + selfObj._attached_to.type = '' + selfObj._attached_to.pos = nil + selfObj.object:set_properties({ collisionbox = { 0, 0, 0, 0, 0, 0 } }) + + return + end + end + + while pointed_thing do + local ip_pos = pointed_thing.intersection_point + local in_pos = pointed_thing.intersection_normal + selfObj.pointed_thing = pointed_thing + + if pointed_thing.type == 'object' + and pointed_thing.ref ~= selfObj.object + and pointed_thing.ref:get_hp() > 0 + and ( + ( + pointed_thing.ref:is_player() + and pointed_thing.ref:get_player_name() ~= selfObj._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 selfObj.object:get_attach() == nil + and not selfObj._attached + then + if pointed_thing.ref:is_player() then + minetest.sound_play('x_bows_arrow_successful_hit', { + to_player = selfObj._user:get_player_name(), + gain = 0.3 + }) + else + minetest.sound_play(selfObj._sound_hit, { + to_player = selfObj._user:get_player_name(), + gain = 0.6 + }) + end + + selfObj.object:set_velocity({ x = 0, y = 0, z = 0 }) + selfObj.object:set_acceleration({ x = 0, y = 0, z = 0 }) + + -- calculate damage + local target_armor_groups = pointed_thing.ref:get_armor_groups() + local _damage = 0 + + if selfObj._add_damage then + -- add damage from quiver + _damage = _damage + selfObj._add_damage + end + + if selfObj._x_enchanting.power then + -- add damage from enchantment + _damage = _damage + _damage * (selfObj._x_enchanting.power.value / 100) + end + + for group, base_damage in pairs(selfObj._tool_capabilities.damage_groups) do + _damage = _damage + + base_damage + * limit(selfObj._tflp / selfObj._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 + + -- crits + if selfObj._is_critical_hit then + _damage = _damage * 2 + end + + -- knockback + local dir = vector.normalize(vector.subtract(selfObj._shot_from_pos, ip_pos)) + local distance = vector.distance(selfObj._shot_from_pos, ip_pos) + local knockback = minetest.calculate_knockback( + pointed_thing.ref, + selfObj.object, + selfObj._tflp, + { + full_punch_interval = selfObj._tool_capabilities.full_punch_interval, + damage_groups = { fleshy = _damage }, + }, + dir, + distance, + _damage + ) + + if selfObj._x_enchanting.punch then + -- add knockback from enchantment + -- the `punch.value` multiplier is too strong so divide it by half + knockback = knockback * (selfObj._x_enchanting.punch.value / 2) + + pointed_thing.ref:add_velocity({ + x = dir.x * knockback * -1, + y = 7, + z = dir.z * knockback * -1 + }) + else + pointed_thing.ref:add_velocity({ + x = dir.x * knockback * -1, + y = 5, + z = dir.z * knockback * -1 + }) + end + + pointed_thing.ref:punch( + selfObj.object, + selfObj._tflp, + { + full_punch_interval = selfObj._tool_capabilities.full_punch_interval, + damage_groups = { fleshy = _damage, knockback = knockback } + }, + { + x = dir.x * -1, + y = -7, + z = dir.z * -1 + } + ) + + selfObj._caused_damage = _damage + selfObj._caused_knockback = knockback + + XBows:show_damage_numbers(selfObj.object:get_pos(), _damage, selfObj._is_critical_hit) + + -- already dead (entity) + if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then + selfObj.object:remove() + return + end + + -- already dead (player) + if pointed_thing.ref:get_hp() <= 0 then + selfObj.object:remove() + return + end + + -- attach arrow prepare + local rotation = { x = 0, y = 0, z = 0 } + + 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) + 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) + 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) + 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) + 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) + 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) + end + + if not XBows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then + selfObj.object:remove() + return + end + + ---normalize arrow scale when attached to scaled entity + ---(prevents huge arrows when attached to scaled up entity models) + local obj_props = selfObj.object:get_properties() + local obj_to_props = pointed_thing.ref:get_properties() + local vs = vector.divide(obj_props.visual_size, obj_to_props.visual_size) + + selfObj.object:set_properties({ visual_size = vs }) + + -- attach arrow + local position = vector.subtract( + ip_pos, + pointed_thing.ref:get_pos() + ) + + if pointed_thing.ref:is_player() then + position = vector.multiply(position, 10) + end + + ---`after` here prevents visual glitch when the arrow still shows as huge for a split second + ---before the new calculated scale is applied + minetest.after(0, function() + selfObj.object:set_attach( + pointed_thing.ref, + '', + position, + rotation, + true + ) + end) + + selfObj._attached = true + selfObj._attached_to.type = pointed_thing.type + selfObj._attached_to.pos = position + + -- remove last arrow when too many already attached + local children = {} + local projectile_entity = self.registered_arrows[selfObj._arrow_name].custom.projectile_entity + + for _, object in ipairs(pointed_thing.ref:get_children()) do + if object:get_luaentity() and object:get_luaentity().name == projectile_entity then + table.insert(children, object) + end + end + + if #children >= 5 then + children[1]:remove() + end + + if pointed_thing.ref:is_player() then + local on_hit_player_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_player + + if on_hit_player_callback then + on_hit_player_callback(selfObj, pointed_thing) + end + else + local on_hit_entity_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_entity + + if on_hit_entity_callback then + on_hit_entity_callback(selfObj, pointed_thing) + end + end + + return + + elseif pointed_thing.type == 'node' and not selfObj._attached then + local node = minetest.get_node(pointed_thing.under) + local node_def = minetest.registered_nodes[node.name] + + if not node_def then + return + end + + selfObj._velocity = selfObj.object:get_velocity() + + if node_def.drawtype == 'liquid' and not selfObj._is_drowning then + selfObj._is_drowning = true + selfObj._in_liquid = true + local drag = 1 / (node_def.liquid_viscosity * 6) + selfObj.object:set_velocity(vector.multiply(selfObj._velocity, drag)) + selfObj.object:set_acceleration({ x = 0, y = -1.0, z = 0 }) + + XBows:get_particle_effect_for_arrow('bubble', selfObj._old_pos) + elseif selfObj._is_drowning then + selfObj._is_drowning = false + + if selfObj._velocity then + selfObj.object:set_velocity(selfObj._velocity) + end + + selfObj.object:set_acceleration({ x = 0, y = -9.81, z = 0 }) + end + + if XBows.mesecons and node.name == 'x_bows:target' then + local distance = vector.distance(pointed_thing.under, ip_pos) + distance = math.floor(distance * 100) / 100 + + -- 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 + + if node_def.walkable then + selfObj.object:set_velocity({ x = 0, y = 0, z = 0 }) + selfObj.object:set_acceleration({ x = 0, y = 0, z = 0 }) + selfObj.object:set_pos(ip_pos) + selfObj.object:set_rotation(selfObj.object:get_rotation()) + selfObj._attached = true + selfObj._attached_to.type = pointed_thing.type + selfObj._attached_to.pos = pointed_thing.under + selfObj.object:set_properties({ collisionbox = { -0.2, -0.2, -0.2, 0.2, 0.2, 0.2 } }) + + -- remove last arrow when too many already attached + local children = {} + local projectile_entity = self.registered_arrows[selfObj._arrow_name].custom.projectile_entity + + 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 == projectile_entity + then + table.insert(children, object) + end + end + + if #children >= 5 then + children[#children]:remove() + end + + ---Wiggle + local x_bows_registered_entity_def = self.registered_entities[selfObj.name] + if x_bows_registered_entity_def and x_bows_registered_entity_def._custom.animations.on_hit_node then + selfObj.object:set_animation( + unpack(x_bows_registered_entity_def._custom.animations.on_hit_node)--[[@as table]] + ) + end + + ---API callbacks + local on_hit_node_callback = self.registered_arrows[selfObj._arrow_name].custom.on_hit_node + + if on_hit_node_callback then + on_hit_node_callback(selfObj, pointed_thing) + end + + local new_pos = selfObj.object:get_pos() + + if new_pos then + minetest.add_particlespawner({ + amount = 5, + time = 0.25, + minpos = { x = new_pos.x - 0.4, y = new_pos.y + 0.2, z = new_pos.z - 0.4 }, + maxpos = { x = new_pos.x + 0.4, y = new_pos.y + 0.3, z = new_pos.z + 0.4 }, + minvel = { x = 0, y = 3, z = 0 }, + maxvel = { x = 0, y = 4, z = 0 }, + minacc = { x = 0, y = -28, z = 0 }, + maxacc = { x = 0, y = -32, z = 0 }, + minexptime = 1, + maxexptime = 1.5, + node = { name = node_def.name }, + collisiondetection = true, + object_collision = true, + }) + end + + minetest.sound_play(selfObj._sound_hit, { + pos = pointed_thing.under, + gain = 0.6, + max_hear_distance = 16 + }) + + return + end + end + pointed_thing = ray:next() + end + + selfObj._old_pos = pos +end + +---Function receive a "luaentity" table as `self`. Called when somebody punches the object. +---Note that you probably want to handle most punches using the automatic armor group system. +---Can return `true` to prevent the default damage mechanism. +---@param self XBows +---@param selfObj EnityCustomAttrDef +---@param puncher ObjectRef|nil +---@param time_from_last_punch number|integer|nil +---@param tool_capabilities ToolCapabilitiesDef +---@param dir Vector +---@param damage number|integer +---@return boolean +function XBowsEntityDef.on_punch(self, selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage) + local pos = selfObj.object:get_pos() + + if pos then + minetest.sound_play('default_dig_choppy', { + pos = pos, + gain = 0.4 + }) + end + + return false +end + +---Register new projectile entity +---@param self XBows +---@param name string +---@param def XBowsEntityDef +function XBows.register_entity(self, name, def) + def._custom = def._custom or {} + def._custom.animations = def._custom.animations or {} + + local mod_name = def._custom.mod_name or 'x_bows' + def._custom.name = mod_name .. ':' .. name + def.initial_properties = mergeTables({ + ---defaults + visual = 'wielditem', + collisionbox = { 0, 0, 0, 0, 0, 0 }, + selectionbox = { 0, 0, 0, 0, 0, 0 }, + physical = false, + textures = { 'air' }, + hp_max = 1, + visual_size = { x = 1, y = 1, z = 1 }, + glow = 1 + }, def.initial_properties or {}) + + def.on_death = function(selfObj, killer) + return XBowsEntityDef:on_death(selfObj, killer) + end + + if def._custom.on_death then + def.on_death = def._custom.on_death + end + + def.on_activate = function(selfObj, killer) + return XBowsEntityDef:on_activate(selfObj, killer) + end + + def.on_step = function(selfObj, dtime) + return XBowsEntityDef:on_step(selfObj, dtime) + end + + def.on_punch = function(selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage) + return XBowsEntityDef:on_punch(selfObj, puncher, time_from_last_punch, tool_capabilities, dir, damage) + end + + if def._custom.on_punch then + def.on_punch = def._custom.on_punch + end + + self.registered_entities[def._custom.name] = def + + minetest.register_entity(def._custom.name, { + initial_properties = def.initial_properties, + on_death = def.on_death, + on_activate = def.on_activate, + on_step = def.on_step, + on_punch = def.on_punch + }) +end + +---- +--- QUIVER API +---- + +---Close one or all open quivers in players inventory +---@param self XBowsQuiver +---@param player ObjectRef +---@param quiver_id? string If `nil` then all open quivers will be closed +---@return nil +function XBowsQuiver.close_quiver(self, 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 = self:get_replacement_item(st, 'x_bows:quiver') + player_inv:set_stack('main', i, replace_item) + break + else + local replace_item = self: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 self XBowsQuiver +---@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 XBowsQuiver.get_replacement_item(self, 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 self XBowsQuiver +---@param player ObjectRef +---@diagnostic disable-next-line: codestyle-check +---@return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number} +function XBowsQuiver.get_itemstack_arrow_from_quiver(self, player) + local player_inv = player:get_inventory() + local wielded_stack = player:get_wielded_item() + ---@type ItemStack|nil + local found_arrow_stack + local found_arrow_stack_idx = 1 + local prev_detached_inv_list = {} + local quiver_id + local quiver_name + + ---check quiver inventory slot + if player_inv and player_inv:contains_item('x_bows:quiver_inv', 'x_bows:quiver') then + local player_name = player:get_player_name() + local quiver_stack = player_inv:get_stack('x_bows:quiver_inv', 1) + local st_meta = quiver_stack:get_meta() + quiver_id = st_meta:get_string('quiver_id') + + local detached_inv = self: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 + local is_allowed_ammunition = self:is_allowed_ammunition(wielded_stack:get_name(), qst:get_name()) + + if is_allowed_ammunition then + quiver_name = quiver_stack:get_name() + found_arrow_stack = qst:take_item() + found_arrow_stack_idx = j + + ---X Enchanting + local wielded_stack_meta = wielded_stack:get_meta() + local is_infinity = wielded_stack_meta:get_float('is_infinity') + + if not self:is_creative(player_name) and is_infinity == 0 then + -- take item will be set + detached_inv:set_list('main', detached_inv_list) + self:save(detached_inv, player, true) + end + end + end + end + end + + if found_arrow_stack then + ---show HUD - quiver inventory + self:udate_or_create_hud(player, prev_detached_inv_list, found_arrow_stack_idx) + end + end + + if self.fallback_quiver then + ---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 = self: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 + local is_allowed_ammunition = self:is_allowed_ammunition( + wielded_stack:get_name(), + qst:get_name() + ) + + if is_allowed_ammunition then + quiver_name = st:get_name() + found_arrow_stack = qst:take_item() + found_arrow_stack_idx = j + + if not self:is_creative(player_name) then + detached_inv:set_list('main', detached_inv_list) + self:save(detached_inv, player, true) + end + end + end + end + end + end + + if found_arrow_stack then + ---show HUD - quiver inventory + self:udate_or_create_hud(player, prev_detached_inv_list, found_arrow_stack_idx) + + break + end + end + end + end + + return { + found_arrow_stack = found_arrow_stack, + quiver_id = quiver_id, + quiver_name = quiver_name, + found_arrow_stack_idx = found_arrow_stack_idx + } +end + +---Remove all added HUDs +---@param self XBowsQuiver +---@param player ObjectRef +---@return nil +function XBowsQuiver.remove_hud(self, player) + local player_name = player:get_player_name() + + if self.hud_item_ids[player_name] then + for _, v in pairs(self.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 + + self.hud_item_ids[player_name] = { + arrow_inv_img = {}, + stack_count = {} + } + else + self.hud_item_ids[player_name] = { + arrow_inv_img = {}, + stack_count = {} + } + end +end + +---@todo implement hud_change? +---Update or create quiver HUD +---@param self XBowsQuiver +---@param player ObjectRef +---@param inv_list ItemStack[] +---@param idx? number +---@return nil +function XBowsQuiver.udate_or_create_hud(self, player, inv_list, idx) + local _idx = idx or 1 + local player_name = player:get_player_name() + local selected_bg_added = false + local is_arrow = #inv_list == 1 + local item_def = minetest.registered_items['x_bows:quiver'] + local is_no_ammo = false + + if is_arrow then + item_def = minetest.registered_items[inv_list[1]:get_name()] + is_no_ammo = inv_list[1]:get_name() == 'x_bows:no_ammo' + end + + if is_no_ammo then + item_def = { + inventory_image = 'x_bows_arrow_slot.png', + short_description = S('No Ammo') .. '!' + } + end + + if not item_def then + return + end + + ---cancel previous timeouts and reset + if self.after_job[player_name] then + for _, v in pairs(self.after_job[player_name]) do + v:cancel() + end + + self.after_job[player_name] = {} + else + self.after_job[player_name] = {} + end + + self:remove_hud(player) + + ---title image + self.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 = item_def.inventory_image, + scale = { x = 4, y = 4 }, + alignment = 0, + }) + + ---title copy + self.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 = item_def.short_description, + alignment = 0, + scale = { x = 100, y = 30 }, + number = 0xFFFFFF, + }) + + ---hotbar bg + self.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 = is_arrow and 'x_bows_single_hotbar.png' or '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 is_no_ammo then + found_arrow_stack_def = item_def + end + + if not selected_bg_added and j == _idx then + selected_bg_added = true + + ---ui selected bg + self.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_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(self.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(self.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 = is_no_ammo and 0 or qst:get_count(), + alignment = -1, + scale = { x = 50, y = 10 }, + number = 0xFFFFFF, + })) + end + end + end + + ---@param v_player ObjectRef + table.insert(self.after_job[player_name], minetest.after(10, function(v_player) + self:remove_hud(v_player) + end, player)) +end + +---Get existing detached inventory or create new one +---@param self XBowsQuiver +---@param quiver_id string +---@param player_name string +---@param quiver_items? string +---@return InvRef +function XBowsQuiver.get_or_create_detached_inv(self, 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 self: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 self: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 self: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) + self: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) + local quiver_inv_st = player:get_inventory():get_stack('x_bows:quiver_inv', 1) + + if quiver_inv_st and quiver_inv_st:get_meta():get_string('quiver_id') == inv:get_location().name then + if inv:is_empty('main') then + self:show_3d_quiver(player, { is_empty = true }) + else + self:show_3d_quiver(player) + end + end + + self: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) + local quiver_inv_st = player:get_inventory():get_stack('x_bows:quiver_inv', 1) + + if quiver_inv_st and quiver_inv_st:get_meta():get_string('quiver_id') == inv:get_location().name then + if inv:is_empty('main') then + self:show_3d_quiver(player, { is_empty = true }) + else + self:show_3d_quiver(player) + end + end + + self: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 + self:set_string_to_inv(detached_inv, quiver_items) + end + + return detached_inv +end + +---Create formspec +---@param self XBowsQuiver +---@param name string name of the form +---@return string +function XBowsQuiver.get_formspec(self, 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]' + } + + if minetest.global_exists('default') then + formspec[#formspec + 1] = default.get_hotbar_bg(0, height + 0.85) + end + + --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 + 1] = 'image[' .. px .. ',' .. py .. ';1,1;x_bows_arrow_slot.png]' + end + + px = px + 1 + end + + formspec = table.concat(formspec, '') + + return formspec +end + +---Convert inventory of itemstacks to serialized string +---@param self XBowsQuiver +---@param inv InvRef +---@return {['inv_string']: string, ['content_description']: string} +function XBowsQuiver.get_string_from_inv(self, 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 '\n' .. S('Empty') or content_description + } +end + +---Set items from serialized string to inventory +---@param self XBowsQuiver +---@param inv InvRef inventory to add items to +---@param str string previously stringified inventory of itemstacks +---@return nil +function XBowsQuiver.set_string_to_inv(self, 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 self XBowsQuiver +---@param inv InvRef +---@param player ObjectRef +---@param quiver_is_closed? boolean +---@return nil +function XBowsQuiver.save(self, inv, player, quiver_is_closed) + local player_inv = player:get_inventory() --[[@as InvRef]] + local inv_loc = inv:get_location() + local quiver_item_name = quiver_is_closed and 'x_bows:quiver' or 'x_bows:quiver_open' + local player_quiver_inv_stack = player_inv:get_stack('x_bows:quiver_inv', 1) + + if not player_quiver_inv_stack:is_empty() + and player_quiver_inv_stack:get_meta():get_string('quiver_id') == inv_loc.name + then + local st_meta = player_quiver_inv_stack:get_meta() + ---save inventory items in quiver item meta + local string_from_inventory_result = self:get_string_from_inv(inv) + + st_meta:set_string('quiver_items', string_from_inventory_result.inv_string) + + ---update description + local new_description = player_quiver_inv_stack:get_short_description() .. '\n' .. + string_from_inventory_result.content_description .. '\n' + + st_meta:set_string('description', new_description) + player_inv:set_stack('x_bows:quiver_inv', 1, player_quiver_inv_stack) + elseif player_inv and player_inv:contains_item('main', quiver_item_name) then + ---find matching quiver item in players inventory with the open formspec name + 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 = self: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 self XBowsQuiver +---@param inv InvRef +---@param player ObjectRef +---@return boolean +function XBowsQuiver.quiver_can_allow(self, inv, player) + local player_inv = player:get_inventory() --[[@as InvRef]] + local inv_loc = inv:get_location() + local player_quiver_inv_stack = player_inv:get_stack('x_bows:quiver_inv', 1) + + if not player_quiver_inv_stack:is_empty() + and player_quiver_inv_stack:get_meta():get_string('quiver_id') == inv_loc.name + then + ---find quiver in player `quiver_inv` inv list + return true + elseif player_inv and player_inv:contains_item('main', 'x_bows:quiver_open') then + ---find quiver in player `main` inv list + ---matching quiver item in players inventory with the open formspec name + 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 self XBows +---@param itemstack ItemStack +---@param user ObjectRef +---@return ItemStack +function XBows.open_quiver(self, 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() .. '_' .. self.uuid() + itemstack_meta:set_string('quiver_id', quiver_id) + end + + local quiver_items = itemstack_meta:get_string('quiver_items') + + XBowsQuiver:get_or_create_detached_inv(quiver_id, pname, quiver_items) + + ---show open variation of quiver + local replace_item = XBowsQuiver: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, XBowsQuiver:get_formspec(quiver_id)) + return itemstack +end + +---Register sfinv page +---@param self XBowsQuiver +function XBowsQuiver.sfinv_register_page(self) + sfinv.register_page('x_bows:quiver_page', { + title = 'X Bows', + get = function(this, player, context) + local formspec = { + ---arrow + 'label[0,0;' .. minetest.formspec_escape(S('Arrows')) .. ':]', + 'list[current_player;x_bows:arrow_inv;0,0.5;1,1;]', + 'image[0,0.5;1,1;x_bows_arrow_slot.png;]', + 'listring[current_player;x_bows:arrow_inv]', + 'listring[current_player;main]', + ---quiver + 'label[3.5,0;' .. minetest.formspec_escape(S('Quiver')) .. ':]', + 'list[current_player;x_bows:quiver_inv;3.5,0.5;1,1;]', + 'image[3.5,0.5;1,1;x_bows_quiver_slot.png]', + 'listring[current_player;x_bows:quiver_inv]', + 'listring[current_player;main]', + } + + local player_inv = player:get_inventory() --[[@as InvRef]] + context._itemstack_arrow = player_inv:get_stack('x_bows:arrow_inv', 1) + context._itemstack_quiver = player_inv:get_stack('x_bows:quiver_inv', 1) + + if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then + local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()] + local short_description = context._itemstack_arrow:get_short_description() + + if x_bows_registered_arrow_def and short_description then + formspec[#formspec + 1] = 'label[0,1.5;' .. + minetest.formspec_escape(short_description) .. '\n' .. + minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']' + end + end + + + if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then + local st_meta = context._itemstack_quiver:get_meta() + local quiver_id = st_meta:get_string('quiver_id') + local short_description = context._itemstack_quiver:get_short_description() + + ---description + if short_description then + formspec[#formspec + 1] = 'label[3.5,1.5;' .. + minetest.formspec_escape(short_description) .. ']' + end + + formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;4.5,0.5;3,1;]' + formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]' + formspec[#formspec + 1] = 'listring[current_player;main]' + end + + return sfinv.make_formspec(player, context, table.concat(formspec, ''), true) + end + }) +end + +---Register i3 page +function XBowsQuiver.i3_register_page(self) + i3.new_tab('x_bows:quiver_page', { + description = 'X Bows', + formspec = function(player, data, fs) + local formspec = { + ---arrow + 'label[0.5,1;' .. minetest.formspec_escape(S('Arrows')) .. ':]', + 'list[current_player;x_bows:arrow_inv;0.5,1.5;1,1;]', + 'listring[current_player;x_bows:arrow_inv]', + 'listring[current_player;main]', + ---quiver + 'label[5,1;' .. minetest.formspec_escape(S('Quiver')) .. ':]', + 'list[current_player;x_bows:quiver_inv;5,1.5;1,1;]', + 'listring[current_player;x_bows:quiver_inv]', + 'listring[current_player;main]', + ---main + 'background9[0,0;10.23,12;i3_bg_full.png;false;12]', + 'listcolors[#bababa50;#bababa99]', + 'style_type[box;colors=#77777710,#77777710,#777,#777]', + 'box[0.22,6.9;1,1;]', + 'box[1.32,6.9;1,1;]', + 'box[2.42,6.9;1,1;]', + 'box[3.52,6.9;1,1;]', + 'box[4.62,6.9;1,1;]', + 'box[5.72,6.9;1,1;]', + 'box[6.82,6.9;1,1;]', + 'box[7.92,6.9;1,1;]', + 'box[9.02,6.9;1,1;]', + 'style_type[list;size=1;spacing=0.1]', + 'list[current_player;main;0.22,6.9;9,1;]', + 'style_type[list;size=1;spacing=0.1,0.1]', + 'list[current_player;main;0.22,8.05;9,4;9]', + 'style_type[list;size=1;spacing=0.15]', + 'listring[current_player;craft]listring[current_player;main]' + } + + local context = {} + local player_inv = player:get_inventory() + context._itemstack_arrow = player_inv:get_stack('x_bows:arrow_inv', 1) + context._itemstack_quiver = player_inv:get_stack('x_bows:quiver_inv', 1) + + if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then + local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()] + + if x_bows_registered_arrow_def then + formspec[#formspec + 1] = 'label[0.5,3;' .. + minetest.formspec_escape(context._itemstack_arrow:get_short_description()) .. '\n' .. + minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']' + end + end + + + if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then + local st_meta = context._itemstack_quiver:get_meta() + local quiver_id = st_meta:get_string('quiver_id') + + ---description + formspec[#formspec + 1] = 'label[5,3;' .. + minetest.formspec_escape(context._itemstack_quiver:get_short_description()) .. ']' + formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;6.3,1.5;3,1;]' + formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]' + formspec[#formspec + 1] = 'listring[current_player;main]' + end + + formspec = table.concat(formspec, '') + + fs(formspec) + end + }) +end + +---Register i3 page +function XBowsQuiver.ui_register_page(self) + unified_inventory.register_page('x_bows:quiver_page', { + get_formspec = function(player, data, fs) + local formspec = { + unified_inventory.style_full.standard_inv_bg, + 'listcolors[#00000000;#00000000]', + ---arrow + 'label[0.5,0.5;' .. minetest.formspec_escape(S('Arrows')) .. ':]', + unified_inventory.single_slot(0.4, 0.9), + 'list[current_player;x_bows:arrow_inv;0.5,1;1,1;]', + 'listring[current_player;x_bows:arrow_inv]', + 'listring[current_player;main]', + ---quiver + 'label[5,0.5;' .. minetest.formspec_escape(S('Quiver')) .. ':]', + unified_inventory.single_slot(4.9, 0.9), + 'list[current_player;x_bows:quiver_inv;5,1;1,1;]', + 'listring[current_player;x_bows:quiver_inv]', + 'listring[current_player;main]', + } + + local context = {} + context._itemstack_arrow = player:get_inventory():get_stack('x_bows:arrow_inv', 1) + context._itemstack_quiver = player:get_inventory():get_stack('x_bows:quiver_inv', 1) + + if context._itemstack_arrow and not context._itemstack_arrow:is_empty() then + local x_bows_registered_arrow_def = self.registered_arrows[context._itemstack_arrow:get_name()] + + if x_bows_registered_arrow_def then + formspec[#formspec + 1] = 'label[0.5,2.5;' .. + minetest.formspec_escape(context._itemstack_arrow:get_short_description()) .. '\n' .. + minetest.formspec_escape(x_bows_registered_arrow_def.custom.description_abilities) .. ']' + end + end + + + if context._itemstack_quiver and not context._itemstack_quiver:is_empty() then + local st_meta = context._itemstack_quiver:get_meta() + local quiver_id = st_meta:get_string('quiver_id') + + ---description + formspec[#formspec + 1] = 'label[5,2.5;' .. + minetest.formspec_escape(context._itemstack_quiver:get_short_description()) .. ']' + formspec[#formspec + 1] = unified_inventory.single_slot(6.4, 0.9) + formspec[#formspec + 1] = unified_inventory.single_slot(7.65, 0.9) + formspec[#formspec + 1] = unified_inventory.single_slot(8.9, 0.9) + formspec[#formspec + 1] = 'list[detached:' .. quiver_id .. ';main;6.5,1;3,1;]' + formspec[#formspec + 1] = 'listring[detached:' .. quiver_id .. ';main]' + formspec[#formspec + 1] = 'listring[current_player;main]' + end + + return { + formspec = table.concat(formspec, '') + } + end + }) + + unified_inventory.register_button('x_bows:quiver_page', { + type = 'image', + image = "x_bows_bow_wood_charged.png", + tooltip = 'X Bows', + }) +end + +function XBowsQuiver.show_3d_quiver(self, player, props) + if not XBows.settings.x_bows_show_3d_quiver or not XBows.player_api then + return + end + + local _props = props or {} + local p_name = player:get_player_name() + local quiver_texture = 'x_bows_quiver_mesh.png' + local player_textures + + if _props.is_empty then + quiver_texture = 'x_bows_quiver_empty_mesh.png' + end + + if self.skinsdb then + minetest.after(1, function() + local textures = player_api.get_textures(player) + + ---cleanup + for index, value in ipairs(textures) do + if value == 'x_bows_quiver_blank_mesh.png' or value == 'x_bows_quiver_mesh.png' + or value == 'x_bows_quiver_empty_mesh.png' + then + table.remove(textures, index) + end + end + + table.insert(textures, quiver_texture) + + player_textures = textures + + if player_textures then + if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = true + player_api.set_textures(player, player_textures) + elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = false + player_api.set_textures(player, player_textures) + end + end + end) + + return + elseif self._3d_armor then + minetest.after(0.1, function() + player_textures = { + armor.textures[p_name].skin, + armor.textures[p_name].armor, + armor.textures[p_name].wielditem, + quiver_texture + } + + if player_textures then + if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = true + player_api.set_textures(player, player_textures) + elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = false + player_api.set_textures(player, player_textures) + end + end + end) + + return + elseif self.u_skins then + local u_skin_texture = u_skins.u_skins[p_name] + + player_textures = { + u_skin_texture .. '.png', + quiver_texture + } + elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins[p_name] then + player_textures = { + wardrobe.playerSkins[p_name], + quiver_texture + } + else + local textures = player_api.get_textures(player) + + ---cleanup + for index, value in ipairs(textures) do + if value == 'x_bows_quiver_blank_mesh.png' or value == 'x_bows_quiver_mesh.png' + or value == 'x_bows_quiver_empty_mesh.png' + then + table.remove(textures, index) + end + end + + table.insert(textures, quiver_texture) + + player_textures = textures + end + + if player_textures then + if _props.is_empty and not self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = true + player_api.set_textures(player, player_textures) + elseif not _props.is_empty and self.quiver_empty_state[player:get_player_name()] then + self.quiver_empty_state[player:get_player_name()] = false + player_api.set_textures(player, player_textures) + end + end +end + +function XBowsQuiver.hide_3d_quiver(self, player) + if not XBows.settings.x_bows_show_3d_quiver or not XBows.player_api then + return + end + + local p_name = player:get_player_name() + local player_textures + + if self.skinsdb then + minetest.after(1, function() + local textures = player_api.get_textures(player) + + ---cleanup + for index, value in ipairs(textures) do + if value == 'x_bows_quiver_mesh.png' or value == 'x_bows_quiver_blank_mesh.png' + or value == 'x_bows_quiver_empty_mesh.png' + then + table.remove(textures, index) + end + end + + table.insert(textures, 'x_bows_quiver_blank_mesh.png') + + player_textures = textures + + if player_textures then + player_api.set_textures(player, player_textures) + end + end) + + return + elseif self._3d_armor then + minetest.after(0.1, function() + player_textures = { + armor.textures[p_name].skin, + armor.textures[p_name].armor, + armor.textures[p_name].wielditem, + 'x_bows_quiver_blank_mesh.png' + } + + if player_textures then + player_api.set_textures(player, player_textures) + end + + end) + + return + elseif self.u_skins then + local u_skin_texture = u_skins.u_skins[p_name] + + player_textures = { + u_skin_texture .. '.png', + 'x_bows_quiver_blank_mesh.png' + } + elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins[p_name] then + player_textures = { + wardrobe.playerSkins[p_name], + 'x_bows_quiver_blank_mesh.png' + } + else + local textures = player_api.get_textures(player) + + ---cleanup + for index, value in ipairs(textures) do + if value == 'x_bows_quiver_mesh.png' or value == 'x_bows_quiver_blank_mesh.png' + or value == 'x_bows_quiver_empty_mesh.png' + then + table.remove(textures, index) + end + end + + table.insert(textures, 'x_bows_quiver_blank_mesh.png') + + player_textures = textures + end + + if player_textures then + player_api.set_textures(player, player_textures) + end +end + +---string split to characters +---@param str string +---@return string[] | nil +local function split(str) + if #str > 0 then + return str:sub(1, 1), split(str:sub(2)) + end +end + +function XBows.show_damage_numbers(self, pos, damage, is_crit) + if not pos or not self.settings.x_bows_show_damage_numbers then + return + end + + ---get damage texture + local dmgstr = tostring(math.round(damage)) + local results = { split(dmgstr) } + local texture = '' + local dmg_nr_offset = 0 + + for i, value in ipairs(results) do + if i == 1 then + texture = texture .. '[combine:' .. 7 * #results .. 'x' .. 9 * #results .. ':0,0=x_bows_dmg_' .. value .. '.png' + else + texture = texture .. ':' .. dmg_nr_offset .. ',0=x_bows_dmg_' .. value .. '.png' + end + + dmg_nr_offset = dmg_nr_offset + 7 + end + + if texture and texture ~= '' then + local size = 7 + + if is_crit then + size = 14 + texture = texture .. '^[colorize:#FF0000:255' + else + texture = texture .. '^[colorize:#FFFF00:127' + end + + ---show damage texture + minetest.add_particlespawner({ + amount = 1, + time = 0.01, + minpos = { x = pos.x, y = pos.y + 1, z = pos.z }, + maxpos = { x = pos.x, y = pos.y + 2, z = pos.z }, + minvel = { x = math.random(-1, 1), y = 5, z = math.random(-1, 1) }, + maxvel = { x = math.random(-1, 1), y = 5, z = math.random(-1, 1) }, + minacc = { x = math.random(-1, 1), y = -7, z = math.random(-1, 1) }, + maxacc = { x = math.random(-1, 1), y = -7, z = math.random(-1, 1) }, + minexptime = 2, + maxexptime = 2, + minsize = size, + maxsize = size, + texture = texture, + collisiondetection = true, + glow = 10 + }) + end +end diff --git a/arrow.lua b/arrow.lua index 9f4e158..e2b4000 100644 --- a/arrow.lua +++ b/arrow.lua @@ -1,520 +1,31 @@ --- Gets total armor level from 3d armor -local function get_3d_armor_armor(player) - 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 - - armor_total = armor.def[player:get_player_name()].level - - return armor_total -end - --- Limits number `x` between `min` and `max` values -local function limit(x, min, max) - return math.min(math.max(x, min), max) -end - --- Gets `ObjectRef` collision box -local function get_obj_box(obj) - 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 - - return box -end - --- Poison Arrow Effects -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 - - target_obj:set_properties({damage_texture_modifier = '^[colorize:#00FF0050'}) - - 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 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() - - -- if not lua_ent then - -- return - -- end - - -- 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 - - 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() - - 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 = 0.5 - }, - - on_activate = function(self, staticdata) - if not self or not staticdata or staticdata == '' then - self.object:remove() - return - end - - 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 - - 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, - - 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, - - 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 - - -- 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 - - -- 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 - - 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 - - -- 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 - - 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}}) - - 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 - - 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 - - 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 - - -- crits - if self._is_critical_hit then - _damage = _damage * 2 - 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 - ) - - pointed_thing.ref:add_velocity({ - x = dir.x * knockback * -1, - y = 7, - z = dir.z * knockback * -1 - }) - - 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 - } - ) - - -- already dead (entity) - if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then - self.object:remove() - return - end - - -- 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 - - -- attach arrow prepare - local rotation = {x = 0, y = 0, z = 0} - local position = {x = 0, y = 0, z = 0} - - 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 = 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 = 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 = 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 = 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 = 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 = math.random(ymin, ymax) / 10 - position.z = zmin / 10 - 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 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 - - 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 - - if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then - self.object:remove() - return - 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 - - local children = pointed_thing.ref:get_children() - - -- remove last arrow when too many already attached - if #children >= 5 then - children[1]:remove() - end - - return - - 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 not node_def then - return - end - - self._velocity = self.object:get_velocity() - - 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}) - - x_bows.particle_effect(self._old_pos, 'bubble') - elseif self._is_drowning then - self._is_drowning = false - - if self._velocity then - self.object:set_velocity(self._velocity) - end - - self.object:set_acceleration({x = 0, y = -9.81, z = 0}) - end - - 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 - - -- 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 - - 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}}) - - -- remove last arrow when too many already attached - local children = {} - - for k, 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 #children >= 5 then - children[#children]:remove() - end - - 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, -}) \ No newline at end of file +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +XBows:register_entity('arrow_entity', { + initial_properties = { + visual = 'mesh', + mesh = 'x_bows_arrow.b3d', + textures = { 'x_bows_arrow_mesh.png' }, + }, + _custom = { + animations = { + idle = { { x = 41, y = 42 }, 0, 0, false }, + on_hit_node = { { x = 1, y = 40 }, 40, 0, false } + } + } +}) diff --git a/assets/skinsdb_3d_armor_character_5.blend b/assets/skinsdb_3d_armor_character_5.blend new file mode 100644 index 0000000..067cd74 Binary files /dev/null and b/assets/skinsdb_3d_armor_character_5.blend differ diff --git a/assets/x_bows_3d_armor_character.blend b/assets/x_bows_3d_armor_character.blend new file mode 100644 index 0000000..fb7d4ef Binary files /dev/null and b/assets/x_bows_3d_armor_character.blend differ diff --git a/assets/x_bows_arrow.blend b/assets/x_bows_arrow.blend new file mode 100644 index 0000000..01acca7 Binary files /dev/null and b/assets/x_bows_arrow.blend differ diff --git a/assets/x_bows_character.blend b/assets/x_bows_character.blend new file mode 100644 index 0000000..da9824a Binary files /dev/null and b/assets/x_bows_character.blend differ diff --git a/bin/lua-language-server-3.5.6-linux-x64.tar.gz b/bin/lua-language-server-3.5.6-linux-x64.tar.gz new file mode 100644 index 0000000..9b23cf5 Binary files /dev/null and b/bin/lua-language-server-3.5.6-linux-x64.tar.gz differ diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..70b139a --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,77 @@ +image: atlassian/default-image:3 + +pipelines: + pull-requests: + "**": + - step: + name: Install Node Dependencies + caches: + - node-modules + - npm + - nvm + script: + - nvm install v17.2.0 + - npm i -g npm@8 + - npm ci + - parallel: + - step: + name: Lua Check + script: + - apt-get update + - apt-get -y install lua5.1 + - apt-get -y install luarocks + - luarocks install luacheck + - luacheck . + - step: + name: Lua Diagnostics + caches: + - node-modules + - npm + - nvm + script: + - nvm use v17.2.0 + - npm run lua-diagnostics + tags: + "*": + - step: + name: Install Node Dependencies + caches: + - node-modules + - npm + - nvm + script: + - nvm install v17.2.0 + - npm i -g npm@8 + - npm ci + - parallel: + - step: + name: Lua Check + script: + - apt-get update + - apt-get -y install lua5.1 + - apt-get -y install luarocks + - luarocks install luacheck + - luacheck . + - step: + name: Lua Diagnostics + caches: + - node-modules + - npm + - nvm + script: + - nvm use v17.2.0 + - npm run lua-diagnostics + - step: + name: Deploy to ContentDB + caches: + - node-modules + - npm + - nvm + script: + - nvm use v17.2.0 + - npm run push:ci -- --token=$CONTENT_DB_X_BOWS_TOKEN --title=$BITBUCKET_TAG +definitions: + caches: + node-modules: ./node_modules + npm: ~/.npm + nvm: ~/.nvm diff --git a/config.ld b/config.ld new file mode 100644 index 0000000..9477936 --- /dev/null +++ b/config.ld @@ -0,0 +1,8 @@ +file = {"docs"} +title = "x_bows API documentation" +description = "Minetest mod" +format = "markdown" +dir = "docs/build" +readme = "README.md" +project = "x_bows" +ext = "html" diff --git a/docs/x_bows_api.lua b/docs/x_bows_api.lua new file mode 100644 index 0000000..f0a656a --- /dev/null +++ b/docs/x_bows_api.lua @@ -0,0 +1,126 @@ +---- +-- Base XBows class +-- @author SaKeL +-- @license LGPL-2.1-or-later +-- @classmod XBows +XBows = { + --- `enable_pvp` setting, default `false` + pvp = false, + --- `creative_mode` setting, default `false` + creative = false, + --- `mesecons` check if MOD enabled or exists + mesecons = false, + --- `playerphysics` check if MOD enabled or exists + playerphysics = false, + --- `player_monoids` check if MOD enabled or exists + player_monoids = false, + --- table with key/value pairs, `key` is the item name (e.g. `x_bows:bow_wood`), `value` is the definition passed to XBows register method + registered_bows = {}, + --- table with key/value pairs, `key` is the item name (e.g. `x_bows:arrow_wood`), `value` is the definition passed to XBows register method + registered_arrows = {}, + --- table with key/value pairs, `key` is the item name (e.g. `x_bows:quiver`), `value` is the definition passed to XBows register method + registered_quivers = {}, + --- registered particle spawners for internal use + registered_particle_spawners = {}, + --- sneaking players when bow is charged + player_bow_sneak = {}, + --- `Settings` from minetest + settings = { + --- `x_bows_attach_arrows_to_entities` setting, default: `false` + x_bows_attach_arrows_to_entities = false + }, + --- table of `after` jobs + charge_sound_after_job = {} +} + +---Check if creative is enabled or if player has creative priv +-- @param self XBows +-- @param name string +-- @return boolean +function XBows.is_creative(self, name) end + +---Updates `allowed_ammunition` definition on already registered item, so MODs can add new ammunitions to this list. +-- @param self XBows +-- @param name string +-- @param allowed_ammunition string[] +-- @return nil +function XBows.update_bow_allowed_ammunition(self, name, allowed_ammunition) end + +---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also +-- @param self XBows +-- @param player ObjectRef Player Ref +-- @param includeWielded? boolean Will include reset for wielded bow also. default: `false` +-- @return nil +function XBows.reset_charged_bow(self, player, includeWielded) end + +---Register bows +-- @param self XBows +-- @param name string +-- @param def ItemDef | BowItemDefCustom +-- @param override? boolean MOD everride +-- @return boolean|nil +function XBows.register_bow(self, name, def, override) end + +---Register arrows +-- @param self XBows +-- @param name string +-- @param def ItemDef | ArrowItemDefCustom +-- @return boolean|nil +function XBows.register_arrow(self, name, def) end + +---Register quivers +-- @param self XBows +-- @param name string +-- @param def ItemDef | QuiverItemDefCustom +-- @return boolean|nil +function XBows.register_quiver(self, name, def) end + +---Load bow +-- @param self XBows +-- @param itemstack ItemStack +-- @param user ObjectRef +-- @param pointed_thing PointedThingDef +-- @return ItemStack +function XBows.load(self, itemstack, user, pointed_thing) end + +---Shoot bow +-- @param self XBows +-- @param itemstack ItemStack +-- @param user ObjectRef +-- @param pointed_thing? PointedThingDef +-- @return ItemStack +function XBows.shoot(self, itemstack, user, pointed_thing) end + +---Add new particle to XBow registration +-- @param self XBows +-- @param name string +-- @param def ParticlespawnerDef|ParticlespawnerDefCustom +-- @return nil +function XBows.register_particle_effect(self, name, def) end + +---Get particle effect from registered spawners table +-- @param self XBows +-- @param name string +-- @param pos Vector +-- @return number|boolean +function XBows.get_particle_effect_for_arrow(self, name, pos) end + +---Check if ammunition is allowed to charge this weapon +-- @param self XBows +-- @param weapon_name string +-- @param ammo_name string +-- @return boolean +function XBows.is_allowed_ammunition(self, weapon_name, ammo_name) end + +---Register new projectile entity +-- @param self XBows +-- @param name string +-- @param def XBowsEntityDef +function XBows.register_entity(self, name, def) end + +---Open quiver +-- @param self XBows +-- @param itemstack ItemStack +-- @param user ObjectRef +-- @return ItemStack +function XBows.open_quiver(self, itemstack, user) end diff --git a/docs/x_bows_quiver_api.lua b/docs/x_bows_quiver_api.lua new file mode 100644 index 0000000..2daeb4d --- /dev/null +++ b/docs/x_bows_quiver_api.lua @@ -0,0 +1,88 @@ +---- +-- XBowsQuiver class extended from XBows +-- @author SaKeL +-- @license LGPL-2.1-or-later +-- @classmod XBowsQuiver +XBowsQuiver = { + --- IDs of added HUDs + hud_item_ids = {}, + --- `after` job tables + after_job = {} +} + +---Close one or all open quivers in players inventory +-- @param self XBowsQuiver +-- @param player ObjectRef +-- @param quiver_id? string If `nil` then all open quivers will be closed +-- @return nil +function XBowsQuiver.close_quiver(self, player, quiver_id) end + +---Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta. +-- @param self XBowsQuiver +-- @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 XBowsQuiver.get_replacement_item(self, from_stack, to_item_name) end + +---Gets arrow from quiver +-- @param self XBowsQuiver +-- @param player ObjectRef +-- @return {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number} +function XBowsQuiver.get_itemstack_arrow_from_quiver(self, player) end + +---Remove all added HUDs +-- @param self XBowsQuiver +-- @param player ObjectRef +-- @return nil +function XBowsQuiver.remove_hud(self, player) end + +---Update or create quiver HUD +-- @param self XBowsQuiver +-- @param player ObjectRef +-- @param inv_list ItemStack[] +-- @param idx? number +-- @return nil +-- @todo implement hud_change? +function XBowsQuiver.udate_or_create_hud(self, player, inv_list, idx) end + +---Get existing detached inventory or create new one +-- @param self XBowsQuiver +-- @param quiver_id string +-- @param player_name string +-- @param quiver_items? string +-- @return InvRef +function XBowsQuiver.get_or_create_detached_inv(self, quiver_id, player_name, quiver_items) end + +---Create formspec +-- @param self XBowsQuiver +-- @param name string name of the form +-- @return string +function XBowsQuiver.get_formspec(self, name) end + +---Convert inventory of itemstacks to serialized string +-- @param self XBowsQuiver +-- @param inv InvRef +-- @return {['inv_string']: string, ['content_description']: string} +function XBowsQuiver.get_string_from_inv(self, inv) end + +---Set items from serialized string to inventory +-- @param self XBowsQuiver +-- @param inv InvRef inventory to add items to +-- @param str string previously stringified inventory of itemstacks +-- @return nil +function XBowsQuiver.set_string_to_inv(self, inv, str) end + +---Save quiver inventory to itemstack meta +-- @param self XBowsQuiver +-- @param inv InvRef +-- @param player ObjectRef +-- @param quiver_is_closed? boolean +-- @return nil +function XBowsQuiver.save(self, inv, player, quiver_is_closed) end + +---Check if we are allowing actions in the correct quiver inventory +-- @param self XBowsQuiver +-- @param inv InvRef +-- @param player ObjectRef +-- @return boolean +function XBowsQuiver.quiver_can_allow(self, inv, player) end diff --git a/i18n.py b/i18n.py new file mode 100755 index 0000000..da1c825 --- /dev/null +++ b/i18n.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer +# LGPLv2.1+ +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. + +from __future__ import print_function +import os, fnmatch, re, shutil, errno +from sys import argv as _argv +from sys import stderr as _stderr + +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False, + "break-long-lines": False, + "sort": False, + "print-source": False, + "truncate-unused": False, +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods', '-m'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file', '-O'], + "break-long-lines": ['--break-long-lines', '-b'], + "sort": ['--sort', '-s'], + "print-source": ['--print-source', '-p'], + "truncate-unused": ['--truncate-unused', '-t'], +} + +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 80 + +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["sort"])} + sort output strings alphabetically + {', '.join(options["break-long-lines"])} + add extra line breaks before and after long strings + {', '.join(options["print-source"])} + add comments denoting the source file + {', '.join(options["verbose"])} + add output information + {', '.join(options["truncate-unused"])} + delete unused strings from files +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}") + run_all_subfolders(os.path.expanduser("~/.minetest/mods")) + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) +pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.*?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + if not os.path.isfile(os.path.join(folder, "modpack.txt")): + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + if folder_name == "builtin": + return "__builtin" + else: + return folder_name + else: + return None + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = f'{modname}.{language_code}.tr' + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name, header_comments): + lOut = [f"# textdomain: {mod_name}"] + if header_comments is not None: + lOut.append(header_comments) + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + if params["sort"]: + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + if params["sort"]: + localizedStrings.sort() + if params["print-source"]: + if lOut[-1] != "": + lOut.append("") + lOut.append(source) + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None and comment != "" and not comment.startswith("# textdomain:"): + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + if not params["truncate-unused"]: + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if params["break-long-lines"] and len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2]) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua_s.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_s.findall(text): + strings.append(s) + for s in pattern_lua_fs.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_fs.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +# Returns also header comments in the third return value. +def import_tr_file(tr_file): + dOut = {} + text = None + header_comment = None + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line.startswith("###"): + if header_comment is None and not latest_comment_block is None: + # Save header comments + header_comment = latest_comment_block + # Strip textdomain line + tmp_h_c = "" + for l in header_comment.split('\n'): + if not l.startswith("# textdomain:"): + tmp_h_c += l + '\n' + header_comment = tmp_h_c + + # Reset comment block if we hit a header + latest_comment_block = None + continue + elif line.startswith("#"): + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text, header_comment) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") + + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut + +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2]) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr) + exit(1) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] + for subfolder in subfolders: + update_mod(subfolder) + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]: + update_folder(modfolder) + + +main() diff --git a/init.lua b/init.lua index 991574c..e68f8d1 100644 --- a/init.lua +++ b/init.lua @@ -1,360 +1,363 @@ +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +minetest = minetest.global_exists('minetest') and minetest --[[@as Minetest]] +ItemStack = minetest.global_exists('ItemStack') and ItemStack --[[@as ItemStack]] +vector = minetest.global_exists('vector') and vector --[[@as Vector]] +default = minetest.global_exists('default') and default --[[@as MtgDefault]] +sfinv = minetest.global_exists('sfinv') and sfinv --[[@as Sfinv]] +unified_inventory = minetest.global_exists('unified_inventory') and unified_inventory --[[@as UnifiedInventory]] +player_api = minetest.global_exists('player_api') and player_api --[[@as MtgPlayerApi]] + +math.randomseed(tonumber(tostring(os.time()):reverse():sub(1, 9))--[[@as number]] ) + +local path = minetest.get_modpath('x_bows') local mod_start_time = minetest.get_us_time() local bow_charged_timer = 0 -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'), - 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) - } -} - -function x_bows.is_creative(name) - return x_bows.creative or minetest.check_player_privs(name, {creative = true}) -end +dofile(path .. '/api.lua') +dofile(path .. '/particle_effects.lua') +dofile(path .. '/nodes.lua') +dofile(path .. '/arrow.lua') +dofile(path .. '/items.lua') -function x_bows.register_bow(name, def) - 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 - - 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 - }) - - -- 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}, - }) - - -- recipes - if def.recipe then - minetest.register_craft({ - output = def.name, - recipe = def.recipe - }) - end +if XBows.i3 then + XBowsQuiver:i3_register_page() +elseif XBows.unified_inventory then + XBowsQuiver:ui_register_page() +else + XBowsQuiver:sfinv_register_page() end -function x_bows.register_arrow(name, def) - if name == nil or name == '' then - return false - end - - def.name = 'x_bows:' .. name - def.description = def.description or name - - 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} - }) - - -- recipes - if def.craft then - minetest.register_craft({ - output = def.name ..' ' .. (def.craft_count or 4), - recipe = def.craft - }) - end -end +minetest.register_on_joinplayer(function(player) + local inv_quiver = player:get_inventory() --[[@as InvRef]] + local inv_arrow = player:get_inventory() --[[@as InvRef]] + + if XBows.settings.x_bows_show_3d_quiver and XBows.player_api then + ---Order matters here + if XBows.skinsdb then + player_api.set_model(player, 'skinsdb_3d_armor_character_5.b3d') + elseif XBows._3d_armor then + player_api.set_model(player, 'x_bows_3d_armor_character.b3d') + else + player_api.set_model(player, 'x_bows_character.b3d') + end + end + + inv_quiver:set_size('x_bows:quiver_inv', 1 * 1) + inv_arrow:set_size('x_bows:arrow_inv', 1 * 1) + + local quiver_stack = player:get_inventory():get_stack('x_bows:quiver_inv', 1) + + if quiver_stack and not quiver_stack:is_empty() then + local st_meta = quiver_stack:get_meta() + local quiver_id = st_meta:get_string('quiver_id') + + ---create detached inventory + local detached_inv = XBowsQuiver:get_or_create_detached_inv( + quiver_id, + player:get_player_name(), + st_meta:get_string('quiver_items') + ) + + ---set model textures + if detached_inv:is_empty('main') then + XBowsQuiver.quiver_empty_state[player:get_player_name()] = false + XBowsQuiver:show_3d_quiver(player, { is_empty = true }) + else + XBowsQuiver.quiver_empty_state[player:get_player_name()] = true + XBowsQuiver:show_3d_quiver(player) + end + else + ---set model textures + XBowsQuiver:hide_3d_quiver(player) + end + + XBows:reset_charged_bow(player, true) + XBowsQuiver:close_quiver(player) +end) -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 = {} - - 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 - node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing) - return - end - end - - for k, 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 - - -- take 1st found arrow in the list - local itemstack_arrow = itemstack_arrows[1] - - if itemstack_arrow and bow_def then - local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities - - 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() - - if wielded_item_name == v_bow_name then - local meta = wielded_item:get_meta() - - 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 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) - - -- 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_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) - - minetest.sound_play('x_bows_bow_load', { - to_player = user:get_player_name(), - gain = 0.6 - }) - - return itemstack - end +if XBows.settings.x_bows_show_3d_quiver and XBows.player_api then + local model_name = 'x_bows_character.b3d' + + if XBows.skinsdb then + ---skinsdb + model_name = 'skinsdb_3d_armor_character_5.b3d' + elseif XBows._3d_armor then + ---3d armor + model_name = 'x_bows_3d_armor_character.b3d' + end + + player_api.register_model(model_name, { + animation_speed = 30, + textures = { 'character.png' }, + animations = { + -- Standard animations. + stand = { x = 0, y = 79 }, + lay = { x = 162, y = 166, eye_height = 0.3, override_local = true, + collisionbox = { -0.6, 0.0, -0.6, 0.6, 0.3, 0.6 } }, + walk = { x = 168, y = 187 }, + mine = { x = 189, y = 198 }, + walk_mine = { x = 200, y = 219 }, + sit = { x = 81, y = 160, eye_height = 0.8, override_local = true, + collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.0, 0.3 } } + }, + collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.7, 0.3 }, + stepheight = 0.6, + eye_height = 1.47 + }) end -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 - - if not x_bows.registered_arrows[meta_arrow] then - return itemstack - 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 - - local staticdata = { - arrow = meta_arrow, - user_name = user:get_player_name(), - is_critical_hit = false, - _tool_capabilities = _tool_capabilities, - _tflp = tflp, - } - - -- 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 sound_name = 'x_bows_bow_shoot' - if staticdata.is_critical_hit then - sound_name = 'x_bows_bow_shoot_crit' - end - - meta:set_string('arrow', '') - itemstack:set_name(bow_name) - - 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)) - - if not obj then - return itemstack - end - - local lua_ent = obj:get_luaentity() - local strength_multiplier = tflp - - if strength_multiplier > _tool_capabilities.full_punch_interval then - strength_multiplier = 1 - end - - 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 +---formspec callbacks +minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info) + ---arrow inventory + if action == 'move' and inventory_info.to_list == 'x_bows:arrow_inv' then + local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) + + if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 then + return inventory_info.count + else + return 0 + end + elseif action == 'move' and inventory_info.from_list == 'x_bows:arrow_inv' then + local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) + + if minetest.get_item_group(stack:get_name(), 'arrow') ~= 0 then + return inventory_info.count + else + return 0 + end + elseif action == 'put' and inventory_info.listname == 'x_bows:arrow_inv' then + if minetest.get_item_group(inventory_info.stack:get_name(), 'arrow') ~= 0 then + return inventory_info.stack:get_count() + else + return 0 + end + elseif action == 'take' and inventory_info.listname == 'x_bows:arrow_inv' then + if minetest.get_item_group(inventory_info.stack:get_name(), 'arrow') ~= 0 then + return inventory_info.stack:get_count() + else + return 0 + end + end + + ---quiver inventory + if action == 'move' and inventory_info.to_list == 'x_bows:quiver_inv' then + local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) + if minetest.get_item_group(stack:get_name(), 'quiver') ~= 0 then + return inventory_info.count + else + return 0 + end + elseif action == 'move' and inventory_info.from_list == 'x_bows:quiver_inv' then + local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) + if minetest.get_item_group(stack:get_name(), 'quiver') ~= 0 then + return inventory_info.count + else + return 0 + end + elseif action == 'put' and inventory_info.listname == 'x_bows:quiver_inv' then + if minetest.get_item_group(inventory_info.stack:get_name(), 'quiver') ~= 0 then + return inventory_info.stack:get_count() + else + return 0 + end + elseif action == 'take' and inventory_info.listname == 'x_bows:quiver_inv' then + if minetest.get_item_group(inventory_info.stack:get_name(), 'quiver') ~= 0 then + return inventory_info.stack:get_count() + else + return 0 + end + end + + return inventory_info.count or inventory_info.stack:get_count() +end) -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 -end +minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info) + ---arrow + if action == 'move' and inventory_info.to_list == 'x_bows:arrow_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + elseif action == 'move' and inventory_info.from_list == 'x_bows:arrow_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + elseif action == 'put' and inventory_info.listname == 'x_bows:arrow_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + elseif action == 'take' and inventory_info.listname == 'x_bows:arrow_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + end + + ---quiver + if action == 'move' and inventory_info.to_list == 'x_bows:quiver_inv' then + local stack = inventory:get_stack(inventory_info.to_list, inventory_info.to_index) + + ---init detached inventory if not already + local st_meta = stack:get_meta() + local quiver_id = st_meta:get_string('quiver_id') + + if quiver_id == '' then + quiver_id = stack:get_name() .. '_' .. XBows.uuid() + st_meta:set_string('quiver_id', quiver_id) + inventory:set_stack(inventory_info.to_list, inventory_info.to_index, stack) + end + + local detached_inv = XBowsQuiver:get_or_create_detached_inv( + quiver_id, + player:get_player_name(), + st_meta:get_string('quiver_items') + ) + + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + + ---set player visual + if detached_inv:is_empty('main') then + XBowsQuiver.quiver_empty_state[player:get_player_name()] = false + XBowsQuiver:show_3d_quiver(player, { is_empty = true }) + else + XBowsQuiver.quiver_empty_state[player:get_player_name()] = true + XBowsQuiver:show_3d_quiver(player) + end + elseif action == 'move' and inventory_info.from_list == 'x_bows:quiver_inv' then + local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) + + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + + ---set player visual + if stack:is_empty() then + XBowsQuiver:hide_3d_quiver(player) + end + elseif action == 'put' and inventory_info.listname == 'x_bows:quiver_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + elseif action == 'take' and inventory_info.listname == 'x_bows:quiver_inv' then + if XBows.i3 then + i3.set_fs(player) + elseif XBows.unified_inventory then + unified_inventory.set_inventory_formspec(player, 'x_bows:quiver_page') + else + sfinv.set_player_inventory_formspec(player) + end + + ---set player visual + if inventory:is_empty(inventory_info.listname) then + XBowsQuiver:hide_3d_quiver(player) + end + end +end) --- sneak, fov adjustments when bow is charged -minetest.register_globalstep(function(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 not item then - return - 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 minetest.get_modpath('playerphysics') then - playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25) - 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 minetest.get_modpath('playerphysics') then - playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged') - end - - x_bows.player_bow_sneak[name].sneak = false - player:set_fov(1, true, 0.4) - end - end - - bow_charged_timer = 0 - end +minetest.register_on_player_receive_fields(function(player, formname, fields) + if player and fields.quit then + XBowsQuiver:close_quiver(player, formname) + end end) -local path = minetest.get_modpath('x_bows') +---backwards compatibility +minetest.register_alias('x_bows:arrow_diamond_tipped_poison', 'x_bows:arrow_diamond') -dofile(path .. '/nodes.lua') -dofile(path .. '/arrow.lua') -dofile(path .. '/items.lua') +-- sneak, fov adjustments when bow is charged +minetest.register_globalstep(function(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 player_name = player:get_player_name() + local wielded_stack = player:get_wielded_item() + local wielded_stack_name = wielded_stack:get_name() + + if not wielded_stack_name then + return + end + + if not XBows.player_bow_sneak[player_name] then + XBows.player_bow_sneak[player_name] = {} + end + + if minetest.get_item_group(wielded_stack_name, 'bow_charged') ~= 0 + and not XBows.player_bow_sneak[player_name].sneak + then + --charged weapon + if XBows.playerphysics then + playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_charged_speed', 0.25) + elseif XBows.player_monoids then + player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_charged_speed') + end + + XBows.player_bow_sneak[player_name].sneak = true + player:set_fov(0.9, true, 0.4) + elseif minetest.get_item_group(wielded_stack_name, 'bow_charged') == 0 + and XBows.player_bow_sneak[player_name].sneak + then + if XBows.playerphysics then + playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_charged_speed') + elseif XBows.player_monoids then + player_monoids.speed:del_change(player, 'x_bows:bow_charged_speed') + end + + XBows.player_bow_sneak[player_name].sneak = false + player:set_fov(0, true, 0.4) + end + + XBows:reset_charged_bow(player) + end + + bow_charged_timer = 0 + end +end) local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000 -print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]') +print('[Mod] x_bows loaded.. [' .. mod_end_time .. 's]') diff --git a/items.lua b/items.lua index 24c2050..13685de 100644 --- a/items.lua +++ b/items.lua @@ -1,130 +1,169 @@ -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'}, - } -}) +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> -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} - } -}) + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +local S = minetest.get_translator(minetest.get_current_modname()) -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} - } +XBows:register_bow('bow_wood', { + description = S('Wooden Bow'), + short_description = S('Wooden Bow'), + custom = { + uses = 385, + crit_chance = 10, + recipe = { + { '', 'default:stick', 'farming:string' }, + { 'default:stick', '', 'farming:string' }, + { '', 'default:stick', 'farming:string' } + }, + fuel_burntime = 3, + allowed_ammunition = { + 'x_bows:arrow_wood', + 'x_bows:arrow_stone', + 'x_bows:arrow_bronze', + 'x_bows:arrow_steel', + 'x_bows:arrow_mese', + 'x_bows:arrow_diamond' + } + } }) -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} - } +XBows:register_arrow('arrow_wood', { + description = S('Arrow Wood'), + short_description = S('Arrow Wood'), + inventory_image = 'x_bows_arrow_wood.png', + custom = { + recipe = { + { 'default:flint' }, + { 'group:stick' }, + { 'group:wool' } + }, + tool_capabilities = { + full_punch_interval = 1, + max_drop_level = 0, + damage_groups = { fleshy = 2 } + }, + fuel_burntime = 1 + } }) -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} - } +XBows:register_arrow('arrow_stone', { + description = S('Arrow Stone'), + short_description = S('Arrow Stone'), + inventory_image = 'x_bows_arrow_stone.png', + custom = { + recipe = { + { '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_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} - } +XBows:register_arrow('arrow_bronze', { + description = S('Arrow Bronze'), + short_description = S('Arrow Bronze'), + inventory_image = 'x_bows_arrow_bronze.png', + custom = { + recipe = { + { '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_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} - } +XBows:register_arrow('arrow_steel', { + description = S('Arrow Steel'), + short_description = S('Arrow Steel'), + inventory_image = 'x_bows_arrow_steel.png', + custom = { + recipe = { + { '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_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 +XBows:register_arrow('arrow_mese', { + description = S('Arrow Mese'), + short_description = S('Arrow Mese'), + inventory_image = 'x_bows_arrow_mese.png', + custom = { + recipe = { + { 'default:flint' }, + { 'default:mese_crystal' }, + { 'group:wool' } + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = { fleshy = 7 } + } + } }) -minetest.register_craft({ - type = 'fuel', - recipe = 'x_bows:bow_wood', - burntime = 3, +XBows:register_arrow('arrow_diamond', { + description = S('Arrow Diamond'), + short_description = S('Arrow Diamond'), + inventory_image = 'x_bows_arrow_diamond.png', + custom = { + recipe = { + { 'default:flint' }, + { 'default:diamond' }, + { 'group:wool' } + }, + tool_capabilities = { + full_punch_interval = 0.7, + max_drop_level = 1, + damage_groups = { fleshy = 8 } + } + } }) -minetest.register_craft({ - type = 'fuel', - recipe = 'x_bows:arrow_wood', - burntime = 1, +XBows:register_quiver('quiver', { + description = S('Quiver') .. '\n\n' .. S('Empty') .. '\n', + short_description = S('Quiver'), + custom = { + description = S('Quiver') .. '\n\n' .. S('Empty') .. '\n', + short_description = S('Quiver'), + recipe = { + { 'group:arrow', 'group:arrow', 'group:arrow' }, + { 'group:arrow', 'wool:brown', 'group:arrow' }, + { 'group:arrow', 'group:arrow', 'group:arrow' } + }, + recipe_count = 1, + faster_arrows = 5, + add_damage = 2, + fuel_burntime = 3 + } }) diff --git a/locale/template.txt b/locale/template.txt new file mode 100644 index 0000000..109876a --- /dev/null +++ b/locale/template.txt @@ -0,0 +1,21 @@ +# textdomain: x_bows +Critical Arrow Chance= +Strength= +Allowed ammunition= +none= +Damage= +Charge Time= +Faster Arrows= +Arrow Damage= +No Ammo= +Arrows= +Quiver= +Empty= +Wooden Bow= +Arrow Wood= +Arrow Stone= +Arrow Bronze= +Arrow Steel= +Arrow Mese= +Arrow Diamond= +Target= diff --git a/locale/x_bows.sk.tr b/locale/x_bows.sk.tr new file mode 100644 index 0000000..be7c233 --- /dev/null +++ b/locale/x_bows.sk.tr @@ -0,0 +1,21 @@ +# textdomain: x_bows +Critical Arrow Chance=Šanca kritického šípu +Strength=Sila +Allowed ammunition=Povolené strelivo +none=Žiaden +Damage=Poškodenie +Charge Time=Doba nabíjania +Faster Arrows=Rýchlejšie šípy +Arrow Damage=Poškodenie šípom +No Ammo=Žiadne strelivo +Arrows=Šípy +Quiver=Púzdro +Empty=Prázdne +Wooden Bow=Drevený luk +Arrow Wood=Drevený šíp +Arrow Stone=Kamenný šíp +Arrow Bronze=Bronzový šíp +Arrow Steel=Oceľový šíp +Arrow Mese=Mese šíp +Arrow Diamond=Diamantový šíp +Target=Terč diff --git a/mod.conf b/mod.conf index f5c7e0d..4befe04 100644 --- a/mod.conf +++ b/mod.conf @@ -1,5 +1,6 @@ name = x_bows -description = Adds bow and arrows to Minetest. +description = Adds bow and arrows with API. depends = -optional_depends = default, farming, 3d_armor, hbhunger, mesecons, playerphysics -min_minetest_version = 5.0 \ No newline at end of file +optional_depends = default, farming, 3d_armor, mesecons, playerphysics, player_monoids, wool, i3, unified_inventory, simple_skins, u_skins, wardrobe, sfinv, skinsdb, player_api +supported_games = minetest_game +min_minetest_version = 5.4 diff --git a/models/skinsdb_3d_armor_character_5.b3d b/models/skinsdb_3d_armor_character_5.b3d new file mode 100644 index 0000000..52b78f5 Binary files /dev/null and b/models/skinsdb_3d_armor_character_5.b3d differ diff --git a/models/x_bows_3d_armor_character.b3d b/models/x_bows_3d_armor_character.b3d new file mode 100644 index 0000000..ee6d07d Binary files /dev/null and b/models/x_bows_3d_armor_character.b3d differ diff --git a/models/x_bows_arrow.b3d b/models/x_bows_arrow.b3d new file mode 100644 index 0000000..7d7e286 Binary files /dev/null and b/models/x_bows_arrow.b3d differ diff --git a/models/x_bows_character.b3d b/models/x_bows_character.b3d new file mode 100644 index 0000000..aea1342 Binary files /dev/null and b/models/x_bows_character.b3d differ diff --git a/nodes.lua b/nodes.lua index 9c8200c..29a4859 100644 --- a/nodes.lua +++ b/nodes.lua @@ -1,54 +1,54 @@ -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} -}) +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +local S = minetest.get_translator(minetest.get_current_modname()) 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 = S('Target'), + short_description = S('Target'), + tiles = { 'x_bows_target.png' }, + is_ground_content = false, + groups = { snappy = 3, flammable = 4, fall_damage_add_percent = -30 }, + sounds = minetest.global_exists('default') and default.node_sound_leaves_defaults() or {}, + mesecons = { receptor = { state = 'off' } }, + ---@param pos Vector + ---@param elapsed number + ---@return boolean + on_timer = function(pos, elapsed) + if XBows.mesecons then + mesecon.receptor_off(pos) + end + + 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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..eb73212 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1388 @@ +{ + "name": "x_bows", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "x_bows", + "version": "1.0.0", + "license": "LGPL-2.1-or-later", + "devDependencies": { + "jaguar": "^6.0.1", + "node-fetch": "^3.2.10", + "yargs": "^17.6.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==", + "dev": true, + "engines": { + "node": ">=0.8.22" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "dependencies": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "bin": { + "gunzip-maybe": "bin.js" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/jaguar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jaguar/-/jaguar-6.0.1.tgz", + "integrity": "sha512-WrJrwHHl4lHQa4dVG4fJTLxca19It/pTwtr5cnnUlY0MvWO0y1Y4dWn3ABVCEZzfd5gDbverVqin3tiZ/YloIQ==", + "dev": true, + "dependencies": { + "findit2": "^2.2.3", + "glob": "^7.1.0", + "gunzip-maybe": "^1.3.1", + "minimist": "^1.2.0", + "pipe-io": "^4.0.0", + "tar-fs": "^2.0.0", + "tar-stream": "^2.1.0", + "try-to-catch": "^3.0.0" + }, + "bin": { + "jaguar": "bin/jaguar.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "node_modules/pipe-io": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pipe-io/-/pipe-io-4.0.1.tgz", + "integrity": "sha512-Wj9G85wJCpIgHq7xd0g4/IDjrA51pxmd+m9AbTiC6zRmWzVC6jOJIUyf92r7/B2+NE6zwqZIz0BZr85xkc3/Sg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/try-to-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.6.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.1.tgz", + "integrity": "sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==", + "dev": true + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "jaguar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jaguar/-/jaguar-6.0.1.tgz", + "integrity": "sha512-WrJrwHHl4lHQa4dVG4fJTLxca19It/pTwtr5cnnUlY0MvWO0y1Y4dWn3ABVCEZzfd5gDbverVqin3tiZ/YloIQ==", + "dev": true, + "requires": { + "findit2": "^2.2.3", + "glob": "^7.1.0", + "gunzip-maybe": "^1.3.1", + "minimist": "^1.2.0", + "pipe-io": "^4.0.0", + "tar-fs": "^2.0.0", + "tar-stream": "^2.1.0", + "try-to-catch": "^3.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, + "node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "pipe-io": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pipe-io/-/pipe-io-4.0.1.tgz", + "integrity": "sha512-Wj9G85wJCpIgHq7xd0g4/IDjrA51pxmd+m9AbTiC6zRmWzVC6jOJIUyf92r7/B2+NE6zwqZIz0BZr85xkc3/Sg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "try-to-catch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz", + "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.6.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.1.tgz", + "integrity": "sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c251c60 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "x_bows", + "version": "1.0.0", + "description": "Adds bow and arrows to Minetest.", + "main": "index.js", + "type": "module", + "directories": { + "doc": "docs" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "push:ci": "node ./scripts/deploy", + "lua-diagnostics": "node ./scripts/lls-check" + }, + "repository": { + "type": "git", + "url": "git+https://juraj_vajda@bitbucket.org/minetest_gamers/x_bows.git" + }, + "author": "SaKeL", + "license": "LGPL-2.1-or-later", + "bugs": { + "url": "https://bitbucket.org/minetest_gamers/x_bows/issues" + }, + "homepage": "https://bitbucket.org/minetest_gamers/x_bows#readme", + "devDependencies": { + "jaguar": "^6.0.1", + "node-fetch": "^3.2.10", + "yargs": "^17.6.1" + } +} diff --git a/particle_effects.lua b/particle_effects.lua new file mode 100644 index 0000000..a0621f7 --- /dev/null +++ b/particle_effects.lua @@ -0,0 +1,94 @@ +--[[ + X Bows. Adds bow and arrows with API. + Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to juraj.vajda@gmail.com +--]] + +XBows:register_particle_effect('arrow', { + amount = 1, + time = 0.1, + minexptime = 0.5, + maxexptime = 0.5, + minsize = 2, + maxsize = 2, + texture = 'x_bows_arrow_particle.png', + animation = { + type = 'vertical_frames', + aspect_w = 8, + aspect_h = 8, + length = 1, + }, + glow = 1, + minvel = { x = 0, y = -0.1, z = 0 }, + maxvel = { x = 0, y = -0.1, z = 0 }, + minacc = { x = 0, y = -0.1, z = 0 }, + maxacc = { x = 0, y = -0.1, z = 0 } +}) + +XBows:register_particle_effect('arrow_crit', { + amount = 1, + time = 0.1, + 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, + minvel = { x = 0, y = -0.1, z = 0 }, + maxvel = { x = 0, y = -0.1, z = 0 }, + minacc = { x = 0, y = -0.1, z = 0 }, + maxacc = { x = 0, y = -0.1, z = 0 } +}) + +XBows:register_particle_effect('arrow_fast', { + amount = 1, + time = 0.1, + 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, + minvel = { x = 0, y = -0.1, z = 0 }, + maxvel = { x = 0, y = -0.1, z = 0 }, + minacc = { x = 0, y = -0.1, z = 0 }, + maxacc = { x = 0, y = -0.1, z = 0 } +}) + +XBows:register_particle_effect('bubble', { + amount = 1, + time = 1, + 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' +}) 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.4.png b/screenshot.4.png new file mode 100644 index 0000000..db15ea8 Binary files /dev/null and b/screenshot.4.png differ diff --git a/screenshot.5.png b/screenshot.5.png new file mode 100644 index 0000000..a2bd87a Binary files /dev/null and b/screenshot.5.png differ diff --git a/screenshot.png b/screenshot.png index c5fd447..a6bf611 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 0000000..2faa964 --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1,50 @@ +/** + * Deploy code to CDB + * Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to juraj.vajda@gmail.com + */ + +import fetch from 'node-fetch' +import yargs from 'yargs/yargs' +import {hideBin} from 'yargs/helpers' + +const argv = yargs(hideBin(process.argv)).argv + +try { + const body = { + method: 'git', + title: argv.title, + ref: 'master' + } + + const response = await fetch('https://content.minetest.net/api/packages/SaKeL/x_bows/releases/new/', { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${argv.token}` + } + }) + const data = await response.json() + + console.log(data) + + if (!data.success) { + process.exit(1) + } +} catch (error) { + console.log(error) + process.exit(1) +} diff --git a/scripts/lls-check.js b/scripts/lls-check.js new file mode 100644 index 0000000..d4a74d3 --- /dev/null +++ b/scripts/lls-check.js @@ -0,0 +1,111 @@ +/** + * Run LUA diagnostics in continuous integration + * Copyright (C) 2022 SaKeL <juraj.vajda@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to juraj.vajda@gmail.com + */ + +import * as path from 'node:path' +import * as fs from 'node:fs' +import {exec} from 'node:child_process' +import yargs from 'yargs/yargs' +import {hideBin} from 'yargs/helpers' +import jaguar from 'jaguar' + +const argv = yargs(hideBin(process.argv)).argv +const cwd = process.cwd() +const logPath = path.join(cwd, 'logs') + +// Extract lua language server +const from = path.join(cwd, 'bin/lua-language-server-3.5.6-linux-x64.tar.gz'); +const to = path.join(cwd, 'bin', 'lua-language-server-3.5.6-linux-x64'); +const extract = jaguar.extract(from, to) + +// extract.on('file', (name) => { +// console.log(name) +// }) + +extract.on('start', () => { + console.log('Extracting...') +}) + +// extract.on('progress', (percent) => { +// console.log(percent + '%') +// }) + +extract.on('error', (error) => { + console.error(error) + process.exit(1) +}) + +extract.on('end', () => { + console.log('Extracting: Done') + + // Delete directory recursively + try { + fs.rmSync(logPath, { recursive: true, force: true }) + console.log(`Removed folder: ${logPath}`) + } catch (err) { + console.error(`Error while deleting ${logPath}.`) + console.error(err) + } + + let command = './bin/lua-language-server-3.5.6-linux-x64/bin/lua-language-server' + + if (argv.local) { + command = 'lua-language-server' + } + + exec(`${command} --logpath "${logPath}" --check "${cwd}"`, (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`) + process.exit(1) + } + + if (stderr) { + console.log(`stderr: ${stderr}`) + process.exit(1) + } + + console.log(`\n${stdout}`) + + if (fs.existsSync('./logs/check.json')) { + const rawdata = fs.readFileSync('./logs/check.json') + const diagnosticsJson = JSON.parse(rawdata) + + Object.keys(diagnosticsJson).forEach((key) => { + console.log(key) + + diagnosticsJson[key].forEach((errObj) => { + console.log(`line: ${errObj.range.start.line} - ${errObj.message}`) + }) + }) + + console.error('Fix the errors/warnings above.') + process.exit(1) + } + + // Delete directory recursively + const llsFolder = path.join(cwd, 'bin', 'lua-language-server-3.5.6-linux-x64') + + try { + fs.rmSync(llsFolder, { recursive: true, force: true }) + console.log(`Removed folder: ${llsFolder}`) + } catch (err) { + console.error(`Error while deleting ${llsFolder}.`) + console.error(err) + process.exit(1) + } + }) +}) diff --git a/settingtypes.txt b/settingtypes.txt index f4842e4..42a2a45 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,2 +1,9 @@ # Disabled per default due to inconsistent mob models scaling (visual_size). This will scale the arrows unproportionally and looks bad. If you have mobs with no scaling you can enable this setting. x_bows_attach_arrows_to_entities (Attach arrows to entities) bool false + +# Shows caused damage number flying out from the hit object/player. +x_bows_show_damage_numbers (Show damage caused) bool false + + +# Shows 3d quiver in 3rd person view (3d armor, and some skins MODs supported - see optional dependencies) +x_bows_show_3d_quiver (Show 3D Quiver) bool true 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/smoke_puff.png b/textures/smoke_puff.png new file mode 100644 index 0000000..88c5d39 Binary files /dev/null and b/textures/smoke_puff.png differ diff --git a/textures/x_bows_arrow_diamond_poison.png b/textures/x_bows_arrow_diamond_poison.png deleted file mode 100644 index 53f35c9..0000000 Binary files a/textures/x_bows_arrow_diamond_poison.png and /dev/null differ diff --git a/textures/x_bows_arrow_mesh.png b/textures/x_bows_arrow_mesh.png new file mode 100644 index 0000000..21aae46 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..045d9d7 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_arrow_tipped_particle.png b/textures/x_bows_arrow_tipped_particle.png deleted file mode 100644 index 1a17ed6..0000000 Binary files a/textures/x_bows_arrow_tipped_particle.png and /dev/null differ diff --git a/textures/x_bows_dmg_0.png b/textures/x_bows_dmg_0.png new file mode 100644 index 0000000..a19cd00 Binary files /dev/null and b/textures/x_bows_dmg_0.png differ diff --git a/textures/x_bows_dmg_1.png b/textures/x_bows_dmg_1.png new file mode 100644 index 0000000..6702c8d Binary files /dev/null and b/textures/x_bows_dmg_1.png differ diff --git a/textures/x_bows_dmg_2.png b/textures/x_bows_dmg_2.png new file mode 100644 index 0000000..8b35f55 Binary files /dev/null and b/textures/x_bows_dmg_2.png differ diff --git a/textures/x_bows_dmg_3.png b/textures/x_bows_dmg_3.png new file mode 100644 index 0000000..dc24c16 Binary files /dev/null and b/textures/x_bows_dmg_3.png differ diff --git a/textures/x_bows_dmg_4.png b/textures/x_bows_dmg_4.png new file mode 100644 index 0000000..f603d7c Binary files /dev/null and b/textures/x_bows_dmg_4.png differ diff --git a/textures/x_bows_dmg_5.png b/textures/x_bows_dmg_5.png new file mode 100644 index 0000000..3300e98 Binary files /dev/null and b/textures/x_bows_dmg_5.png differ diff --git a/textures/x_bows_dmg_6.png b/textures/x_bows_dmg_6.png new file mode 100644 index 0000000..8da46e5 Binary files /dev/null and b/textures/x_bows_dmg_6.png differ diff --git a/textures/x_bows_dmg_7.png b/textures/x_bows_dmg_7.png new file mode 100644 index 0000000..d01881f Binary files /dev/null and b/textures/x_bows_dmg_7.png differ diff --git a/textures/x_bows_dmg_8.png b/textures/x_bows_dmg_8.png new file mode 100644 index 0000000..6cd88e5 Binary files /dev/null and b/textures/x_bows_dmg_8.png differ diff --git a/textures/x_bows_dmg_9.png b/textures/x_bows_dmg_9.png new file mode 100644 index 0000000..72feaa1 Binary files /dev/null and b/textures/x_bows_dmg_9.png differ diff --git a/textures/x_bows_hotbar_selected.png b/textures/x_bows_hotbar_selected.png new file mode 100644 index 0000000..0666240 Binary files /dev/null and b/textures/x_bows_hotbar_selected.png 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_blank_mesh.png b/textures/x_bows_quiver_blank_mesh.png new file mode 100644 index 0000000..9e7ec19 Binary files /dev/null and b/textures/x_bows_quiver_blank_mesh.png differ diff --git a/textures/x_bows_quiver_empty_mesh.png b/textures/x_bows_quiver_empty_mesh.png new file mode 100644 index 0000000..65fb025 Binary files /dev/null and b/textures/x_bows_quiver_empty_mesh.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_mesh.png b/textures/x_bows_quiver_mesh.png new file mode 100644 index 0000000..d128775 Binary files /dev/null and b/textures/x_bows_quiver_mesh.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/textures/x_bows_quiver_slot.png b/textures/x_bows_quiver_slot.png new file mode 100644 index 0000000..1acc67a Binary files /dev/null and b/textures/x_bows_quiver_slot.png differ diff --git a/textures/x_bows_single_hotbar.png b/textures/x_bows_single_hotbar.png new file mode 100644 index 0000000..4aa7104 Binary files /dev/null and b/textures/x_bows_single_hotbar.png differ diff --git a/types/colors.type.lua b/types/colors.type.lua new file mode 100644 index 0000000..6ae5ef2 --- /dev/null +++ b/types/colors.type.lua @@ -0,0 +1,12 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias ColorSpec string|ColorSpecTable A ColorSpec specifies a 32-bit color. It can be written in any of the following forms: `colorspec = {a=255, r=0, g=255, b=0}`, numerical form: The raw integer value of an ARGB8 quad: `colorspec = 0xFF00FF00`, string form: A ColorString (defined above): `colorspec = "green"` +---@alias ColorString string `#RGB` defines a color in hexadecimal format. `#RGBA` defines a color in hexadecimal format and alpha channel. `#RRGGBB` defines a color in hexadecimal format. `#RRGGBBAA` defines a color in hexadecimal format and alpha channel. Named colors are also supported and are equivalent to [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/#named-color). To specify the value of the alpha channel, append `#A` or `#AA` to the end of the color name (e.g. `colorname#08`). + +---A ColorSpec table form: Each element ranging from 0..255 (a, if absent, defaults to 255): +---@class ColorSpecTable +---@field a number +---@field r number +---@field g number +---@field b number diff --git a/types/craft-recipe.type.lua b/types/craft-recipe.type.lua new file mode 100644 index 0000000..b2e2bd6 --- /dev/null +++ b/types/craft-recipe.type.lua @@ -0,0 +1,12 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Crafting recipes +---@class CraftRecipeDef +---@field type string (optional) specifies recipe type as shaped, e.g. "shaped", "shapeless", "toolrepair", "cooking", "fuel", default: "shaped" +---@field output string Itemstring of output itemstack (item counts >= 1 are allowed) +---@field recipe table<integer|number, string>[]|string A 2-dimensional matrix of items, with a width *w* and height *h*. *w* and *h* are chosen by you, they don't have to be equal but must be at least 1. The matrix is specified as a table containing tables containing itemnames. The inner tables are the rows. There must be *h* tables, specified from the top to the bottom row. Values inside of the inner table are the columns. Each inner table must contain a list of *w* items, specified from left to right. Empty slots *must* be filled with the empty string. +---@field replacements string[] (optional) Allows you to replace input items with some other items when something is crafted. Provided as a list of item pairs of the form `{ old_item, new_item }` where `old_item` is the input item to replace (same syntax as for a regular input slot; groups are allowed) and `new_item` is an itemstring for the item stack it will become. When the output is crafted, Minetest iterates through the list of input items if the crafting grid. For each input item stack, it checks if it matches with an `old_item` in the item pair list. If it matches, the item will be replaced. Also, this item pair will *not* be applied again for the remaining items. If it does not match, the item is consumed (reduced by 1) normally. The `new_item` will appear in one of 3 places: Crafting grid, if the input stack size was exactly 1, Player inventory, if input stack size was larger, Drops as item entity, if it fits neither in craft grid or inventory. +---@field additional_wear number|integer For `{type = "toolrepair"}` only. Adds a shapeless recipe for *every* tool that doesn't have the `disable_repair=1` group. If this recipe is used, repairing is possible with any crafting grid with at least 2 slots. The player can put 2 equal tools in the craft grid to get one "repaired" tool back. The wear of the output is determined by the wear of both tools, plus a 'repair bonus' given by `additional_wear`. To reduce the wear (i.e. 'repair'), you want `additional_wear` to be negative. The formula used to calculate the resulting wear is: 65536 * (1 - ( (1 - tool_1_wear) + (1 - tool_2_wear) + additional_wear )) The result is rounded and can't be lower than 0. If the result is 65536 or higher, no crafting is possible. +---@field cooktime number|integer For `{type = "cooking"}` only. (optional) Time it takes to cook this item, in seconds. A floating-point number. (default: 3.0) +---@field burntime number|integer For `{type = "fuel"}` only. (optional) Burning time this item provides, in seconds. A floating-point number. (default: 1.0) diff --git a/types/decoration.type.lua b/types/decoration.type.lua new file mode 100644 index 0000000..501bd7c --- /dev/null +++ b/types/decoration.type.lua @@ -0,0 +1,9 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---The varying types of decorations that can be placed. +---@class DecorationDef +---@field deco_type 'simple'|'schematic' `simple`: Creates a 1 times `H` times 1 column of a specified node (or a random node from a list, if a decoration list is specified). Can specify a certain node it must spawn next to, such as water or lava, for example. Can also generate a decoration of random height between a specified lower and upper bound. This type of decoration is intended for placement of grass, flowers, cacti, papyri, waterlilies and so on. `schematic`: Copies a box of `MapNodes` from a specified schematic file (or raw description). Can specify a probability of a node randomly appearing when placed. This decoration type is intended to be used for multi-node sized discrete structures, such as trees, cave spikes, rocks, and so on. +---@field biomes any List of biomes in which this decoration occurs. Occurs in all biomes if this is omitted, and ignored if the Mapgen being used does not support biomes. Can be a list of (or a single) biome names, IDs, or definitions. +---@field decoration string| string[] The node name used as the decoration. If instead a list of strings, a randomly selected node from the list is placed as the decoration. +---@field place_on string| string[] Node (or list of nodes) that the decoration can be placed on diff --git a/types/entity.type.lua b/types/entity.type.lua new file mode 100644 index 0000000..37e8092 --- /dev/null +++ b/types/entity.type.lua @@ -0,0 +1,17 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Entity definition +---@class EntityDef +---@field initial_properties ObjectProperties A table of object properties. The properties in this table are applied to the object once when it is spawned. `dtime_s` is the time passed since the object was unloaded, which can be used for updating the entity state. +---@field on_activate fun(self: table, staticdata: string, dtime_s: integer|number): nil Function receive a "luaentity" table as `self`. Called when the object is instantiated. +---@field on_deactivate fun(self: table, removal: boolean): nil Function receive a "luaentity" table as `self`. Called when the object is about to get removed or unloaded. `removal`: boolean indicating whether the object is about to get removed. Calling `object:remove()` on an active object will call this with `removal=true`. The mapblock the entity resides in being unloaded will call this with `removal=false`. Note that this won't be called if the object hasn't been activated in the first place. In particular, `minetest.clear_objects({mode = "full"})` won't call this, whereas `minetest.clear_objects({mode = "quick"})` might call this. +---@field on_step fun(self: table, dtime: integer|number, moveresult?: table): nil Function receive a "luaentity" table as `self`. Called on every server tick, after movement and collision processing. `dtime`: elapsed time since last call. `moveresult`: table with collision info (only available if physical=true). +---@field on_punch fun(self: table, puncher: ObjectRef|nil, time_from_last_punch: number|integer|nil, tool_capabilities: ToolCapabilitiesDef|nil, dir: Vector, damage: number|integer): boolean|nil Function receive a "luaentity" table as `self`. Called when somebody punches the object. Note that you probably want to handle most punches using the automatic armor group system. Can return `true` to prevent the default damage mechanism. +---@field on_death fun(self: table, killer: ObjectRef|nil): nil Function receive a "luaentity" table as `self`. Called when the object dies. +---@field on_rightclick fun(self: table, clicker: ObjectRef): nil Function receive a "luaentity" table as `self`. Called when `clicker` pressed the 'place/use' key while pointing to the object (not neccessarily an actual rightclick). `clicker`: an `ObjectRef` (may or may not be a player) +---@field on_attach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as `self`. `child`: an `ObjectRef` of the child that attaches +---@field on_detach_child fun(self: table, child: ObjectRef): nil Function receive a "luaentity" table as `self`. `child`: an `ObjectRef` of the child that detaches +---@field on_detach fun(self: table, parent: ObjectRef|nil): nil Function receive a "luaentity" table as `self`. `parent`: an `ObjectRef` (can be `nil`) from where it got detached. This happens before the parent object is removed from the world. +---@field get_staticdata fun(self: table) Function receive a "luaentity" table as `self`. Should return a string that will be passed to `on_activate` when the object is instantiated the next time. +---@field drops table Custom for mob drops diff --git a/types/generic.type.lua b/types/generic.type.lua new file mode 100644 index 0000000..97aba40 --- /dev/null +++ b/types/generic.type.lua @@ -0,0 +1,4 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@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 new file mode 100644 index 0000000..6a6dd6b --- /dev/null +++ b/types/inventory.type.lua @@ -0,0 +1,21 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +--An `InvRef` is a reference to an inventory. +---@class InvRef +---@field add_item fun(self: InvRef, listname: string, stack: string|ItemStack): ItemStack Add item somewhere in list, returns leftover `ItemStack`. +---@field contains_item fun(self: InvRef, listname: string, stack: string|ItemStack, match_meta?: boolean): boolean Returns `true` if the stack of items can be fully taken from the list. If `match_meta` is false, only the items' names are compared, default: `false` +---@field get_list fun(self: InvRef, listname: string): ItemStack[] Return full list, list of `ItemStack`s +---@field room_for_item fun(self: InvRef, listname: string, stack: string|ItemStack): boolean Returns `true` if the stack of items can be fully added to the list +---@field set_stack fun(self: InvRef, listname: string, i: integer, stack: string|ItemStack): nil Copy `stack` to index `i` in list +---@field is_empty fun(self: InvRef, listname: string): boolean Return `true` if list is empty +---@field get_size fun(self: InvRef, listname: string): integer Get size of a list +---@field set_size fun(self: InvRef, listname: string, size: integer): boolean Set size of a list, returns `false` on error, e.g. invalid `listname` or `size` +---@field get_width fun(self: InvRef, listname: string): boolean Get width of a list +---@field set_width fun(self: InvRef, listname: string, width: integer): nil Set width of list; currently used for crafting +---@field get_stack fun(self: InvRef, listname: string, i: integer): ItemStack Get a copy of stack index `i` in list +---@field set_list fun(self: InvRef, listname: string, list: ItemStack[]): nil Set full list, size will not change +---@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(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/item.type.lua b/types/item.type.lua new file mode 100644 index 0000000..0ec0b11 --- /dev/null +++ b/types/item.type.lua @@ -0,0 +1,55 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest item definition. Used by `minetest.register_node`, `minetest.register_craftitem`, and `minetest.register_tool`. +---Add your own custom fields. By convention, all custom field names. Should start with `_` to avoid naming collisions with future engine usage. +---@class ItemDef +---@field description string Can contain new lines. "\n" has to be used as new line character. +---@field short_description string|nil Must not contain new lines. Defaults to nil. +---@field groups table<string, string|number|integer|boolean> key = name, value = rating; rating = <number>. If rating not applicable, use 1. e.g. `{wool = 1, fluffy = 3}` `{soil = 2, outerspace = 1, crumbly = 1}` `{bendy = 2, snappy = 1}` {hard = 1, metal = 1, spikes = 1} +---@field inventory_image string Texture shown in the inventory GUI. Defaults to a 3D rendering of the node if left empty. +---@field inventory_overlay string An overlay texture which is not affected by colorization +---@field wield_image string Texture shown when item is held in hand. Defaults to a 3D rendering of the node if left empty. +---@field wield_overlay string Like inventory_overlay but only used in the same situation as wield_image +---@field wield_scale table<string, number|integer> Scale for the item when held in hand +---@field palette string An image file containing the palette of a node. You can set the currently used color as the "palette_index" field of the item stack metadata. The palette is always stretched to fit indices between 0 and 255, to ensure compatibility with "colorfacedir" (and similar) nodes. +---@field color string Color the item is colorized with. The palette overrides this. +---@field stack_max integer|number Maximum amount of items that can be in a single stack. +---@field range integer|number Range of node and object pointing that is possible with this item held. +---@field liquids_pointable boolean If true, item can point to all liquid nodes (`liquidtype ~= "none"`), even those for which `pointable = false` +---@field light_source integer|number When used for nodes: Defines amount of light emitted by node. Otherwise: Defines texture glow when viewed as a dropped item. To set the maximum (14), use the value 'minetest.LIGHT_MAX'. A value outside the range 0 to minetest.LIGHT_MAX causes undefined behavior. +---@field tool_capabilities ToolCapabilitiesDef +---@field node_placement_prediction string|nil If nil and item is node, prediction is made automatically. If nil and item is not a node, no prediction is made. If "" and item is anything, no prediction is made. Otherwise should be name of node which the client immediately places on ground when the player places the item. Server will always update with actual result shortly. +---@field node_dig_prediction string if "", no prediction is made. if "air", node is removed. Otherwise should be name of node which the client immediately places upon digging. Server will always update with actual result shortly. +---@field sound ItemSoundDef +---@field on_place fun(itemstack: ItemStack, placer: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil When the 'place' key was pressed with the item in hand and a node was pointed at. Shall place item and return the leftover itemstack or nil to not modify the inventory. The placer may be any ObjectRef or nil. default: minetest.item_place +---@field on_secondary_use fun(itemstack: ItemStack, user: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil Same as on_place but called when not pointing at a node. Function must return either nil if inventory shall not be modified, or an itemstack to replace the original itemstack. The user may be any ObjectRef or nil. default: nil +---@field on_drop fun(itemstack: ItemStack, dropper: ObjectRef|nil, pos: Vector): ItemStack|nil Shall drop item and return the leftover itemstack. The dropper may be any ObjectRef or nil. default: minetest.item_drop +---@field on_pickup fun(itemstack: ItemStack, picker: ObjectRef|nil, pointed_thing?: PointedThingDef, time_from_last_punch?: number|integer, rest?: any): ItemStack|nil Called when a dropped item is punched by a player. Shall pick-up the item and return the leftover itemstack or nil to not modify the dropped item. `rest` are other parameters from `luaentity:on_punch`. default: `minetest.item_pickup` +---@field on_use fun(itemstack: ItemStack, user: ObjectRef|nil, pointed_thing: PointedThingDef): ItemStack|nil default: nil. When user pressed the 'punch/mine' key with the item in hand. Function must return either nil if inventory shall not be modified, or an itemstack to replace the original itemstack. e.g. itemstack:take_item(); return itemstack. Otherwise, the function is free to do what it wants. The user may be any ObjectRef or nil. The default functions handle regular use cases. +---@field after_use fun(itemstack: ItemStack, user: ObjectRef|nil, node: NodeDef, digparams: DigParamsDef): ItemStack|nil default: nil. If defined, should return an itemstack and will be called instead of wearing out the item (if tool). If returns nil, does nothing. +---@field soil table Only for farming + +---Tool capabilities definition +---@class ToolCapabilitiesDef +---@field full_punch_interval number|integer +---@field max_drop_level number|integer +---@field groupcaps GroupCapsDef +---@field damage_groups table<string, number|integer> Damage values must be between -32768 and 32767 (2^15) +---@field punch_attack_uses number|integer|nil Amount of uses this tool has for attacking players and entities by punching them (0 = infinite uses). For compatibility, this is automatically set from the first suitable groupcap using the forumla "uses * 3^(maxlevel - 1)". It is recommend to set this explicitly instead of relying on the fallback behavior. + +---Known damage and digging time defining groups +---@class GroupCapsDef +---@field crumbly number|GroupCapsItemDef dirt, sand +---@field cracky number|GroupCapsItemDef tough but crackable stuff like stone. +---@field snappy number|GroupCapsItemDef something that can be cut using things like scissors, shears, bolt cutters and the like, e.g. leaves, small plants, wire, sheets of metal +---@field choppy number|GroupCapsItemDef something that can be cut using force; e.g. trees, wooden planks +---@field fleshy number|GroupCapsItemDef Living things like animals and the player. This could imply some blood effects when hitting. +---@field explody number|GroupCapsItemDef Especially prone to explosions +---@field oddly_breakable_by_hand number|GroupCapsItemDef Can be added to nodes that shouldn't logically be breakable by the hand but are. Somewhat similar to `dig_immediate`, but times are more like `{[1]=3.50,[2]=2.00,[3]=0.70}` and this does not override the digging speed of an item if it can dig at a faster speed than this suggests for the hand. + +---Known damage and digging time defining groups +---@class GroupCapsItemDef +---@field maxlevel number|integer Tells what is the maximum level of a node of this group that the item will be able to dig. +---@field uses number|integer Tools only. Determines how many uses the tool has when it is used for digging a node, of this group, of the maximum level. The maximum supported number of uses is 65535. The special number 0 is used for infinite uses. For lower leveled nodes, the use count is multiplied by `3^leveldiff`. `leveldiff` is the difference of the tool's `maxlevel` `groupcaps` and the node's `level` group. The node cannot be dug if `leveldiff` is less than zero. +---@field times table<number|integer, number|integer> List of digging times for different ratings of the group, for nodes of the maximum level. For example, as a Lua table, `times={[2]=2.00, [3]=0.70}`. This would result in the item to be able to dig nodes that have a rating of `2` or `3` for this group, and unable to dig the rating `1`, which is the toughest. Unless there is a matching group that enables digging otherwise. If the result digging time is 0, a delay of 0.15 seconds is added between digging nodes; If the player releases LMB after digging, this delay is set to 0, i.e. players can more quickly click the nodes away instead of holding LMB. diff --git a/types/itemstack.type.lua b/types/itemstack.type.lua new file mode 100644 index 0000000..e8b1e1b --- /dev/null +++ b/types/itemstack.type.lua @@ -0,0 +1,36 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---A native C++ format with many helper methods. Useful for converting between formats. +---An `ItemStack` is a stack of items. +---It can be created via `ItemStack(x)`, where x is an `ItemStack`, an itemstring, a table or `nil`. +---@class ItemStack +---@field is_empty fun(): boolean Returns `true` if stack is empty. +---@field get_name fun(): string returns item name (e.g. `"default:stone"`). +---@field set_name fun(self: ItemStack, item_name: string): boolean Returns a boolean indicating whether the item was cleared. +---@field get_count fun(): integer Returns number of items on the stack. +---@field set_count fun(self: ItemStack, count: integer): boolean Returns a boolean indicating whether the item was cleared +---@field get_wear fun(): integer Returns tool wear (`0`-`65535`), `0` for non-tools. +---@field set_wear fun(self: ItemStack, wear: integer): boolean Returns boolean indicating whether item was cleared +---@field get_meta fun(): ItemStackMetaRef Returns `ItemStackMetaRef`. +---@field get_description fun(): string Returns the description shown in inventory list tooltips. The engine uses this when showing item descriptions in tooltips. Fields for finding the description, in order: `description` in item metadata. +---@field get_short_description fun(): string|nil Returns the short description or nil. Unlike the description, this does not include new lines. Fields for finding the short description, in order: `short_description` in item metadata. Returns nil if none of the above are set. +---@field clear fun(): nil Removes all items from the stack, making it empty. +---@field replace fun(self: ItemStack, item: string|table)`: replace the contents of this stack. `item` can also be an itemstring or table. +---@field to_string fun(): string Returns the stack in itemstring form. +---@field to_table fun(): table Returns the stack in Lua table form. +---@field get_stack_max fun(): integer Returns the maximum size of the stack (depends on the item). +---@field get_free_space fun(): integer Returns `get_stack_max() - get_count()`. +---@field is_known fun(): boolean Returns `true` if the item name refers to a defined item type. +---@field get_definition fun(): table Returns the item definition table. +---@field get_tool_capabilities fun(): table Returns the digging properties of the item, or those of the hand if none are defined for this item type +---@field add_wear fun(self: ItemStack, amount: integer|number): nil Increases wear by `amount` if the item is a tool, otherwise does nothing. Valid `amount` range is [0,65536] `amount`: number, integer +---@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 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 +---@field wear string +---@field metadata string diff --git a/types/mapgen-aliases.type.lua b/types/mapgen-aliases.type.lua new file mode 100644 index 0000000..c955426 --- /dev/null +++ b/types/mapgen-aliases.type.lua @@ -0,0 +1,39 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +--- In a game, a certain number of these must be set to tell core mapgens which of the game's nodes are to be used for core mapgen generation. +---@alias MapgenAliasesNonV6 +---| '"mapgen_stone"' +---| '"mapgen_water_source"' +---| '"mapgen_river_water_source"' +---| '"mapgen_river_water_source"' # is required for mapgens with sloping rivers where it is necessary to have a river liquid node with a short `liquid_range` and `liquid_renewable = false` to avoid flooding. +---| '"mapgen_lava_source"' # Optional, Fallback lava node used if cave liquids are not defined in biome definitions. Deprecated, define cave liquids in biome definitions instead. +---| '"mapgen_cobble"' # Optional, Fallback node used if dungeon nodes are not defined in biome definitions. Deprecated, define dungeon nodes in biome definitions instead. + +--- In a game, a certain number of these must be set to tell core mapgens which of the game's nodes are to be used for core mapgen generation. +---@alias MapgenAliasesV6 +---| '"mapgen_stone"' +---| '"mapgen_water_source"' +---| '"mapgen_lava_source"' +---| '"mapgen_dirt"' +---| '"mapgen_dirt_with_grass"' +---| '"mapgen_sand"' +---| '"mapgen_tree"' +---| '"mapgen_leaves"' +---| '"mapgen_apple"' +---| '"mapgen_cobble"' +---| '"mapgen_gravel"' # Optional, (falls back to stone) +---| '"mapgen_desert_stone"' # Optional, (falls back to stone) +---| '"mapgen_desert_sand"' # Optional, (falls back to sand) +---| '"mapgen_dirt_with_snow"' # Optional, (falls back to dirt_with_grass) +---| '"mapgen_snowblock"' # Optional, (falls back to dirt_with_grass) +---| '"mapgen_snow"' # Optional, (not placed if missing) +---| '"mapgen_ice"' # Optional, (falls back to water_source) +---| '"mapgen_jungletree"' # Optional, (falls back to tree) +---| '"mapgen_jungleleaves"' # Optional, (falls back to leaves) +---| '"mapgen_junglegrass"' # Optional, (not placed if missing) +---| '"mapgen_pine_tree"' # Optional, (falls back to tree) +---| '"mapgen_pine_needles"' # Optional, (falls back to leaves) +---| '"mapgen_stair_cobble"' # Optional, (falls back to cobble) +---| '"mapgen_mossycobble"' # Optional, (falls back to cobble) +---| '"mapgen_stair_desert_stone"' # Optional, (falls backto desert_stone) diff --git a/types/math.type.lua b/types/math.type.lua new file mode 100644 index 0000000..c0078f9 --- /dev/null +++ b/types/math.type.lua @@ -0,0 +1,8 @@ +---@diagnostic disable: codestyle-check, duplicate-doc-alias +---https://github.com/sumneko/lua-language-server/wiki + +---@alias mathlib mathlib|MathAbstract + +---Math helpers +---@class MathAbstract +---@field round fun(x: number): number Returns `x` rounded to the nearest integer. At a multiple of 0.5, rounds away from zero. diff --git a/types/meta.type.lua b/types/meta.type.lua new file mode 100644 index 0000000..1a9e54e --- /dev/null +++ b/types/meta.type.lua @@ -0,0 +1,36 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias ItemStackMetaRef MetaDataRef|ItemStackMetaRefAbstract +---@alias NodeMetaRef MetaDataRef|NodeMetaRefAbstract +---@alias PlayerMetaRef MetaDataRef|PlayerMetaRefAbstract +---@alias StorageRef MetaDataRef|StorageRefAbstract + +---Base class used by [`StorageRef`], [`NodeMetaRef`], [`ItemStackMetaRef`], and [`PlayerMetaRef`]. +---@class MetaDataRef +---@field contains fun(self: MetaDataRef, key: string): boolean|nil Returns true if key present, otherwise false. Returns `nil` when the MetaData is inexistent. +---@field get fun(self: MetaDataRef, key: string): string|nil Returns `nil` if key not present, else the stored string. +---@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): 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) +---@field from_table fun(self: MetaDataRef, t: nil|table): boolean Any non-table value will clear the metadata. Returns `true` on success +---@field equals fun(self: MetaDataRef, other: any): boolean Returns `true` if this metadata has the same key-value pairs as `other` + +---ItemStack metadata: reference extra data and functionality stored in a stack. Can be obtained via `item:get_meta()`. +---@class ItemStackMetaRefAbstract +---@field set_tool_capabilities fun(self: ItemStackMetaRef, tool_capabilities?: table): nil Overrides the item's tool capabilities. A nil value will clear the override data and restore the original behavior. + +---Node metadata: reference extra data and functionality stored in a node. Can be obtained via `minetest.get_meta(pos)`. +---@class NodeMetaRefAbstract +---@field get_inventory fun(self: NodeMetaRef): InvRef +---@field mark_as_private fun(self: NodeMetaRef, name: string | table<string[]>) Mark specific vars as private This will prevent them from being sent to the client. Note that the "private" status will only be remembered if an associated key-value pair exists, meaning it's best to call this when initializing all other meta (e.g. `on_construct`). + +---Player metadata. Uses the same method of storage as the deprecated player attribute API, so data there will also be in player meta. Can be obtained using `player:get_meta()`. +---@class PlayerMetaRefAbstract + +---Mod metadata: per mod metadata, saved automatically. Can be obtained via `minetest.get_mod_storage()` during load time. WARNING: This storage backend is incapable of saving raw binary data due to restrictions of JSON. +---@class StorageRefAbstract diff --git a/types/minetest.type.lua b/types/minetest.type.lua new file mode 100644 index 0000000..1f9e885 --- /dev/null +++ b/types/minetest.type.lua @@ -0,0 +1,168 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest globals +---@class Minetest +---@field item_drop fun(itemstack: string|ItemStack, dropper: ObjectRef, pos: Vector): ItemStack Drop the item, returns the leftover itemstack +---@field get_us_time fun(): integer|number Returns time with microsecond precision. May not return wall time. +---@field get_modpath fun(modname: string): string|nil Returns the directory path for a mod, e.g. `"/home/user/.minetest/usermods/modname"`. Returns nil if the mod is not enabled or does not exist (not installed). Works regardless of whether the mod has been loaded yet. Useful for loading additional `.lua` modules or static data from a mod, or checking if a mod is enabled. +---@field check_player_privs fun(player_or_name: ObjectRef|string, privs: table|string[]): boolean Returns `bool, missing_privs`. A quickhand for checking privileges. `player_or_name`: Either a Player object or the name of a player. `privs` is either a list of strings, e.g. `"priva", "privb"` or a table, e.g. `{ priva = true, privb = true }`. +---@field register_on_joinplayer fun(f: fun(player: ObjectRef, last_login: number|integer|nil)): nil Called when a player joins the game. `last_login`: The timestamp of the previous login, or nil if player is new +---@field register_tool fun(name: string, item_definition: ItemDef): nil Registers the item in the engine +---@field colorize fun(color: string, message: string): nil +---@field register_craft fun(recipe: CraftRecipeDef): nil +---@field register_craftitem fun(name: string, item_definition: ItemDef): nil +---@field add_entity fun(pos: Vector, name: string, staticdata?: string): ObjectRef|nil Spawn Lua-defined entity at position. Returns `ObjectRef`, or `nil` if failed. +---@field get_node fun(pos: Vector): NodeDef Returns the node at the given position as table in the format `{name="node_name", param1=0, param2=0}`, returns `{name="ignore", param1=0, param2=0}` for unloaded areas. +---@field registered_nodes table<string, NodeDef|ItemDef> Map of registered node definitions, indexed by name +---@field after fun(time: number|integer, func: fun(...), ...): JobTable Call the function `func` after `time` seconds, may be fractional. Optional: Variable number of arguments that are passed to `func`. +---@field sound_play fun(spec: SimpleSoundSpec|string, parameters: SoundParamDef, ephemeral?: boolean): any Returns a `handle`. Ephemeral sounds will not return a handle and can't be stopped or faded. It is recommend to use this for short sounds that happen in response to player actions (e.g. door closing). +---@field add_particlespawner fun(particlespawner_definition: ParticlespawnerDef): number|integer Add a `ParticleSpawner`, an object that spawns an amount of particles over `time` seconds. Returns an `id`, and -1 if adding didn't succeed. +---@field register_globalstep fun(func: fun(dtime: number|integer)): nil Called every server step, usually interval of 0.1s +---@field get_connected_players fun(): ObjectRef[] Returns list of `ObjectRefs` +---@field serialize fun(t: table): string Convert a table containing tables, strings, numbers, booleans and `nil`s into string form readable by `minetest.deserialize`. Example: `serialize({foo="bar"})`, returns `'return { ["foo"] = "bar" }'`. +---@field dir_to_yaw fun(dir: Vector): number|integer Convert a vector into a yaw (angle) +---@field settings MinetestSettings Settings object containing all of the settings from the main config file (`minetest.conf`). +---@field register_entity fun(name: string, entity_definition: EntityDef): nil +---@field deserialize fun(s: string, safe?: boolean): table Returns a table. Convert a string returned by `minetest.serialize` into a table `string` is loaded in an empty sandbox environment. Will load functions if safe is false or omitted. Although these functions cannot directly access the global environment, they could bypass this restriction with maliciously crafted Lua bytecode if mod security is disabled. This function should not be used on untrusted data, regardless of the value of `safe`. It is fine to serialize then deserialize user-provided data, but directly providing user input to deserialize is always unsafe. +---@field raycast fun(pos1: Vector, pos2: Vector, objects: boolean, liquids: boolean): Raycast `pos1`: start of the ray, `pos2`: end of the ray, `objects`: if false, only nodes will be returned. Default is true. `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be returned. Default is false. +---@field calculate_knockback fun(player: ObjectRef, hitter: ObjectRef, time_from_last_punch: number|integer, tool_capabilities: ToolCapabilitiesDef, dir: Vector, distance: number|integer, damage: number|integer): integer|number Returns the amount of knockback applied on the punched player. Arguments are equivalent to `register_on_punchplayer`, except the following: `distance`: distance between puncher and punched player. This function can be overriden by mods that wish to modify this behaviour. You may want to cache and call the old function to allow multiple mods to change knockback behaviour. +---@field get_player_by_name fun(name: string): ObjectRef Get an `ObjectRef` to a player +---@field get_node_timer fun(pos: Vector): NodeTimerRef Get `NodeTimerRef` +---@field get_objects_inside_radius fun(pos: Vector, radius: number|integer): ObjectRef[] Returns a list of ObjectRefs. `radius`: using an euclidean metric. +---@field register_node fun(name: string, node_definition: NodeDef): nil +---@field get_meta fun(pos: Vector): NodeMetaRef Get a `NodeMetaRef` at that position +---@field pos_to_string fun(pos: Vector, decimal_places?: number|integer): string returns string `"(X,Y,Z)"`, `pos`: table {x=X, y=Y, z=Z}. Converts the position `pos` to a human-readable, printable string. `decimal_places`: number, if specified, the x, y and z values of the position are rounded to the given decimal place. +---@field get_node_light fun(pos: Vector, timeofday: number|integer|nil): number|integer|nil Gets the light value at the given position. Note that the light value "inside" the node at the given position is returned, so you usually want to get the light value of a neighbor. `pos`: The position where to measure the light. `timeofday`: `nil` for current time, `0` for night, `0.5` for day. Returns a number between `0` and `15` or `nil`. `nil` is returned e.g. when the map isn't loaded at `pos`. +---@field set_node fun(pos: Vector, node: SetNodeTable): nil 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 place_schematic fun(pos: Vector, schematic, rotation?: '0'|'90'|'180'|'270'|'random', replacements?: table<string, string>, force_placement?: boolean, flags?: string): nil Place the schematic specified by schematic at `pos`. `rotation` can equal `"0"`, `"90"`, `"180"`, `"270"`, or `"random"`. If the `rotation` parameter is omitted, the schematic is not rotated. `replacements` = `{["old_name"] = "convert_to", ...}`. `force_placement` is a boolean indicating whether nodes other than `air` and `ignore` are replaced by the schematic. Returns nil if the schematic could not be loaded. **Warning**: Once you have loaded a schematic from a file, it will be cached. Future calls will always use the cached version and the replacement list defined for it, regardless of whether the file or the replacement list parameter have changed. The only way to load the file anew is to restart the server. `flags` is a flag field with the available flags: place_center_x, place_center_y, place_center_z +---@field log fun(level?: 'none'|'error'|'warning'|'action'|'info'|'verbose', text: string): nil +---@field get_item_group fun(name: string, group): any returns a rating. Get rating of a group of an item. (`0` means: not in group) +---@field get_biome_data fun(pos: Vector): BiomeData|nil +---@field get_biome_name fun(biome_id: string|number|integer): string|nil Returns the biome name string for the provided biome id, or `nil` on failure. If no biomes have been registered, such as in mgv6, returns `default`. +---@field find_nodes_in_area fun(pos1: Vector, pos2: Vector, nodenames: string|string[], grouped?: boolean): Vector[] `pos1` and `pos2` are the min and max positions of the area to search. `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` If `grouped` is true the return value is a table indexed by node name which contains lists of positions. If `grouped` is false or absent the return values are as follows: first value: Table with all node positions, second value: Table with the count of each node with the node name as index, Area volume is limited to 4,096,000 nodes +---@field find_nodes_in_area_under_air fun(pos1: Vector, pos2: Vector, nodenames: string|string[]): table returns a list of positions. `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`. Return value: Table with all node positions with a node air above. Area volume is limited to 4,096,000 nodes. +---@field registered_decorations table<any, DecorationDef> Map of registered decoration definitions, indexed by the `name` field. If `name` is nil, the key is the object handle returned by `minetest.register_schematic`. +---@field swap_node fun(pos: Vector, node: NodeDef): nil Set node at position, but don't remove metadata +---@field item_eat fun(hp_change: number, replace_with_item?: string): fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef) Returns `function(itemstack, user, pointed_thing)` as a function wrapper for `minetest.do_item_eat`. `replace_with_item` is the itemstring which is added to the inventory. If the player is eating a stack, then replace_with_item goes to a different spot. +---@field override_item fun(name: string, redefinition: ItemDef|NodeDef): nil Overrides fields of an item registered with register_node/tool/craftitem. Note: Item must already be defined, (opt)depend on the mod defining it. Example: `minetest.override_item("default:mese", {light_source=minetest.LIGHT_MAX})` +---@field register_decoration fun(decoration_definition: DecorationDef): number|integer Returns an integer object handle uniquely identifying the registered decoration on success. To get the decoration ID, use `minetest.get_decoration_id`. The order of decoration registrations determines the order of decoration generation. +---@field find_node_near fun(pos: Vector, radius: number, nodenames: string[], search_center?: boolean): Vector|nil returns pos or `nil`. `radius`: using a maximum metric, `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`, `search_center` is an optional boolean (default: `false`) If true `pos` is also checked for the nodes +---@field remove_node fun(pos: Vector): nil By default it does the same as `minetest.set_node(pos, {name="air"})` +---@field get_node_or_nil fun(pos: Vector): NodeDef|nil Same as `get_node` but returns `nil` for unloaded areas. +---@field facedir_to_dir fun(facedir: number): Vector Convert a facedir back into a vector aimed directly out the "back" of a node. +---@field record_protection_violation fun(pos: Vector, name: string): nil This function calls functions registered with `minetest.register_on_protection_violation`. +---@field dir_to_facedir fun(dir: Vector, is6d?: any): number Convert a vector to a facedir value, used in `param2` for `paramtype2="facedir"`. passing something non-`nil`/`false` for the optional second parameter causes it to take the y component into account. +---@field register_lbm fun(lbm_definition: LbmDef): nil +---@field rotate_node fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef): nil calls `rotate_and_place()` with `infinitestacks` set according to the state of the creative mode setting, checks for "sneak" to set the `invert_wall` parameter and `prevent_after_place` set to `true`. +---@field global_exists fun(name: string): nil Checks if a global variable has been set, without triggering a warning. +---@field register_alias fun(alias: string|MapgenAliasesV6|MapgenAliasesNonV6, original_name: string): nil Also use this to set the 'mapgen aliases' needed in a game for the core mapgens. See [Mapgen aliases] section above. +---@field register_alias_force fun(alias: string|MapgenAliasesV6|MapgenAliasesNonV6, original_name: string): nil +---@field add_item fun(pos: Vector, item: ItemStack): ObjectRef|nil Spawn item. Returns `ObjectRef`, or `nil` if failed. +---@field registered_items table<string, ItemDef> Map of registered items, indexed by name +---@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:<whatever>"` naming convention. `formspec`: formspec to display +---@field register_on_player_receive_fields fun(func: fun(player: ObjectRef, formname: string, fields: table)): nil Called when the server received input from `player` in a formspec with the given `formname`. Specifically, this is called on any of the following events: a button was pressed, Enter was pressed while the focus was on a text field, a checkbox was toggled, something was selected in a dropdown list, a different tab was selected, selection was changed in a textlist or table, an entry was double-clicked in a textlist or table, a scrollbar was moved, or the form was actively closed by the player. +---@field get_inventory fun(location: {['"type"']: 'player'|'node'|'detached', ['"name"']: string|nil, ['"pos"']: Vector|nil}): InvRef +---@field dir_to_wallmounted fun(dir: Vector): number Convert a vector to a wallmounted value, used for `paramtype2="wallmounted"` +---@field item_place_node fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, param2?: string , prevent_after_place?: boolean): Vector|nil Place item as a node, `param2` overrides `facedir` and wallmounted `param2`, `prevent_after_place`: if set to `true`, `after_place_node` is not called or the newly placed node to prevent a callback and placement loop. returns `itemstack, position`, `position`: the location the node was placed to. `nil` if nothing was placed. +---@field unregister_item fun(name: string): nil Unregisters the item from the engine, and deletes the entry with key `name` from `minetest.registered_items` and from the associated item table according to its nature: `minetest.registered_nodes`, etc. +---@field register_allow_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): number): nil Determines how much of a stack may be taken, put or moved to a player inventory. `player` (type `ObjectRef`) is the player who modified the inventory, `inventory` (type `InvRef`). List of possible `action` (string) values and their `inventory_info` (table) contents: `move`: `{from_list=string, to_list=string, from_index=number, to_index=number, count=number}`, `put`: `{listname=string, index=number, stack=ItemStack}`, `take`: Same as `put`. Return a numeric value to limit the amount of items to be taken, put or moved. A value of `-1` for `take` will make the source stack infinite. +---@field register_on_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): nil): nil Called after a take, put or move event from/to/in a player inventory. Function arguments: see `minetest.register_allow_player_inventory_action`. Does not accept or handle any return value. +---@field formspec_escape fun(str: string): string returns a string, escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs. +---@field get_translator fun(textdomain: string): any +---@field get_current_modname fun(): string returns the currently loading mod's name, when loading a mod. +---@field get_pointed_thing_position fun(pointed_thing: PointedThingDef, above?: boolean): Vector | nil Returns the position of a `pointed_thing` or `nil` if the `pointed_thing` does not refer to a node or entity. If the optional `above` parameter is true and the `pointed_thing` refers to a node, then it will return the `above` position of the `pointed_thing`. +---@field item_place fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, param2?: string|number): ItemStack Wrapper that calls `minetest.item_place_node` if appropriate. Calls `on_rightclick` of `pointed_thing.under` if defined instead **Note**: is not called when wielded item overrides `on_place`, `param2` overrides facedir and wallmounted `param2`, returns `itemstack, position`, `position`: the location the node was placed to. `nil` if nothing was placed. +---@field node_dig fun(pos: Vector, node: NodeDef, digger: ObjectRef): nil Checks if node can be dug, puts item into inventory, removes node. Calls functions registered by `minetest.registered_on_dignodes()` +---@field delete_particlespawner fun(id: number, player?: ObjectRef): nil Delete `ParticleSpawner` with `id` (return value from `minetest.add_particlespawner`). If playername is specified, only deletes on the player's client, otherwise on all clients. +---@field is_area_protected fun(pos1: Vector, pos2: Vector, player_name: ObjectRef, interval: number): Vector | boolean Returns the position of the first node that `player_name` may not modify in the specified cuboid between `pos1` and `pos2`. Returns `false` if no protections were found. Applies `is_protected()` to a 3D lattice of points in the defined volume. The points are spaced evenly throughout the volume and have a spacing similar to, but no larger than, `interval`. All corners and edges of the defined volume are checked. `interval` defaults to 4. `interval` should be carefully chosen and maximised to avoid an excessive number of points being checked. Like `minetest.is_protected`, this function may be extended or overwritten by mods to provide a faster implementation to check the cuboid for intersections. +---@field is_protected fun(pos: Vector, name: string): boolean Returning `true` restricts the player `name` from modifying (i.e. digging, placing) the node at position `pos`. `name` will be `""` for non-players or unknown players. This function should be overridden by protection mods. It is highly recommended to grant access to players with the `protection_bypass` privilege. Cache and call the old version of this function if the position is not protected by the mod. This will allow using multiple protection mods. +---@field register_on_mods_loaded fun(func: fun(): nil): nil Called after mods have finished loading and before the media is cached or the aliases handled. +---@field register_on_leaveplayer fun(func: fun(player: ObjectRef, timed_out: number): nil): nil Called when a player leaves the game `timed_out`: True for timeout, false for other reasons. +---@field place_node fun(pos: Vector, node: SetNodeTable): nil Place node with the same effects that a player would cause +---@field add_particle fun(def: ParticleDef): nil + +---Minetest settings +---@class MinetestSettings +---@field get fun(self: MinetestSettings, key: string): string|number|integer Returns a value +---@field get_bool fun(self: MinetestSettings, key: string, default?: boolean): boolean|nil Returns a boolean. `default` is the value returned if `key` is not found. Returns `nil` if `key` is not found and `default` not specified. +---@field get_np_group fun(self: MinetestSettings, key: string): table Returns a NoiseParams table +---@field get_flags fun(self: MinetestSettings, key: string): table Returns `{flag = true/false, ...}` according to the set flags. Is currently limited to mapgen flags `mg_flags` and mapgen-specific flags like `mgv5_spflags`. +---@field set fun(self: MinetestSettings, key: string, value: string|integer|number): nil Setting names can't contain whitespace or any of `="{}#`. Setting values can't contain the sequence `\n"""`. Setting names starting with "secure." can't be set on the main settings object (`minetest.settings`). +---@field set_bool fun(self: MinetestSettings, key: string, value: boolean): nil Setting names can't contain whitespace or any of `="{}#`. Setting values can't contain the sequence `\n"""`. Setting names starting with "secure." can't be set on the main settings object (`minetest.settings`). +---@field set_np_group fun(self: MinetestSettings, key: string, value: table): nil `value` is a NoiseParams table. +---@field remove fun(self: MinetestSettings, key: string): boolean Returns a boolean (`true` for success) +---@field get_names fun(): table Returns `{key1,...}` +---@field write fun(): boolean Returns a boolean (`true` for success). Writes changes to file. +---@field to_table fun(): table Returns `{[key1]=value1,...}` + +--- Set node table +---@class SetNodeTable +---@field name string +---@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 + +--- Biome data +---@class BiomeData +---@field biome string|number|integer the biome id of the biome at that position +---@field heat string|number|integer the heat at the position +---@field humidity string|number|integer the humidity at the position + +--- LBM (LoadingBlockModifier) definition. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined by `nodenames`) when a mapblock which contains such nodes gets activated (not loaded!) +---@class LbmDef +---@field label string Descriptive label for profiling purposes (optional). Definitions with identical labels will be listed as one. +---@field name string Identifier of the LBM, should follow the modname:<whatever> convention +---@field nodenames string[] List of node names to trigger the LBM on. Names of non-registered nodes and groups (as group:groupname) will work as well. +---@field run_at_every_load boolean Whether to run the LBM's action every time a block gets activated, and not only the first time the block gets activated after the LBM was introduced. +---@field action fun(pos: Vector, node: NodeDef): nil Function triggered for each qualifying node. + +--- Sound parameters. +--- Looped sounds must either be connected to an object or played locationless to one player using `to_player = name`. A positional sound will only be heard by players that are within `max_hear_distance` of the sound position, at the start of the sound. `exclude_player = name` can be applied to locationless, positional and object-bound sounds to exclude a single player from hearing them. +---@class SoundParamDef +---@field to_player string Name +---@field gain number|integer +---@field fade number|integer Change to a value > 0 to fade the sound in +---@field pitch number|integer +---@field loop boolean +---@field pos Vector +---@field max_hear_distance number|integer +---@field object ObjectRef +---@field exclude_player string Name + +---Partcile definition +---@class ParticleDef +---@field pos Vector +---@field velocity Vector +---@field acceleration Vector Spawn particle at pos with velocity and acceleration +---@field expirationtime number Disappears after expirationtime seconds +---@field size number Scales the visual size of the particle texture. If `node` is set, size can be set to 0 to spawn a randomly-sized particle (just like actual node dig particles). +---@field collisiondetection boolean If true collides with `walkable` nodes and, depending on the `object_collision` field, objects too. +---@field collision_removal boolean If true particle is removed when it collides. Requires collisiondetection = true to have any effect. +---@field object_collision boolean If true particle collides with objects that are defined as `physical = true,` and `collide_with_objects = true,`. Requires collisiondetection = true to have any effect. +---@field vertical boolean If true faces player using y axis only +---@field texture string The texture of the particle v5.6.0 and later: also supports the table format described in the following section +---@field playername string | nil Optional, if specified spawns particle only on the player's client +---@field animation TileAnimationDef | nil Optional, specifies how to animate the particle texture +---@field glow number | nil Optional, specify particle self-luminescence in darkness. Values 0-14. +---@field node {["name"]: string, ["param2"]: number} | nil Optional, if specified the particle will have the same appearance as node dig particles for the given node. `texture` and `animation` will be ignored if this is set. +---@field node_tile number | nil Optional, only valid in combination with `node` If set to a valid number 1-6, specifies the tile from which the particle texture is picked. Otherwise, the default behavior is used. (currently: any random tile) +---@field drag Vector | nil v5.6.0 and later: Optional drag value, consult the following section +---@field bounce {["min"]: number, ["max"]: number, ["bias"]: number} | nil v5.6.0 and later: Optional bounce range, consult the following section diff --git a/types/mtg-creative.type.lua b/types/mtg-creative.type.lua new file mode 100644 index 0000000..4b4199d --- /dev/null +++ b/types/mtg-creative.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game creative mod +---@class MtgCreative +---@field is_enabled_for fun(name: string): boolean Returning `true` means that Creative Mode is enabled for player `name`. `name` will be `""` for non-players or if the player is unknown. By default, this function returns `true` if the setting `creative_mode` is `true` and `false` otherwise. diff --git a/types/mtg-default.type.lua b/types/mtg-default.type.lua new file mode 100644 index 0000000..0f10ba4 --- /dev/null +++ b/types/mtg-default.type.lua @@ -0,0 +1,40 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game default mod +---@class MtgDefault +---@field LIGHT_MAX number|integer The maximum light level. Maximum light to grow. +---@field can_grow fun(pos: Vector): boolean Grow trees from saplings +---@field grow_new_apple_tree fun(pos: Vector): nil +---@field grow_new_jungle_tree fun(pos: Vector): nil +---@field grow_new_emergent_jungle_tree fun(pos: Vector): nil +---@field grow_new_acacia_tree fun(pos: Vector): nil +---@field grow_new_aspen_tree fun(pos: Vector): nil +---@field grow_new_snowy_pine_tree fun(pos: Vector): nil +---@field grow_new_pine_tree fun(pos: Vector): nil +---@field grow_bush fun(pos: Vector): nil +---@field grow_acacia_bush fun(pos: Vector): nil +---@field grow_pine_bush fun(pos: Vector): nil +---@field grow_blueberry_bush fun(pos: Vector): nil +---@field grow_papyrus fun(pos: Vector, node: NodeDef): nil +---@field grow_large_cactus fun(pos: Vector, node: NodeDef): nil +---@field sapling_on_place fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, sapling_name: string, minp_relative: Vector, maxp_relative: Vector, interval: number): nil Sapling 'on place' function to check protection of node and resulting tree volume +---@field register_leafdecay fun(def: RegisterLeafdecayDef): nil +---@field after_place_leaves fun(pos: Vector, placer: ObjectRef, itemstack?: ItemStack, pointed_thing?: PointedThingDef): nil Prevent decay of placed leaves +---@field node_sound_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_stone_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_dirt_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_sand_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_wood_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_leaves_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_glass_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_metal_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_ice_defaults fun(table?: NodeSoundDef): NodeSoundDef +---@field node_sound_gravel_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 +---@field trunks string[] +---@field leaves string[] +---@field radius number diff --git a/types/mtg-dungeon-loot.type.lua b/types/mtg-dungeon-loot.type.lua new file mode 100644 index 0000000..88faa2b --- /dev/null +++ b/types/mtg-dungeon-loot.type.lua @@ -0,0 +1,15 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game dungeon loot mod API +---@class MtgDungeonLoot +---@field register fun(loot_definition: MtgDungeonLootDef): nil Registers one or more loot items, `def` Can be a single loot_definition or a list of them. +---@field registered_loot table Table of all registered loot, not to be modified manually + +---Loot definition +---@class MtgDungeonLootDef +---@field name string +---@field chance number chance value from 0.0 to 1.0 that the item will appear in the chest when chosen, Due to an extra step in the selection process, 0.5 does not(!) mean that on average every second chest will have this item +---@field count number[]|nil table with minimum and maximum amounts of this item, optional, defaults to always single item +---@field y number[]|nil table with minimum and maximum heights this item can be found at, optional, defaults to no height restrictions +---@field types string[]|nil table with types of dungeons this item can be found in supported types: "normal" (the cobble/mossycobble one), "sandstone", "desert" and "ice", optional, defaults to no type restrictions diff --git a/types/mtg-farming.type.lua b/types/mtg-farming.type.lua new file mode 100644 index 0000000..62c6638 --- /dev/null +++ b/types/mtg-farming.type.lua @@ -0,0 +1,17 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game farming mod +---@class MtgFarming +---@field hoe_on_use fun(itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef, uses: number): ItemStack | nil +---@field place_seed fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: PointedThingDef, plantname: string): ItemStack Seed placement +---@field grow_plant fun(pos: Vector, elapsed: number): nil +---@field register_plant fun(name: string, def: table): nil + +----Node definition. Used by `minetest.register_node`. +---@class NodeDefMtgFarming +---@field fertility string[]|nil Used in default farming mod, defines biome name list where plants can grow +---@field steps number How many steps the plant has to grow, until it can be harvested +---@field minlight number Minimum light to grow +---@field maxlight number Maximum light to grow +---@field on_timer fun(pos: Vector, elapsed: number): boolean default: nil, called by NodeTimers, see minetest.get_node_timer and NodeTimerRef. elapsed is the total time passed since the timer was started. return true to run the timer for another cycle with the same timeout value. diff --git a/types/mtg-player-api.type.lua b/types/mtg-player-api.type.lua new file mode 100644 index 0000000..f9a5cc3 --- /dev/null +++ b/types/mtg-player-api.type.lua @@ -0,0 +1,33 @@ +---@diagnostic disable: codestyle-check +---The player API can register player models and update the player's appearance. +---@class MtgPlayerApi +---@field globalstep fun(dtime: number, ...): nil The function called by the globalstep that controls player animations. You can override this to replace the globalstep with your own implementation. Receives all args that minetest.register_globalstep() passes +---@field register_model fun(name: string, def: MtgPlayerApiModelDef): nil Register a new model to be used by players, `name`: model filename such as "character.x", "foo.b3d", etc., `def`: see [#Model definition] Saved to player_api.registered_models +---@field registered_models string[] Get a model's definition, `name`: model filename See [#Model definition] +---@field set_model fun(player: ObjectRef, model_name: string): nil Change a player's model, `player`: PlayerRef, `model_name`: model registered with `player_api.register_model` +---@field set_animation fun(player: ObjectRef, anim_name: string, speed: number): nil Applies an animation to a player if speed or anim_name differ from the currently playing animation, `player`: PlayerRef, `anim_name`: name of the animation, `speed`: keyframes per second. If nil, the default from the model def is used +---@field set_textures fun(player: ObjectRef, textures: string[]): nil Sets player textures `player`: PlayerRef, `textures`: array of textures. If nil, the default from the model def is used +---@field set_texture fun(player: ObjectRef, index: number, texture: string): nil Sets one of the player textures, `player`: PlayerRef, `index`: Index into array of all textures, `texture`: the texture string +---@field get_textures fun(player: ObjectRef): string[] Returns player textures table +---@field get_animation fun(player: ObjectRef): {["model"]: string | nil, ["textures"]: string[] | nil, ["animation"]: table | nil} Returns a table containing fields `model`, `textures` and `animation`. Any of the fields of the returned table may be nil, `player`: PlayerRef +---@field player_attached table<string, boolean> A table that maps a player name to a boolean. If the value for a given player is set to true, the default player animations (walking, digging, ...) will no longer be updated, and knockback from damage is prevented for that player. Example of usage: A mod sets a player's value to true when attached to a vehicle. + + +---Model Definition +---@class MtgPlayerApiModelDef +---@field animation_speed number Default: 30, animation speed, in keyframes per second +---@field textures string[] Default `{"character.png"}`, array of textures +---@field animations table<string, MtgPlayerApiAnimationDef> +---@field visual_size {["x"]: number, ["y"]: number} +---@field collisionbox number[] +---@field stepheight number +---@field eye_height number + + +---Model Animation definition +---@class MtgPlayerApiAnimationDef +---@field x number start frame +---@field y number end frame +---@field collisionbox number[] | nil +---@field eye_height number | nil model eye height +---@field override_local boolean | nil suspend client side animations while this one is active (optional) diff --git a/types/mtg-screwdriver.type.lua b/types/mtg-screwdriver.type.lua new file mode 100644 index 0000000..5550b16 --- /dev/null +++ b/types/mtg-screwdriver.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game screwdriver mod +---@class MtgScrewdriver +---@field disallow fun(): boolean Returns `false` diff --git a/types/mtg-sfinv.lua b/types/mtg-sfinv.lua new file mode 100644 index 0000000..bec0f9c --- /dev/null +++ b/types/mtg-sfinv.lua @@ -0,0 +1,26 @@ +---@diagnostic disable: codestyle-check +---Sfinv API +---@class Sfinv +---@field register_page fun(name: string, def: SfinvDef): nil Register a page +---@field make_formspec fun(player: ObjectRef, contex: SfinvContext, content: string, show_inv?: boolean, size?: string): nil Adds a theme to a formspec show_inv, defaults to false. Whether to show the player's main inventory size, defaults to `size[8,8.6]` if not specified +---@field get_or_create_context fun(player: ObjectRef): SfinvContext Gets the player's context +---@field set_context fun(player: ObjectRef, context: SfinvContext): nil +---@field get_formspec fun(player: ObjectRef, context: SfinvContext): string Builds current page's formspec +---@field set_player_inventory_formspec fun(player: ObjectRef): string (re)builds page formspec and calls set_inventory_formspec(). + + +---Sfinv Definition +---@class SfinvDef +---@field title string Human readable page name (required) +---@field get fun(self: Sfinv, player: ObjectRef, context: SfinvContext): string Returns a formspec string. See formspec variables. (required) +---@field is_in_nav fun(self: Sfinv, player: ObjectRef, context: SfinvContext): boolean Return true to show in the navigation (the tab header, by default) +---@field on_player_receive_fields fun(self: Sfinv, player: ObjectRef, context: SfinvContext, fields: table): nil On formspec submit. +---@field on_enter fun(self: Sfinv, player: ObjectRef, context: SfinvContext): nil Called when the player changes pages, usually using the tabs. +---@field on_leave fun(self: Sfinv, player: ObjectRef, context: SfinvContext): nil When leaving this page to go to another, called before other's on_enter + +---Sfinv Context, including: any thing you want to store, sfinv will clear the stored data on log out / log in +---@class SfinvContext +---@field page string Current page name +---@field nav string[] A list of page names +---@field nav_titles string[] A list of page titles +---@field nav_idx number Current nav index (in nav and nav_titles) diff --git a/types/mtg-stairs.type.lua b/types/mtg-stairs.type.lua new file mode 100644 index 0000000..1984d79 --- /dev/null +++ b/types/mtg-stairs.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Minetest game stairs mod +---@class MtgStairs +---@field register_stair_and_slab fun(subname: string, recipeitem?: string, groups: GroupCapsDef, images: NodeTilesDef, desc_stair: string, desc_slab: string, sounds: NodeSoundDef, worldaligntex?: boolean, desc_stair_inner?: string, desc_stair_outer?: string): nil `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_subname", `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`, `groups`: damage and digging time defining groups, `worldaligntex`: A bool to set all textures world-aligned. Default false. diff --git a/types/node.type.lua b/types/node.type.lua new file mode 100644 index 0000000..d2db139 --- /dev/null +++ b/types/node.type.lua @@ -0,0 +1,53 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias NodeDef NodeDefAbstract | NodeDefMtgFarming + +---Node definition. Used by `minetest.register_node`. +---@class NodeDefAbstract +---@field name string +---@field next_plant string|nil +---@field liquidtype 'none'|'source'|'flowing' specifies liquid flowing physics, "none": no liquid flowing physics, "source": spawns flowing liquid nodes at all 4 sides and below; recommended drawtype: "liquid". "flowing": spawned from source, spawns more flowing liquid nodes around it until `liquid_range` is reached; will drain out without a source; recommended drawtype: "flowingliquid". If it's "source" or "flowing" and `liquid_range > 0`, then both `liquid_alternative_*` fields must be specified +---@field on_rightclick fun(pos: Vector, node: NodeDef, clicker: ObjectRef, itemstack: ItemStack, pointed_thing?: PointedThingDef): ItemStack default: nil, Called when clicker (an ObjectRef) used the 'place/build' key not neccessarily an actual rightclick) while pointing at the node at pos with 'node' being the node table. itemstack will hold clicker's wielded item. Shall return the leftover itemstack. Note: pointed_thing can be nil, if a mod calls this function. This function does not get triggered by clients <=0.4.16 if the "formspec" node metadata field is set. +---@field place_param2 number Value for param2 that is set when player places node +---@field param2 number Value for param2 that is set when player places node +---@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. +---@field after_dig_node fun(pos: Vector, oldnode: NodeDef, oldmetadata: table, digger: ObjectRef): nil oldmetadata is in table format. Called after destructing node when node was dug using minetest.node_dig / minetest.dig_node., default: nil +---@field paramtype2 string +---@field palette string Image + +---Textures of node; +Y, -Y, +X, -X, +Z, -Z. List can be shortened to needed length. +---@class NodeTilesDef +---@field name string +---@field animation TileAnimationDef +---@field backface_culling boolean backface culling enabled by default for most nodes +---@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/nodetimer.type.lua b/types/nodetimer.type.lua new file mode 100644 index 0000000..7a96ea7 --- /dev/null +++ b/types/nodetimer.type.lua @@ -0,0 +1,12 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Node Timers: a high resolution persistent per-node timer. Can be gotten via `minetest.get_node_timer(pos)`. +---@class NodeTimerRef +---@field set fun(self: NodeTimerRef, timeout: integer|number, elapsed: integer|number): nil Set a timer's state. `timeout` is in seconds, and supports fractional values (0.1 etc). `elapsed` is in seconds, and supports fractional values (0.1 etc). Will trigger the node's `on_timer` function after `(timeout - elapsed)` seconds. +---@field start fun(self: NodeTimerRef, timeout: integer|number): nil Start a timer. Equivalent to `set(timeout,0)`. +---@field stop fun(): nil Stops the timer +---@field get_timeout fun(): number|integer Returns current timeout in seconds. +---@field get_elapsed fun(): number|integer Returns current elapsed time in seconds. +---@field is_started fun(): boolean Returns boolean state of timer. Returns `true` if timer is started, otherwise `false`. +---@field get_meta fun(pos: Vector): MetaDataRef Get a `NodeMetaRef` at that position diff --git a/types/object.type.lua b/types/object.type.lua new file mode 100644 index 0000000..3fea64e --- /dev/null +++ b/types/object.type.lua @@ -0,0 +1,89 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---@alias ObjectRef ObjectRefAbstract | ObjectRefLuaEntityRef + +---Moving things in the game are generally these. +---This is basically a reference to a C++ `ServerActiveObject`. +---@class ObjectRefAbstract +---@field get_pos fun(): Vector Position of player +---@field get_inventory fun(): InvRef|nil Returns an `InvRef` for players, otherwise returns `nil` +---@field get_wield_index fun(): integer Returns the index of the wielded item +---@field get_wielded_item fun(): ItemStack Returns an `ItemStack` +---@field set_acceleration fun(self: ObjectRef, acc: Vector): nil +---@field set_yaw fun(self: ObjectRef, yaw: integer|number): nil Sets the yaw in radians (heading). +---@field get_player_name fun(self: ObjectRef): string Returns `""` if is not a player. +---@field set_fov fun(self: ObjectRef, fov: number|integer, is_multiplier: boolean, transition_time: number|integer): nil Sets player's FOV. `fov`: FOV value. `is_multiplier`: Set to `true` if the FOV value is a multiplier. Defaults to `false`. `transition_time`: If defined, enables smooth FOV transition. Interpreted as the time (in seconds) to reach target FOV. If set to 0, FOV change is instantaneous. Defaults to 0. Set `fov` to 0 to clear FOV override. +---@field get_hp fun(self: ObjectRef): number|integer Returns number of health points +---@field is_player fun(self: ObjectRef): boolean returns true for players, false otherwise +---@field get_luaentity fun(self: ObjectRef): table +---@field get_armor_groups fun(self: ObjectRef): ObjectRefArmorGroups returns a table with the armor group ratings +---@field punch fun(self: ObjectRef, puncher: ObjectRef, time_from_last_punch: integer|number, tool_capabilities: ToolCapabilitiesDef, direction: Vector|nil): nil +---@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. +---@field move_to fun(self: ObjectRef, pos: Vector, continuous?: boolean): nil Does an interpolated move for Lua entities for visually smooth transitions. If `continuous` is true, the Lua entity will not be moved to the current position before starting the interpolated move. For players this does the same as `set_pos`,`continuous` is ignored. +---@field set_hp fun(self: ObjectRef, hp: number, reason: table): nil set number of health points See reason in register_on_player_hpchange Is limited to the range of 0 ... 65535 (2^16 - 1) For players: HP are also limited by `hp_max` specified in object properties +---@field set_animation fun(self: ObjectRef, frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean): nil `frame_range`: table {x=num, y=num}, default: `{x=1, y=1}`, `frame_speed`: number, default: `15.0`, `frame_blend`: number, default: `0.0`, `frame_loop`: boolean, default: `true` +---@field get_velocity fun(self: ObjectRef): Vector returns the velocity, a vector. +---@field set_rotation fun(self: ObjectRef, rot: Vector): nil `rot` is a vector (radians). X is pitch (elevation), Y is yaw (heading) and Z is roll (bank). +---@field set_pos fun(self: ObjectRef, pos: Vector): nil + + +---Moving things in the game are generally these. +---This is basically a reference to a C++ `ServerActiveObject`. +---@class ObjectRefLuaEntityRef +---@field set_velocity fun(self: ObjectRef, vel: Vector): nil `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` +---@field remove fun(): nil remove object, The object is removed after returning from Lua. However the `ObjectRef` itself instantly becomes unusable with all further method calls having no effect and returning `nil`. +---@field get_rotation fun(self: ObjectRef): Vector returns the rotation, a vector (radians) +---@field get_attach fun(self: ObjectRef): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. +---@field set_attach fun(self: ObjectRef, parent: ObjectRef, bone?: string, position?: Vector, rotation?: Vector, forced_visible?: boolean): any Returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. + +---`ObjectRef` armor groups +---@class ObjectRefArmorGroups +---@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/particlespawner.type.lua b/types/particlespawner.type.lua new file mode 100644 index 0000000..73d2a91 --- /dev/null +++ b/types/particlespawner.type.lua @@ -0,0 +1,50 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +--- ParticleSpawner definition +---@class ParticlespawnerDef +---@field amount number|integer Number of particles spawned over the time period `time`. +---@field time number|integer Lifespan of spawner in seconds. If time is 0 spawner has infinite lifespan and spawns the `amount` on a per-second basis. +---@field collisiondetection boolean If true collide with `walkable` nodes and, depending on the `object_collision` field, objects too. +---@field collision_removal boolean If true particles are removed when they collide. Requires collisiondetection = true to have any effect. +---@field object_collision boolean If true particles collide with objects that are defined as `physical = true,` and `collide_with_objects = true,`. Requires collisiondetection = true to have any effect. +---@field attached ObjectRef If defined, particle positions, velocities and accelerations are relative to this object's position and yaw +---@field vertical boolean If true face player using y axis only +---@field texture string The texture of the particle. e,g, `"image.png"` +---@field playername string Optional, if specified spawns particles only on the player's client +---@field animation TileAnimationDef Optional, specifies how to animate the particles' texture. v5.6.0 and later: set length to -1 to sychronize the length of the animation with the expiration time of individual particles. (-2 causes the animation to be played twice, and so on) +---@field glow number|integer Optional, specify particle self-luminescence in darkness. Values 0-14. +---@field node table<string, string|number|integer> e.g. `{name = "ignore", param2 = 0}`. Optional, if specified the particles will have the same appearance as node dig particles for the given node. Texture` and `animation` will be ignored if this is set. +---@field node_tile number|integer Optional, only valid in combination with `node`. If set to a valid number 1-6, specifies the tile from which the particle texture is picked. Otherwise, the default behavior is used. (currently: any random tile) +---@field minpos Vector Legacy definition field +---@field maxpos Vector Legacy definition field +---@field minvel Vector Legacy definition field +---@field maxvel Vector Legacy definition field +---@field minacc Vector Legacy definition field +---@field maxacc Vector Legacy definition field +---@field minexptime number|integer Legacy definition field +---@field maxexptime number|integer Legacy definition field +---@field minsize number|integer Legacy definition field +---@field maxsize number|integer Legacy definition field +---@field pos number|integer|Vector|ParticlespawnerPosDef As `number`: absolute value - all components of every particle's position vector will be set to this. As `Vector`: vec3 - all particles will appear at this exact position throughout the lifetime of the particlespawner. As `ParticlespawnerPosDef`: vec3 range - the particle will appear at a position that is picked at random from within a cubic range. + +--- ParticleSpawner pos definition +---@class ParticlespawnerPosDef +---@field min Vector The minimum value this property will be set to in particles spawned by the generator. +---@field max Vector The maximum value this property will be set to in particles spawned by the generator. +---@field bias number|integer When `bias` is 0, all random values are exactly as likely as any other. When it is positive, the higher it is, the more likely values will appear towards the minimum end of the allowed spectrum. When it is negative, the lower it is, the more likely values will appear towards the maximum end of the allowed spectrum. The curve is exponential and there is no particular maximum or minimum value. +---@field pos_tween ParticlespawnerPosTweenDef A tween table should consist of a list of frames in the same form as the untweened pos property above, which the engine will interpolate between, and optionally a number of properties that control how the interpolation takes place. Currently **only two frames**, the first and the last, are used, but extra frames are accepted for the sake of forward compatibility. Any of the above definition styles can be used here as well in any combination supported by the property type. + +--- ParticleSpawner pos_tween definition +---@class ParticlespawnerPosTweenDef +---@field style string e.g. "fwd": linear animation from first to last frame (default), "rev": linear animation from last to first frame, "pulse": linear animation from first to last then back to first again, "flicker": like "pulse", but slightly randomized to add a bit of stutter +---@field reps number|integer Number of times the animation is played over the particle's lifespan +---@field start number|integer Point in the spawner's lifespan at which the animation begins. 0 is the very beginning, 1 is the very end. +---@field frames number|integer|Vector|ParticlespawnerPosDef Frames can be defined in a number of different ways, depending on the underlying type of the property. For now, all but the first and last frame are ignored. + +--- Tile animation definition +---@class TileAnimationDef +---@field type string e.g. "vertical_frames", "sheet_2d" +---@field aspect_w number|integer Width of a frame in pixels +---@field aspect_h number|integer Height of a frame in pixels +---@field length number|integer e.g. 3.0 Full loop length, 0.5 Length of a single frame diff --git a/types/pointed-thing.type.lua b/types/pointed-thing.type.lua new file mode 100644 index 0000000..8c21680 --- /dev/null +++ b/types/pointed-thing.type.lua @@ -0,0 +1,12 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Pointed thing definition +---@class PointedThingDef +---@field type string e.g. `{type="nothing"}` `{type="node"}` `{type="object"}` +---@field under Vector Refers to the node position behind the pointed face. +---@field above Vector Refers to the node position in front of the pointed face. +---@field ref ObjectRef e.g. `{type="object", ref=ObjectRef}` +---@field intersection_point Vector Exact pointing location (currently only `Raycast` supports these field). The absolute world coordinates of the point on the selection box which is pointed at. May be in the selection box if the pointer is in the box too. +---@field box_id number|integer Exact pointing location (currently only `Raycast` supports these field). The ID of the pointed selection box (counting starts from 1). +---@field intersection_normal Vector Exact pointing location (currently only `Raycast` supports these field). Unit vector, points outwards of the selected selection box. This specifies which face is pointed at. Is a null vector `vector.zero()` when the pointer is inside the selection box. diff --git a/types/raycast.type.lua b/types/raycast.type.lua new file mode 100644 index 0000000..b0679fe --- /dev/null +++ b/types/raycast.type.lua @@ -0,0 +1,6 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---A raycast on the map. It works with selection boxes. The map is loaded as the ray advances. If the map is modified after the `Raycast` is created, the changes may or may not have an effect on the object. +---@class Raycast +---@field next fun(): PointedThingDef Returns a `pointed_thing` with exact pointing location. Returns the next thing pointed by the ray or nil. diff --git a/types/sound.type.lua b/types/sound.type.lua new file mode 100644 index 0000000..0ea957c --- /dev/null +++ b/types/sound.type.lua @@ -0,0 +1,28 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +---Specifies a sound name, gain (=volume) and pitch. This is either a string or a table. In string form, you just specify the sound name or the empty string for no sound. +---@class SimpleSoundSpec +---@field name string Sound name. +---@field gain number|integer Volume (`1.0` = 100%). Optional and default to `1.0`. +---@field pitch number|integer Pitch (`1.0` = 100%). Optional and default to `1.0`. + +--- 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_<groupname>`, where `<groupname>` is the name of the item's digging group with the fastest digging time. In case of a tie, one of the sounds will be played (but we cannot predict which one) Default value: `"__group"` +---@field dug SimpleSoundSpec Node was dug +---@field place SimpleSoundSpec Node was placed. Also played after falling +---@field place_failed SimpleSoundSpec When node placement failed. Note: This happens if the _built-in_ node placement failed. This sound will still be played if the node is placed in the `on_place` callback manually. +---@field fall SimpleSoundSpec When node starts to fall or is detached + +---Definition of item sounds to be played at various events. All fields in this table are optional. +---@class ItemSoundDef +---@field breaks SimpleSoundSpec|string When tool breaks due to wear. Ignored for non-tools +---@field eat SimpleSoundSpec|string When item is eaten with `minetest.do_item_eat` +---@field punch_use SimpleSoundSpec|string When item is used with the 'punch/mine' key pointing at a node or entity +---@field punch_use_air SimpleSoundSpec|string When item is used with the 'punch/mine' key pointing at nothing (air) + +---Dig params definition. +---@class DigParamsDef diff --git a/types/string.type.lua b/types/string.type.lua new file mode 100644 index 0000000..b86a4fc --- /dev/null +++ b/types/string.type.lua @@ -0,0 +1,8 @@ +---@diagnostic disable: codestyle-check, duplicate-doc-alias +---https://github.com/sumneko/lua-language-server/wiki + +---@alias string string|StringAbstract + +---String helpers +---@class StringAbstract +---@field split fun(self: string, str: string, separator?: string, include_empty?: boolean, max_splits?: number, sep_is_pattern?: boolean): table `separator`: string, default: `","`, `include_empty`: boolean, default: `false`, `max_splits`: number, if it's negative, splits aren't limited, default: `-1`, `sep_is_pattern`: boolean, it specifies whether separator is a plain string or a pattern (regex), default: `false`. e.g. `"a,b":split","` returns `{"a","b"}` diff --git a/types/table.type.lua b/types/table.type.lua new file mode 100644 index 0000000..f8acf47 --- /dev/null +++ b/types/table.type.lua @@ -0,0 +1,8 @@ +---@diagnostic disable: codestyle-check, duplicate-doc-alias +---https://github.com/sumneko/lua-language-server/wiki + +---@alias tablelib tablelib|TableAbstract + +---Table helpers +---@class TableAbstract +---@field copy fun(table: table): table returns a deep copy of `table` diff --git a/types/unified_inventory.type.lua b/types/unified_inventory.type.lua new file mode 100644 index 0000000..3f0baab --- /dev/null +++ b/types/unified_inventory.type.lua @@ -0,0 +1,8 @@ +---@diagnostic disable: codestyle-check +---Base class Unified Inventory +---@class UnifiedInventory +---@field set_inventory_formspec fun(player: ObjectRef, formspecname: string): nil +---@field register_button fun(name: string, def: table): nil +---@field single_slot fun(x: number, y: number): nil +---@field register_page fun(name: string, def: table): nil +---@field style_full table diff --git a/types/vector.type.lua b/types/vector.type.lua new file mode 100644 index 0000000..ceeb286 --- /dev/null +++ b/types/vector.type.lua @@ -0,0 +1,17 @@ +---@diagnostic disable: codestyle-check +---https://github.com/sumneko/lua-language-server/wiki + +------All `vector.*` functions allow vectors `{x = X, y = Y, z = Z}` without metatables. Returned vectors always have a metatable set. +---@class Vector +---@field x integer|number Pitch +---@field y integer|number Yaw +---@field z integer|number Roll +---@field multiply fun(v: Vector, s: number|integer): Vector Returns a scaled vector. Deprecated: If `s` is a vector: Returns the Schur product. +---@field subtract fun(v: Vector, x: number|integer|Vector): Vector Returns a vector. If `x` is a vector: Returns the difference of `v` subtracted by `x`. If `x` is a number: Subtracts `x` from each component of `v`. +---@field add fun(v: Vector, x: number|integer|Vector): Vector Returns a vector. If `x` is a vector: Returns the sum of `v` and `x`. If `x` is a number: Adds `x` to each component of `v`. +---@field normalize fun(v: Vector): Vector Returns a vector of length 1 with direction of vector `v`. If `v` has zero length, returns `(0, 0, 0)`. +---@field distance fun(p1: Vector, p2: Vector): number|integer Returns zero or a positive number, the distance between `p1` and `p2`. +---@field round fun(v: Vector): Vector Returns a vector, each dimension rounded to nearest integer. At a multiple of 0.5, rounds away from zero. +---@field new fun(a, b?, c?): Vector Returns a new vector `(a, b, c)`. +---@field direction fun(p1: Vector, p2: Vector): Vector Returns a vector of length 1 with direction `p1` to `p2`. If `p1` and `p2` are identical, returns `(0, 0, 0)`. +---@field divide fun(v: Vector, s: Vector | number): Vector Returns a scaled vector. Deprecated: If `s` is a vector: Returns the Schur quotient. diff --git a/types/xbows.type.lua b/types/xbows.type.lua new file mode 100644 index 0000000..58ac036 --- /dev/null +++ b/types/xbows.type.lua @@ -0,0 +1,210 @@ +---@diagnostic disable: codestyle-check +---Base class XBows +---@class XBows +---@field pvp boolean +---@field creative boolean +---@field mesecons string|nil +---@field playerphysics string|nil +---@field player_monoids string|nil +---@field u_skins string|nil +---@field wardrobe string|nil +---@field _3d_armor string|nil +---@field skinsdb string|nil +---@field player_api string|nil +---@field registered_bows table<string, ItemDef|BowItemDefCustom> +---@field registered_arrows table<string, ItemDef|ArrowItemDefCustom> +---@field registered_quivers table<string, ItemDef|QuiverItemDefCustom> +---@field registered_particle_spawners table<string, ParticlespawnerDef|ParticlespawnerDefCustom> +---@field registered_entities table<string, XBowsEntityDef> +---@field player_bow_sneak table<string, table<string, boolean>> +---@field settings {["x_bows_attach_arrows_to_entities"]: boolean | nil, ["x_bows_show_damage_numbers"]: boolean | nil, ["x_bows_show_3d_quiver"]: boolean | nil} +---@field quiver table Quiver class +---@field charge_sound_after_job table<string, JobTable> +---@field is_allowed_ammunition fun(self: XBows, weapon_name: string, ammo_name: string): boolean Check if ammunition is allowed to charge this weapon +---@field is_creative fun(self: XBows, name: string): boolean Check if creative is enabled or if player has creative priv +---@field get_particle_effect_for_arrow fun(self: XBows, name: string, pos: Vector): number|boolean Get particle effect from registered spawners table +---@field register_entity fun(self: EntityDef|XBows, name: string, def: XBowsEntityDef): nil Register new projectile entity +---@field update_bow_allowed_ammunition fun(self: XBows, name: string, def: string[]): nil Updates `allowed_ammunition` definition on already registered item, so MODs can add new ammunitions to this list. +---@field reset_charged_bow fun(self: XBows, player: ObjectRef, includeWielded?: boolean): nil Reset charged bow to uncharged bow, this will return the arrow item to the inventory also +---@field register_bow fun(self: XBows, name: string, def: ItemDef | BowItemDefCustom, mod_override?: boolean): boolean|nil Register new bow/gun. +---@field register_arrow fun(self: XBows, name: string, def: ItemDef | ArrowItemDefCustom): boolean|nil Register new arrow/projectile. +---@field register_quiver fun(self: XBows, name: string, def: ItemDef | ArrowItemDefCustom): boolean|nil Register new quiver. +---@field load fun(self: XBows, itemstack: ItemStack, user: ObjectRef, pointed_thing: PointedThingDef): ItemStack Load bow +---@field shoot fun(self: XBows, itemstack: ItemStack, user: ObjectRef, pointed_thing?: PointedThingDef): ItemStack Shoot bow +---@field register_particle_effect fun(self: XBows, name: string, def: ParticlespawnerDef|ParticlespawnerDefCustom): nil Add new particle to XBow registration +---@field open_quiver fun(self: XBowsQuiver, itemstack: ItemStack, user: ObjectRef): ItemStack Open quiver +---@field uuid fun(): string Creates UUID +---@field fallback_quiver boolean If no invenotory mod is detected then fallback solution will be used +---@field show_damage_numbers fun(self: XBows, pos: Vector, damaga: number, is_crit?: boolean): nil Builds textures and shows textures in particle spawner + + +---XBowsQuiver class extended from XBows +---@alias XBowsQuiver XBowsQuiverBase|XBows +---@class XBowsQuiverBase +---@field hud_item_ids table +---@field after_job table<string, JobTable> +---@field udate_or_create_hud fun(self: XBowsQuiver, player: ObjectRef, inv_list: ItemStack[], idx?: number): nil Update or create quiver HUD +---@field get_or_create_detached_inv fun(self: XBowsQuiver, quiver_id: string, player_name: string, quiver_items?: string): InvRef Get existing detached inventory or create new one +---@field save fun(self: XBowsQuiver, inv: InvRef, player: ObjectRef, quiver_is_closed?: boolean): nil Save quiver inventory to itemstack meta +---@field close_quiver fun(self: XBowsQuiver, player: ObjectRef, quiver_id?: string): nil Close one or all open quivers in players inventory +---@field get_replacement_item fun(self: XBowsQuiver, from_stack: ItemStack, to_item_name: string): ItemStack Swap item in player inventory indicating open quiver. Preserve all ItemStack definition and meta. +---@field get_itemstack_arrow_from_quiver fun(self: XBowsQuiver, player: ObjectRef, to_item_name: string): {["found_arrow_stack"]: ItemStack|nil, ["quiver_id"]: string|nil, ["quiver_name"]: string|nil, ["found_arrow_stack_idx"]: number} Gets arrow from quiver +---@field remove_hud fun(self: XBowsQuiver, player: ObjectRef): nil Remove all added HUDs +---@field get_formspec fun(self: XBowsQuiver, name: string): string Create formspec +---@field get_string_from_inv fun(self: XBowsQuiver, inv: InvRef): {['inv_string']: string, ['content_description']: string} Convert inventory of itemstacks to serialized string +---@field set_string_to_inv fun(self: XBowsQuiver, inv: InvRef, str: string): nil Set items from serialized string to inventory +---@field quiver_can_allow fun(self: XBowsQuiver, inv: InvRef, player: ObjectRef): boolean Check if we are allowing actions in the correct quiver inventory +---@field show_3d_quiver fun(self: XBowsQuiver, player: ObjectRef, props?: {["is_empty"]: boolean|nil}): nil Applies full/empty quiver textures +---@field hide_3d_quiver fun(self: XBowsQuiver, player: ObjectRef): nil Applies blank quiver textures - hiding the quiver +---@field sfinv_register_page fun(): nil register inventoy mod page +---@field i3_register_page fun(): nil register inventoy mod page +---@field ui_register_page fun(): nil register inventoy mod page + + +---Custom field in ParticlespawnerDef +---@class ParticlespawnerDefCustom +---@field custom ParticlespawnerDefCustomAttr + +---Custom field attributes in ParticlespawnerDef +---@class ParticlespawnerDefCustomAttr +---@field minpos Vector +---@field maxpos Vector + +---Custom field in ItemDef +---@class BowItemDefCustom +---@field custom BowItemDefCustomAttr + +---Custom field attributes in ItemDef +---@class BowItemDefCustomAttr +---@field crit_chance number `crit_chance` 10% chance, 5 is 20% chance, (1 / crit_chance) * 100 = % chance +---@field inventory_image_charged string +---@field recipe table +---@field fuel_burntime number +---@field name_charged string +---@field name string +---@field mod_name string +---@field uses number +---@field strength number How strong is the bow. Defines how far the arrow will fly. +---@field strength_min number|nil How strong is the bow. Defines how far the arrow will fly. +---@field strength_max number|nil How strong is the bow. Defines how far the arrow will fly. +---@field allowed_ammunition string[]|nil +---@field wield_image_charged string|nil +---@field acc_x_min number|nil +---@field acc_y_min number|nil +---@field acc_z_min number|nil +---@field acc_x_max number|nil +---@field acc_y_max number|nil +---@field acc_z_max number|nil +---@field sound_load string +---@field sound_hit string +---@field sound_shoot string +---@field sound_shoot_crit string +---@field gravity number + +---Custom field in ItemDef +---@class ArrowItemDefCustom +---@field custom ArrowItemDefCustomAttr + +---Custom field attributes in ItemDef +---@class ArrowItemDefCustomAttr +---@field tool_capabilities ToolCapabilitiesDef +---@field craft_count number +---@field recipe table +---@field fuel_burntime number +---@field name string +---@field mod_name string +---@field particle_effect string|nil +---@field particle_effect_crit string|nil +---@field particle_effect_fast string|nil +---@field projectile_entity string +---@field on_hit_node fun(self: table, pointed_thing_ref: table) +---@field on_hit_entity fun(self: table, pointed_thing_ref: table) +---@field on_hit_player fun(self: table, pointed_thing_ref: table) +---@field on_after_activate fun(self: table) +---@field description_abilities string + + +---Custom field in ItemDef +---@class QuiverItemDefCustom +---@field custom QuiverItemDefCustomAttr + +---Custom field attributes in ItemDef +---@class QuiverItemDefCustomAttr +---@field recipe table +---@field recipe_count number +---@field faster_arrows number +---@field add_damage number +---@field fuel_burntime number +---@field inventory_image_open string +---@field wield_image_open string +---@field name string + + +---Custom field in EntityDef +---@alias XBowsEntityDef EntityDef|EntityDefCustom|XBows +---@class EntityDefCustom +---@field on_death fun(self: XBowsEntityDef, selfObj: table, killer: ObjectRef|nil): nil Function receive a "luaentity" table as `self`. Called when the object dies. +---@field on_punch fun(self: XBowsEntityDef, selfObj: table, puncher: ObjectRef|nil, time_from_last_punch: number|integer|nil, tool_capabilities: ToolCapabilitiesDef|nil, dir: Vector, damage: number|integer): boolean|nil Function receive a "luaentity" table as `self`. Called when somebody punches the object. Note that you probably want to handle most punches using the automatic armor group system. Can return `true` to prevent the default damage mechanism. +---@field _custom EntityDefCustomAttr + +---@alias EntityDefCustomAttr EntityDefCustomAttrDef|EntityDef +---@class EntityDefCustomAttrDef +---@field name string +---@field mod_name string +---@field animations EntityAnimationDef +---@field rotation_factor number|fun(): number + +---Entity animation definition +---@class EntityAnimationDef +---@field idle {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean} +---@field on_hit_node {frame_range?: {["x"]: number, ["y"]: number}, frame_speed?: number, frame_blend?: number, frame_loop?: boolean} + +---Arrow object and custom attributes +---@class EnityCustomAttrDef +---@field object ObjectRef +---@field _velocity Vector +---@field _old_pos Vector +---@field _attached boolean +---@field _attached_to {["type"]: string, ["pos"]: Vector | nil} +---@field _has_particles boolean +---@field _lifetimer number +---@field _nodechecktimer number +---@field _is_drowning boolean +---@field _in_liquid boolean +---@field _shot_from_pos Vector +---@field _arrow_name string +---@field _bow_name string +---@field _user_name string +---@field _user ObjectRef +---@field _tflp number +---@field _tool_capabilities ToolCapabilitiesDef +---@field _is_critical_hit boolean +---@field _faster_arrows_multiplier number +---@field _add_damage number +---@field _caused_damage number +---@field _caused_knockback number +---@field _arrow_particle_effect string +---@field _arrow_particle_effect_crit string +---@field _arrow_particle_effect_fast string +---@field _sound_hit string +---@field _player_look_dir Vector +---@field _acc_x number +---@field _acc_y number +---@field _acc_z number +---@field _strength number +---@field name string +---@field _rotation_factor number | fun(): number +---@field _step_count number +---@field _x_enchanting table<string, {["value"]: number | nil}> + +---Staticdata attributes +---@class EnityStaticDataAttrDef +---@field _arrow_name string +---@field _bow_name string +---@field _user_name string +---@field _is_critical_hit boolean +---@field _tool_capabilities ToolCapabilitiesDef +---@field _tflp number +---@field _add_damage number +---@field _faster_arrows_multiplier number | nil +---@field _x_enchanting table<string, {["value"]: number | nil}> ``` </details>
Author
Member

suggestion: throw out our changes and redo what we need from scratch

suggestion: throw out our changes and redo what we need from scratch
Author
Member

summary of important yl changes:

  • PvP mod awareness
  • craft recipe changes (feathers instead of wool)
  • training arrows
  • double damage of all arrows
  • image compression

PLEASE review the diffs above and let me know if i'm missing something. i'll be using these changes to re-add features to x_bows, though probably via yl_* integration mods.

summary of important yl changes: * PvP mod awareness * craft recipe changes (feathers instead of wool) * training arrows * double damage of all arrows * image compression *PLEASE* review the diffs above and let me know if i'm missing something. i'll be using these changes to re-add features to x_bows, though probably via yl_* integration mods.
Author
Member

@AliasAlreadyTaken @Bla (and possibly others)

@AliasAlreadyTaken @Bla (and possibly others)
Author
Member

items which are missing if we just switch to the new x_bows w/out some sort of upgrade:

  • x_bows:arrow_fire
  • x_bows:arrow_training_* (13 colors)
  • x_bows:ball_rock
  • x_bows:bow_steel
  • x_bows:slingshot_steel
  • x_bows:slingshot_wood
items which are missing if we just switch to the new x_bows w/out some sort of upgrade: * `x_bows:arrow_fire` * `x_bows:arrow_training_*` (13 colors) * `x_bows:ball_rock` * `x_bows:bow_steel` * `x_bows:slingshot_steel` * `x_bows:slingshot_wood`
Author
Member

new x_bows also mucks about w/ the player model, and doesn't work w/ werewolves (though no glitchwolf).

probably there's no way to restore the slingshot and fire arrow, the required APIs are no longer present. the steel bow and training arrows can be recovered though.

new x_bows also mucks about w/ the player model, and doesn't work w/ werewolves (though no glitchwolf). probably there's no way to restore the slingshot and fire arrow, the required APIs are no longer present. the steel bow and training arrows can be recovered though.
AliasAlreadyTaken modified the milestone from 1.1.117.1 to 1.1.118 2023-02-02 10:36:55 +00:00

Let's postpone again to 1.1.119, eventually we'll need to tackle it. One more delay and it will become a meme :D

Let's postpone again to 1.1.119, eventually we'll need to tackle it. One more delay and it will become a meme :D
AliasAlreadyTaken modified the milestone from 1.1.118 to 1.1.119 2023-04-13 22:34:49 +00:00
AliasAlreadyTaken removed this from the 1.1.119 milestone 2023-05-07 19:24:51 +00:00
AliasAlreadyTaken added the
1. kind/meme
label 2023-12-14 09:47:27 +00:00
Author
Member

upstream x_bows is getting progressively more complicated and buggier. i've been playing around with it recently.

once i get the projectiles API (#102) into a "good enough" state, i'll start working on porting our bows and arrows to a "y_bows" mod, which won't use any of the code from the original, just the assets. most of the code will be in the projectiles API.

i'm not sure if we should close this issue now, or if it should turn into a "replace x_bows" issue.

upstream x_bows is getting progressively more complicated and buggier. i've been playing around with it recently. once i get the projectiles API (#102) into a "good enough" state, i'll start working on porting our bows and arrows to a "y_bows" mod, which won't use any of the code from the original, just the assets. most of the code will be in the projectiles API. i'm not sure if we should close this issue now, or if it should turn into a "replace x_bows" issue.

Let's close this and create a "replace x_bows" issue.

If this turns into a general missiles API, which we could also use for cannons and other weapons, all the better.

Also, we don't need to keep compat, if this is going to entirely replace x_bows, we can simply alias items in integration

Let's close this and create a "replace x_bows" issue. If this turns into a general missiles API, which we could also use for cannons and other weapons, all the better. Also, we don't need to keep compat, if this is going to entirely replace x_bows, we can simply alias items in integration
flux added the
5. result/wontfix
label 2023-12-27 18:03:15 +00:00
flux closed this issue 2023-12-27 18:03:18 +00:00
Author
Member

new issue: #5846

new issue: #5846
Sign in to join this conversation.
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: your-land/bugtracker#3684
No description provided.