2022-10-28 10:51:49 -05:00
local S = minetest.get_translator ( minetest.get_current_modname ( ) )
2022-10-25 20:19:31 -05:00
sfinv = sfinv --[[@as Sfinv]]
2022-10-21 14:24:25 -05:00
---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
2022-10-20 08:13:35 -05:00
---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 ' ) ,
2022-10-26 10:55:42 -05:00
i3 = minetest.get_modpath ( ' i3 ' ) ,
unified_inventory = minetest.get_modpath ( ' unified_inventory ' ) ,
2022-10-27 14:38:51 -05:00
u_skins = minetest.get_modpath ( ' u_skins ' ) ,
wardrobe = minetest.get_modpath ( ' wardrobe ' ) ,
_3d_armor = minetest.get_modpath ( ' 3d_armor ' ) ,
2022-11-01 08:18:22 -05:00
skinsdb = minetest.get_modpath ( ' skinsdb ' ) ,
2022-11-01 22:10:47 -05:00
player_api = minetest.get_modpath ( ' player_api ' ) ,
2022-10-20 08:13:35 -05:00
registered_bows = { } ,
registered_arrows = { } ,
registered_quivers = { } ,
registered_particle_spawners = { } ,
2022-10-24 09:52:00 -05:00
registered_entities = { } ,
2022-10-20 08:13:35 -05:00
player_bow_sneak = { } ,
settings = {
2022-10-30 17:55:54 -05:00
x_bows_attach_arrows_to_entities = minetest.settings : get_bool ( ' x_bows_attach_arrows_to_entities ' , false ) ,
2022-10-31 19:49:08 -05:00
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 )
2022-10-20 08:13:35 -05:00
} ,
2022-10-27 14:59:47 -05:00
charge_sound_after_job = { } ,
fallback_quiver = not minetest.global_exists ( ' sfinv ' ) and not minetest.global_exists ( ' unified_inventory ' ) and not minetest.global_exists ( ' i3 ' )
2022-10-20 08:13:35 -05:00
}
XBows.__index = XBows
---@type XBowsQuiver
XBowsQuiver = {
hud_item_ids = { } ,
2022-10-28 08:55:18 -05:00
after_job = { } ,
quiver_empty_state = { }
2022-10-20 08:13:35 -05:00
}
XBowsQuiver.__index = XBowsQuiver
setmetatable ( XBowsQuiver , XBows )
---@type XBowsEntityDef
local XBowsEntityDef = { }
XBowsEntityDef.__index = XBowsEntityDef
setmetatable ( XBowsEntityDef , XBows )
2022-10-26 10:55:42 -05:00
---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
2022-10-21 14:24:25 -05:00
---Check if creative is enabled or if player has creative priv
---@param self XBows
---@param name string
---@return boolean
2022-10-20 08:13:35 -05:00
function XBows . is_creative ( self , name )
return self.creative or minetest.check_player_privs ( name , { creative = true } )
end
2022-10-21 14:24:25 -05:00
---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
2022-10-21 12:37:35 -05:00
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
2022-10-20 08:13:35 -05:00
---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
2022-10-21 14:24:25 -05:00
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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
2022-10-21 14:24:25 -05:00
---@param self XBows
2022-10-20 08:13:35 -05:00
---@param name string
---@param def ItemDef | BowItemDefCustom
2022-10-21 12:37:35 -05:00
---@param override? boolean MOD everride
2022-10-20 08:13:35 -05:00
---@return boolean|nil
2022-10-21 12:37:35 -05:00
function XBows . register_bow ( self , name , def , override )
2022-10-20 08:13:35 -05:00
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 '
2022-10-21 12:37:35 -05:00
def.short_description = def.short_description
def.description = override and def.short_description or ( def.description or name )
2022-10-20 08:13:35 -05:00
def.custom . uses = def.custom . uses or 150
def.groups = mergeTables ( { bow = 1 , flammable = 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 '
2022-10-21 12:37:35 -05:00
def.custom . gravity = def.custom . gravity or - 10
2022-10-20 08:13:35 -05:00
if def.custom . crit_chance then
2022-10-28 10:51:49 -05:00
def.description = def.description .. ' \n ' .. minetest.colorize ( ' #00FF00 ' , S ( ' Critical Arrow Chance ' ) .. ' : '
2022-10-20 08:13:35 -05:00
.. ( 1 / def.custom . crit_chance ) * 100 .. ' % ' )
end
2022-10-28 10:51:49 -05:00
def.description = def.description .. ' \n ' .. minetest.colorize ( ' #00BFFF ' , S ( ' Strength ' ) .. ' : '
2022-10-20 08:13:35 -05:00
.. 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
2022-10-28 10:51:49 -05:00
def.description = def.description .. ' \n ' .. S ( ' Allowed ammunition ' ) .. ' : \n ' .. allowed_amm_desc
2022-10-20 08:13:35 -05:00
else
2022-10-28 10:51:49 -05:00
def.description = def.description .. ' \n ' .. S ( ' Allowed ammunition ' ) .. ' : ' .. S ( ' none ' )
2022-10-20 08:13:35 -05:00
end
end
self.registered_bows [ def.custom . name ] = def
self.registered_bows [ def.custom . name_charged ] = def
---not charged bow
2022-10-21 12:37:35 -05:00
minetest.register_tool ( override and ' : ' .. def.custom . name or def.custom . name , {
2022-10-20 08:13:35 -05:00
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 ,
2022-10-24 15:31:23 -05:00
wield_scale = { x = 2 , y = 2 , z = 1.5 } ,
2022-10-20 08:13:35 -05:00
---@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
2022-10-21 12:37:35 -05:00
minetest.register_tool ( override and ' : ' .. def.custom . name_charged or def.custom . name_charged , {
2022-10-20 08:13:35 -05:00
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 ,
2022-10-24 15:31:23 -05:00
wield_scale = { x = 2 , y = 2 , z = 1.5 } ,
2022-10-30 09:27:51 -05:00
range = 0 ,
2022-10-20 08:13:35 -05:00
---@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
2022-10-21 14:24:25 -05:00
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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
2022-10-25 20:19:31 -05:00
def.short_description = def.short_description or name
2022-10-20 08:13:35 -05:00
def.custom . tool_capabilities = def.custom . tool_capabilities or {
full_punch_interval = 1 ,
max_drop_level = 0 ,
damage_groups = { fleshy = 2 }
}
2022-10-28 10:51:49 -05:00
def.custom . description_abilities = minetest.colorize ( ' #00FF00 ' , S ( ' Damage ' ) .. ' : '
.. def.custom . tool_capabilities.damage_groups . fleshy ) .. ' \n ' .. minetest.colorize ( ' #00BFFF ' , S ( ' Charge Time ' ) .. ' : '
2022-10-25 20:19:31 -05:00
.. def.custom . tool_capabilities.full_punch_interval .. ' s ' )
2022-10-20 08:13:35 -05:00
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
2022-10-21 12:37:35 -05:00
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
2022-10-20 08:13:35 -05:00
self.registered_arrows [ def.custom . name ] = def
minetest.register_craftitem ( def.custom . name , {
2022-10-25 20:19:31 -05:00
description = def.description .. ' \n ' .. def.custom . description_abilities ,
short_description = def.short_description ,
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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
2022-10-28 10:51:49 -05:00
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 .. ' % ' )
2022-10-20 08:13:35 -05:00
end
if def.custom . add_damage then
2022-10-28 10:51:49 -05:00
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 )
2022-10-20 08:13:35 -05:00
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 )
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
2022-10-21 14:24:25 -05:00
---Load bow
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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
2022-10-27 14:59:47 -05:00
---we got arrow from quiver
2022-10-20 08:13:35 -05:00
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
2022-10-27 18:30:35 -05:00
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
2022-10-20 08:13:35 -05:00
---find itemstack arrow in players inventory
2022-10-26 22:44:26 -05:00
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 ( ) )
2022-10-20 08:13:35 -05:00
2022-10-26 22:44:26 -05:00
if self.registered_arrows [ arrow_stack : get_name ( ) ] and is_allowed_ammunition then
table.insert ( itemstack_arrows , { stack = arrow_stack , idx = 1 } )
2022-10-20 08:13:35 -05:00
end
2022-10-27 14:59:47 -05:00
---if everything else fails
if self.fallback_quiver then
local inv_list = inv : get_list ( ' main ' )
2022-10-26 22:44:26 -05:00
2022-10-27 14:59:47 -05:00
for i , st in ipairs ( inv_list ) do
local st_name = st : get_name ( )
2022-10-26 22:44:26 -05:00
2022-10-27 14:59:47 -05:00
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
2022-10-26 22:44:26 -05:00
2022-10-20 08:13:35 -05:00
-- 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 ( )
2022-10-26 22:44:26 -05:00
v_inv : set_stack ( ' x_bows:arrow_inv ' , v_itemstack_arrows [ 1 ] . idx , v_itemstack_arrow )
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Shoot bow
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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 ( )
)
2022-10-30 09:27:51 -05:00
---Handle HUD and 3d Quiver
2022-10-20 08:13:35 -05:00
if is_arrow_from_quiver == 1 then
XBowsQuiver : udate_or_create_hud ( user , detached_inv : get_list ( ' main ' ) , found_arrow_stack_idx )
2022-10-28 08:55:18 -05:00
if detached_inv : is_empty ( ' main ' ) then
XBowsQuiver : show_3d_quiver ( user , { is_empty = true } )
else
XBowsQuiver : show_3d_quiver ( user )
end
2022-10-20 08:13:35 -05:00
else
2022-10-27 18:30:35 -05:00
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
2022-10-30 09:27:51 -05:00
---no ammo (fake stack just for the HUD)
2022-10-27 18:30:35 -05:00
XBowsQuiver : udate_or_create_hud ( user , { ItemStack ( {
name = ' x_bows:no_ammo '
} ) } )
end
2022-10-20 08:13:35 -05:00
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
2022-10-30 09:27:51 -05:00
---@type EnityStaticDataAttrDef
2022-10-20 08:13:35 -05:00
local staticdata = {
_arrow_name = arrow_name ,
_bow_name = bow_name ,
2022-10-30 09:27:51 -05:00
_user_name = user : get_player_name ( ) ,
_is_critical_hit = false ,
2022-10-20 08:13:35 -05:00
_tool_capabilities = _tool_capabilities ,
_tflp = tflp ,
_add_damage = 0
}
---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
2022-10-30 09:27:51 -05:00
staticdata._is_critical_hit = true
2022-10-20 08:13:35 -05:00
end
end
---speed multiply
if quiver_xbows_def and quiver_xbows_def.custom . faster_arrows and quiver_xbows_def.custom . faster_arrows > 1 then
2022-10-30 09:27:51 -05:00
staticdata._faster_arrows_multiplier = quiver_xbows_def.custom . faster_arrows
2022-10-20 08:13:35 -05:00
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
2022-10-30 09:27:51 -05:00
if staticdata._is_critical_hit then
2022-10-20 08:13:35 -05:00
sound_name = x_bows_registered_bow_charged_def.custom . sound_shoot_crit
end
2022-10-30 09:27:51 -05:00
---stop punching close objects/nodes when shooting
minetest.after ( 0.2 , function ( )
if user : get_wielded_item ( ) : get_name ( ) == itemstack : get_name ( ) then
user : set_wielded_item ( ItemStack ( { name = bow_name , wear = itemstack : get_wear ( ) } ) )
end
end )
2022-10-20 08:13:35 -05:00
2022-10-30 09:27:51 -05:00
local player_pos = user : get_pos ( )
2022-10-20 08:13:35 -05:00
local obj = minetest.add_entity (
{
2022-10-30 09:27:51 -05:00
x = player_pos.x ,
y = player_pos.y + 1.5 ,
z = player_pos.z
2022-10-20 08:13:35 -05:00
} ,
projectile_entity ,
minetest.serialize ( staticdata )
)
if not obj then
return itemstack
end
if not self : 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
2022-10-21 14:24:25 -05:00
---Add new particle to XBow registration
---@param self XBows
---@param name string
---@param def ParticlespawnerDef|ParticlespawnerDefCustom
---@return nil
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Get particle effect from registered spawners table
---@param self XBows
---@param name string
---@param pos Vector
---@return number|boolean
2022-10-20 08:13:35 -05:00
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. ' )
2022-10-21 14:24:25 -05:00
return false
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Check if ammunition is allowed to charge this weapon
---@param self XBows
---@param weapon_name string
---@param ammo_name string
---@return boolean
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
return table_contains ( x_bows_weapon_def.custom . allowed_ammunition , ammo_name )
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Function receive a "luaentity" table as `self`. Called when the object is instantiated.
---@param self EntityDef|EntityDefCustom|XBows
2022-10-30 09:27:51 -05:00
---@param selfObj EnityCustomAttrDef
2022-10-21 14:24:25 -05:00
---@param staticdata string
---@param dtime_s? integer|number
---@return nil
function XBowsEntityDef . on_activate ( self , selfObj , staticdata , dtime_s )
2022-10-20 08:13:35 -05:00
if not selfObj or not staticdata or staticdata == ' ' then
selfObj.object : remove ( )
return
end
2022-10-30 09:27:51 -05:00
local _staticdata = minetest.deserialize ( staticdata ) --[[@as EnityStaticDataAttrDef]]
2022-10-20 08:13:35 -05:00
-- 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
2022-10-30 09:27:51 -05:00
selfObj._user_name = _staticdata._user_name
selfObj._user = minetest.get_player_by_name ( _staticdata._user_name )
2022-10-20 08:13:35 -05:00
selfObj._tflp = _staticdata._tflp
selfObj._tool_capabilities = _staticdata._tool_capabilities
2022-10-30 09:27:51 -05:00
selfObj._is_critical_hit = _staticdata._is_critical_hit
selfObj._faster_arrows_multiplier = _staticdata._faster_arrows_multiplier
2022-10-20 08:13:35 -05:00
selfObj._add_damage = _staticdata._add_damage
2022-10-24 09:52:00 -05:00
selfObj._caused_damage = 0
selfObj._caused_knockback = 0
2022-10-20 08:13:35 -05:00
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
2022-10-24 09:52:00 -05:00
2022-10-30 09:27:51 -05:00
---Bow Def
2022-10-24 09:52:00 -05:00
local x_bows_registered_bow_def = self.registered_bows [ selfObj._bow_name ]
2022-10-20 08:13:35 -05:00
selfObj._sound_hit = x_bows_registered_bow_def.custom . sound_hit
2022-10-30 09:27:51 -05:00
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
---acceleration
selfObj._player_look_dir = selfObj._user : get_look_dir ( )
2022-10-24 09:52:00 -05:00
2022-10-30 09:27:51 -05:00
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
2022-10-24 09:52:00 -05:00
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
2022-10-20 08:13:35 -05:00
2022-10-30 09:27:51 -05:00
---add infotext
2022-10-20 08:13:35 -05:00
selfObj.object : set_properties ( {
infotext = selfObj._arrow_name ,
} )
2022-10-21 12:37:35 -05:00
2022-10-30 09:27:51 -05:00
---idle animation
2022-10-24 09:52:00 -05:00
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 ) )
end
2022-10-30 09:27:51 -05:00
---counter, e.g. for initial values set `on_step`
selfObj._step_count = 0
2022-10-24 09:52:00 -05:00
---Callbacks
2022-10-21 12:37:35 -05:00
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
2022-10-20 08:13:35 -05:00
end
2022-10-21 14:24:25 -05:00
---Function receive a "luaentity" table as `self`. Called when the object dies.
---@param self XBows
2022-10-30 09:27:51 -05:00
---@param selfObj EnityCustomAttrDef
2022-10-21 14:24:25 -05:00
---@param killer ObjectRef|nil
---@return nil
2022-10-20 08:13:35 -05:00
function XBowsEntityDef . on_death ( self , selfObj , killer )
if not selfObj._old_pos then
selfObj.object : remove ( )
return
end
minetest.item_drop ( ItemStack ( selfObj._arrow_name ) , nil , vector.round ( selfObj._old_pos ) )
end
2022-10-21 14:24:25 -05:00
--- 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
2022-10-30 09:27:51 -05:00
---@param selfObj EnityCustomAttrDef
2022-10-21 14:24:25 -05:00
---@param dtime number
---@return nil
2022-10-20 08:13:35 -05:00
function XBowsEntityDef . on_step ( self , selfObj , dtime )
2022-10-30 09:27:51 -05:00
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
2022-10-20 08:13:35 -05:00
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 ,
2022-10-24 09:52:00 -05:00
z = v_rotation.z + ( selfObj._rotation_factor or math.pi / 2 )
2022-10-20 08:13:35 -05:00
} )
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 (
2022-10-30 09:27:51 -05:00
( pointed_thing.ref : is_player ( ) and pointed_thing.ref : get_player_name ( ) ~= selfObj._user : get_player_name ( ) )
2022-10-20 08:13:35 -05:00
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
2022-10-21 12:37:35 -05:00
and not selfObj._attached
2022-10-20 08:13:35 -05:00
then
if pointed_thing.ref : is_player ( ) then
minetest.sound_play ( ' x_bows_arrow_successful_hit ' , {
2022-10-30 09:27:51 -05:00
to_player = selfObj._user : get_player_name ( ) ,
2022-10-20 08:13:35 -05:00
gain = 0.3
} )
else
minetest.sound_play ( selfObj._sound_hit , {
2022-10-30 09:27:51 -05:00
to_player = selfObj._user : get_player_name ( ) ,
2022-10-20 08:13:35 -05:00
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
_damage = _damage + selfObj._add_damage
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
)
pointed_thing.ref : add_velocity ( {
x = dir.x * knockback * - 1 ,
y = 7 ,
z = dir.z * knockback * - 1
} )
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
}
)
2022-10-21 12:37:35 -05:00
selfObj._caused_damage = _damage
selfObj._caused_knockback = knockback
2022-10-30 17:55:54 -05:00
XBows : show_damage_numbers ( selfObj.object : get_pos ( ) , _damage , selfObj._is_critical_hit )
2022-10-20 08:13:35 -05:00
-- 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
2022-10-30 09:27:51 -05:00
---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 } )
2022-10-20 08:13:35 -05:00
-- attach arrow
2022-10-30 09:27:51 -05:00
local position = vector.subtract (
ip_pos ,
pointed_thing.ref : get_pos ( )
2022-10-20 08:13:35 -05:00
)
2022-10-30 09:27:51 -05:00
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 )
2022-10-20 08:13:35 -05:00
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 = { }
2022-10-21 12:37:35 -05:00
local projectile_entity = self.registered_arrows [ selfObj._arrow_name ] . custom.projectile_entity
2022-10-20 08:13:35 -05:00
for _ , object in ipairs ( pointed_thing.ref : get_children ( ) ) do
2022-10-21 12:37:35 -05:00
if object : get_luaentity ( ) and object : get_luaentity ( ) . name == projectile_entity then
2022-10-20 08:13:35 -05:00
table.insert ( children , object )
end
end
if # children >= 5 then
children [ 1 ] : remove ( )
end
2022-10-21 12:37:35 -05:00
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
2022-10-20 08:13:35 -05:00
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 = { }
2022-10-21 12:37:35 -05:00
local projectile_entity = self.registered_arrows [ selfObj._arrow_name ] . custom.projectile_entity
2022-10-20 08:13:35 -05:00
for _ , object in ipairs ( minetest.get_objects_inside_radius ( pointed_thing.under , 1 ) ) do
2022-10-21 12:37:35 -05:00
if not object : is_player ( ) and object : get_luaentity ( ) and object : get_luaentity ( ) . name == projectile_entity then
2022-10-20 08:13:35 -05:00
table.insert ( children , object )
end
end
if # children >= 5 then
children [ # children ] : remove ( )
end
2022-10-21 21:46:24 -05:00
---Wiggle
2022-10-24 09:52:00 -05:00
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 ) )
2022-10-21 21:46:24 -05:00
end
---API callbacks
2022-10-20 08:13:35 -05:00
local on_hit_node_callback = self.registered_arrows [ selfObj._arrow_name ] . custom.on_hit_node
if on_hit_node_callback then
2022-10-21 12:37:35 -05:00
on_hit_node_callback ( selfObj , pointed_thing )
2022-10-20 08:13:35 -05:00
end
2022-10-30 09:27:51 -05:00
local new_pos = selfObj.object : get_pos ( )
2022-10-30 09:54:11 -05:00
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
2022-10-30 09:27:51 -05:00
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---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
2022-10-30 09:27:51 -05:00
---@param selfObj EnityCustomAttrDef
2022-10-21 14:24:25 -05:00
---@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
2022-10-20 08:13:35 -05:00
function XBowsEntityDef . on_punch ( self , selfObj , puncher , time_from_last_punch , tool_capabilities , dir , damage )
2022-10-30 18:01:14 -05:00
local pos = selfObj.object : get_pos ( )
2022-10-20 08:13:35 -05:00
2022-10-30 18:01:14 -05:00
if pos then
minetest.sound_play ( ' default_dig_choppy ' , {
pos = pos ,
gain = 0.4
2022-10-26 22:59:36 -05:00
} )
end
2022-10-20 08:13:35 -05:00
return false
end
2022-10-21 14:24:25 -05:00
---Register new projectile entity
---@param self XBows
---@param name string
---@param def XBowsEntityDef
2022-10-20 08:13:35 -05:00
function XBows . register_entity ( self , name , def )
2022-10-24 09:52:00 -05:00
def._custom = def._custom or { }
def._custom . animations = def._custom . animations or { }
2022-10-20 08:13:35 -05:00
local mod_name = def._custom . mod_name or ' x_bows '
def._custom . name = mod_name .. ' : ' .. name
2022-10-24 09:52:00 -05:00
def.initial_properties = mergeTables ( {
---defaults
2022-10-20 08:13:35 -05:00
visual = ' wielditem ' ,
collisionbox = { 0 , 0 , 0 , 0 , 0 , 0 } ,
selectionbox = { 0 , 0 , 0 , 0 , 0 , 0 } ,
physical = false ,
textures = { ' air ' } ,
2022-10-24 09:52:00 -05:00
hp_max = 1 ,
2022-10-24 15:31:23 -05:00
visual_size = { x = 1 , y = 1 , z = 1 } ,
glow = 1
2022-10-24 09:52:00 -05:00
} , def.initial_properties or { } )
2022-10-20 08:13:35 -05:00
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
2022-10-24 09:52:00 -05:00
self.registered_entities [ def._custom . name ] = def
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@param player ObjectRef
---@param quiver_id? string If `nil` then all open quivers will be closed
2022-10-21 14:24:25 -05:00
---@return nil
2022-10-20 08:13:35 -05:00
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.
2022-10-21 14:24:25 -05:00
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@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
---@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
2022-10-26 22:44:26 -05:00
---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
if not self : is_creative ( player_name ) then
detached_inv : set_list ( ' main ' , detached_inv_list )
self : save ( detached_inv , player , true )
2022-10-20 08:13:35 -05:00
end
end
end
end
2022-10-26 22:44:26 -05:00
end
2022-10-20 08:13:35 -05:00
2022-10-26 22:44:26 -05:00
if found_arrow_stack then
---show HUD - quiver inventory
self : udate_or_create_hud ( player , prev_detached_inv_list , found_arrow_stack_idx )
2022-10-20 08:13:35 -05:00
end
end
2022-10-27 14:59:47 -05:00
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
2022-10-26 22:44:26 -05:00
2022-10-27 14:59:47 -05:00
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
2022-10-26 22:44:26 -05:00
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@param player ObjectRef
2022-10-21 14:24:25 -05:00
---@return nil
2022-10-20 08:13:35 -05:00
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?
2022-10-21 14:24:25 -05:00
---Update or create quiver HUD
---@param self XBowsQuiver
---@param player ObjectRef
---@param inv_list ItemStack[]
---@param idx? number
---@return nil
2022-10-20 08:13:35 -05:00
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
2022-10-27 18:30:35 -05:00
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 ' ,
2022-10-28 10:51:49 -05:00
short_description = S ( ' No Ammo ' ) .. ' ! '
2022-10-27 18:30:35 -05:00
}
end
2022-10-20 08:13:35 -05:00
2022-10-27 18:30:35 -05:00
if not item_def then
return
end
---cancel previous timeouts and reset
2022-10-20 08:13:35 -05:00
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 } ,
2022-10-27 18:30:35 -05:00
text = item_def.inventory_image ,
2022-10-20 08:13:35 -05:00
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 } ,
2022-10-27 18:30:35 -05:00
text = item_def.short_description ,
2022-10-20 08:13:35 -05:00
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 } ,
2022-10-27 18:30:35 -05:00
text = is_arrow and ' x_bows_single_hotbar.png ' or ' x_bows_quiver_hotbar.png ' ,
2022-10-20 08:13:35 -05:00
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 ( ) ]
2022-10-27 18:30:35 -05:00
if is_no_ammo then
found_arrow_stack_def = item_def
end
2022-10-20 08:13:35 -05:00
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 } ,
2022-10-27 18:30:35 -05:00
text = ' x_bows_hotbar_selected.png ' ,
2022-10-20 08:13:35 -05:00
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 } ,
2022-10-27 18:30:35 -05:00
text = is_no_ammo and 0 or qst : get_count ( ) ,
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Get existing detached inventory or create new one
---@param self XBowsQuiver
---@param quiver_id string
---@param player_name string
---@param quiver_items? string
2022-10-24 09:52:00 -05:00
---@return InvRef
2022-10-20 08:13:35 -05:00
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 )
2022-10-28 08:55:18 -05:00
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
2022-10-20 08:13:35 -05:00
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 )
2022-10-28 08:55:18 -05:00
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
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Create formspec
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@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
2022-10-28 09:00:49 -05:00
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
2022-10-20 08:13:35 -05:00
--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
2022-10-28 09:00:49 -05:00
formspec [ # formspec + 1 ] = ' image[ ' .. px .. ' , ' .. py .. ' ;1,1;x_bows_arrow_slot.png] '
2022-10-20 08:13:35 -05:00
end
px = px + 1
end
2022-10-28 09:00:49 -05:00
formspec = table.concat ( formspec , ' ' )
2022-10-20 08:13:35 -05:00
return formspec
end
2022-10-21 14:24:25 -05:00
---Convert inventory of itemstacks to serialized string
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@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 ) ,
2022-10-28 10:51:49 -05:00
content_description = content_description == ' ' and ' \n ' .. S ( ' Empty ' ) or content_description
2022-10-20 08:13:35 -05:00
}
end
2022-10-21 14:24:25 -05:00
---Set items from serialized string to inventory
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@param inv InvRef inventory to add items to
---@param str string previously stringified inventory of itemstacks
2022-10-21 14:24:25 -05:00
---@return nil
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Save quiver inventory to itemstack meta
---@param self XBowsQuiver
---@param inv InvRef
---@param player ObjectRef
---@param quiver_is_closed? boolean
---@return nil
2022-10-20 08:13:35 -05:00
function XBowsQuiver . save ( self , inv , player , quiver_is_closed )
2022-10-25 20:19:31 -05:00
local player_inv = player : get_inventory ( ) --[[@as InvRef]]
2022-10-20 08:13:35 -05:00
local inv_loc = inv : get_location ( )
local quiver_item_name = quiver_is_closed and ' x_bows:quiver ' or ' x_bows:quiver_open '
2022-10-25 20:19:31 -05:00
local player_quiver_inv_stack = player_inv : get_stack ( ' x_bows:quiver_inv ' , 1 )
2022-10-20 08:13:35 -05:00
2022-10-25 20:19:31 -05:00
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
2022-10-20 08:13:35 -05:00
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
2022-10-21 14:24:25 -05:00
---Check if we are allowing actions in the correct quiver inventory
---@param self XBowsQuiver
2022-10-20 08:13:35 -05:00
---@param inv InvRef
---@param player ObjectRef
---@return boolean
function XBowsQuiver . quiver_can_allow ( self , inv , player )
2022-10-25 20:19:31 -05:00
local player_inv = player : get_inventory ( ) --[[@as InvRef]]
2022-10-20 08:13:35 -05:00
local inv_loc = inv : get_location ( )
2022-10-25 20:19:31 -05:00
local player_quiver_inv_stack = player_inv : get_stack ( ' x_bows:quiver_inv ' , 1 )
2022-10-20 08:13:35 -05:00
2022-10-25 20:19:31 -05:00
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
2022-10-20 08:13:35 -05:00
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
2022-10-22 22:20:02 -05:00
---@param self XBows
2022-10-20 08:13:35 -05:00
---@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
2022-10-26 10:55:42 -05:00
quiver_id = itemstack : get_name ( ) .. ' _ ' .. self.uuid ( )
2022-10-20 08:13:35 -05:00
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
2022-10-25 20:19:31 -05:00
---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
2022-10-28 10:51:49 -05:00
' label[0,0; ' .. minetest.formspec_escape ( S ( ' Arrows ' ) ) .. ' :] ' ,
2022-10-25 20:19:31 -05:00
' 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
2022-10-28 10:51:49 -05:00
' label[3.5,0; ' .. minetest.formspec_escape ( S ( ' Quiver ' ) ) .. ' :] ' ,
2022-10-26 11:19:36 -05:00
' 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] ' ,
2022-10-25 20:19:31 -05:00
' listring[current_player;x_bows:quiver_inv] ' ,
' listring[current_player;main] ' ,
}
2022-10-26 10:55:42 -05:00
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 )
2022-10-25 20:19:31 -05:00
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
2022-10-26 08:45:59 -05:00
formspec [ # formspec + 1 ] = ' label[0,1.5; ' .. minetest.formspec_escape ( context._itemstack_arrow : get_short_description ( ) ) .. ' \n ' .. minetest.formspec_escape ( x_bows_registered_arrow_def.custom . description_abilities ) .. ' ] '
2022-10-25 20:19:31 -05:00
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
2022-10-26 11:19:36 -05:00
formspec [ # formspec + 1 ] = ' label[3.5,1.5; ' .. minetest.formspec_escape ( context._itemstack_quiver : get_short_description ( ) ) .. ' ] '
formspec [ # formspec + 1 ] = ' list[detached: ' .. quiver_id .. ' ;main;4.5,0.5;3,1;] '
2022-10-25 20:19:31 -05:00
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
2022-10-26 08:45:59 -05:00
---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
2022-10-28 10:51:49 -05:00
' label[0.5,1; ' .. minetest.formspec_escape ( S ( ' Arrows ' ) ) .. ' :] ' ,
2022-10-26 11:19:36 -05:00
' list[current_player;x_bows:arrow_inv;0.5,1.5;1,1;] ' ,
2022-10-26 08:45:59 -05:00
' listring[current_player;x_bows:arrow_inv] ' ,
' listring[current_player;main] ' ,
---quiver
2022-10-28 10:51:49 -05:00
' label[5,1; ' .. minetest.formspec_escape ( S ( ' Quiver ' ) ) .. ' :] ' ,
2022-10-26 11:19:36 -05:00
' list[current_player;x_bows:quiver_inv;5,1.5;1,1;] ' ,
2022-10-26 08:45:59 -05:00
' 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
2022-10-26 11:19:36 -05:00
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 ) .. ' ] '
2022-10-26 08:45:59 -05:00
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
2022-10-26 11:19:36 -05:00
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;] '
2022-10-26 08:45:59 -05:00
formspec [ # formspec + 1 ] = ' listring[detached: ' .. quiver_id .. ' ;main] '
formspec [ # formspec + 1 ] = ' listring[current_player;main] '
end
2022-10-26 11:19:36 -05:00
formspec = table.concat ( formspec , ' ' )
fs ( formspec )
2022-10-26 08:45:59 -05:00
end
2022-10-26 10:55:42 -05:00
} )
2022-10-26 08:45:59 -05:00
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
2022-10-28 10:51:49 -05:00
' label[0.5,0.5; ' .. minetest.formspec_escape ( S ( ' Arrows ' ) ) .. ' :] ' ,
2022-10-26 08:45:59 -05:00
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
2022-10-28 10:51:49 -05:00
' label[5,0.5; ' .. minetest.formspec_escape ( S ( ' Quiver ' ) ) .. ' :] ' ,
2022-10-26 11:19:36 -05:00
unified_inventory.single_slot ( 4.9 , 0.9 ) ,
' list[current_player;x_bows:quiver_inv;5,1;1,1;] ' ,
2022-10-26 08:45:59 -05:00
' 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
2022-10-26 11:19:36 -05:00
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;] '
2022-10-26 08:45:59 -05:00
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
2022-10-27 14:38:51 -05:00
2022-10-28 08:55:18 -05:00
function XBowsQuiver . show_3d_quiver ( self , player , props )
2022-11-01 22:10:47 -05:00
if not XBows.settings . x_bows_show_3d_quiver or not XBows.player_api then
2022-10-31 19:49:08 -05:00
return
end
2022-10-28 08:55:18 -05:00
local _props = props or { }
2022-10-27 14:38:51 -05:00
local p_name = player : get_player_name ( )
2022-10-28 08:55:18 -05:00
local quiver_texture = ' x_bows_quiver_mesh.png '
2022-10-27 14:38:51 -05:00
local player_textures
2022-10-28 08:55:18 -05:00
if _props.is_empty then
quiver_texture = ' x_bows_quiver_empty_mesh.png '
end
2022-11-01 08:18:22 -05:00
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
2022-10-27 14:38:51 -05:00
minetest.after ( 0.1 , function ( )
player_textures = {
armor.textures [ p_name ] . skin ,
armor.textures [ p_name ] . armor ,
armor.textures [ p_name ] . wielditem ,
2022-10-28 08:55:18 -05:00
quiver_texture
2022-10-27 14:38:51 -05:00
}
if player_textures then
2022-10-28 08:55:18 -05:00
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
2022-10-27 14:38:51 -05:00
end
end )
return
elseif self.u_skins then
local u_skin_texture = u_skins.u_skins [ p_name ]
player_textures = {
u_skin_texture .. ' .png ' ,
2022-10-28 08:55:18 -05:00
quiver_texture
2022-10-27 14:38:51 -05:00
}
elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins [ p_name ] then
player_textures = {
wardrobe.playerSkins [ p_name ] ,
2022-10-28 08:55:18 -05:00
quiver_texture
2022-10-27 14:38:51 -05:00
}
else
local textures = player_api.get_textures ( player )
---cleanup
for index , value in ipairs ( textures ) do
2022-10-28 08:55:18 -05:00
if value == ' x_bows_quiver_blank_mesh.png ' or value == ' x_bows_quiver_mesh.png ' or value == ' x_bows_quiver_empty_mesh.png ' then
2022-10-27 14:38:51 -05:00
table.remove ( textures , index )
end
end
2022-10-28 08:55:18 -05:00
table.insert ( textures , quiver_texture )
2022-10-27 14:38:51 -05:00
player_textures = textures
end
if player_textures then
2022-10-28 08:55:18 -05:00
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
2022-10-27 14:38:51 -05:00
end
end
2022-10-28 08:55:18 -05:00
function XBowsQuiver . hide_3d_quiver ( self , player )
2022-11-01 22:10:47 -05:00
if not XBows.settings . x_bows_show_3d_quiver or not XBows.player_api then
2022-10-31 19:49:08 -05:00
return
end
2022-10-27 14:38:51 -05:00
local p_name = player : get_player_name ( )
local player_textures
2022-11-01 08:18:22 -05:00
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
2022-10-27 14:38:51 -05:00
minetest.after ( 0.1 , function ( )
player_textures = {
armor.textures [ p_name ] . skin ,
armor.textures [ p_name ] . armor ,
armor.textures [ p_name ] . wielditem ,
2022-10-28 08:55:18 -05:00
' x_bows_quiver_blank_mesh.png '
2022-10-27 14:38:51 -05:00
}
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 ' ,
2022-10-28 08:55:18 -05:00
' x_bows_quiver_blank_mesh.png '
2022-10-27 14:38:51 -05:00
}
elseif self.wardrobe and wardrobe.playerSkins and wardrobe.playerSkins [ p_name ] then
player_textures = {
wardrobe.playerSkins [ p_name ] ,
2022-10-28 08:55:18 -05:00
' x_bows_quiver_blank_mesh.png '
2022-10-27 14:38:51 -05:00
}
else
local textures = player_api.get_textures ( player )
---cleanup
for index , value in ipairs ( textures ) do
2022-10-28 08:55:18 -05:00
if value == ' x_bows_quiver_mesh.png ' or value == ' x_bows_quiver_blank_mesh.png ' or value == ' x_bows_quiver_empty_mesh.png ' then
2022-10-27 14:38:51 -05:00
table.remove ( textures , index )
end
end
2022-10-28 08:55:18 -05:00
table.insert ( textures , ' x_bows_quiver_blank_mesh.png ' )
2022-10-27 14:38:51 -05:00
player_textures = textures
end
if player_textures then
player_api.set_textures ( player , player_textures )
end
end
2022-10-30 17:55:54 -05:00
2022-10-30 18:39:07 -05:00
---string split to characters
---@param str string
---@return string[] | nil
2022-10-30 17:55:54 -05:00
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
2022-10-30 18:39:07 -05:00
texture = texture .. ' [combine: ' .. 7 * # results .. ' x ' .. 9 * # results .. ' :0,0=x_bows_dmg_ ' .. value .. ' .png '
2022-10-30 17:55:54 -05:00
else
2022-10-30 18:39:07 -05:00
texture = texture .. ' : ' .. dmg_nr_offset .. ' ,0=x_bows_dmg_ ' .. value .. ' .png '
2022-10-30 17:55:54 -05:00
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