2019-12-14 17:47:31 +01:00
throwing = { }
throwing.arrows = { }
throwing.target_object = 1
throwing.target_node = 2
throwing.target_both = 3
throwing.modname = minetest.get_current_modname ( )
2021-05-16 18:37:45 +02:00
local S = minetest.get_translator ( " throwing " )
local use_toolranks = minetest.get_modpath ( " toolranks " ) and minetest.settings : get_bool ( " throwing.toolranks " , true )
2019-12-14 17:47:31 +01:00
--------- Arrows functions ---------
function throwing . is_arrow ( itemstack )
return throwing.arrows [ ItemStack ( itemstack ) : get_name ( ) ]
end
function throwing . spawn_arrow_entity ( pos , arrow , player )
if throwing.is_arrow ( arrow ) then
return minetest.add_entity ( pos , arrow .. " _entity " )
elseif minetest.registered_items [ arrow ] . throwing_entity then
if type ( minetest.registered_items [ arrow ] . throwing_entity ) == " string " then
return minetest.add_entity ( pos , minetest.registered_items [ arrow ] . throwing_entity )
else -- Type is a function
return minetest.registered_items [ arrow ] . throwing_entity ( pos , player )
end
else
2021-05-16 18:37:45 +02:00
return minetest.add_entity ( pos , " __builtin:item " , arrow )
2019-12-14 17:47:31 +01:00
end
end
2021-05-16 18:37:45 +02:00
local function apply_realistic_acceleration ( obj , mass )
if not minetest.settings : get_bool ( " throwing.realistic_trajectory " , false ) then
return
end
local vertical_acceleration = tonumber ( minetest.settings : get ( " throwing.vertical_acceleration " ) ) or - 10
local friction_coef = tonumber ( minetest.settings : get ( " throwing.frictional_coefficient " ) ) or - 3
local velocity = obj : get_velocity ( )
obj : set_acceleration ( {
x = friction_coef * velocity.x / mass ,
y = friction_coef * velocity.y / mass + vertical_acceleration ,
z = friction_coef * velocity.z / mass
} )
end
local function shoot_arrow ( def , toolranks_data , player , bow_index , throw_itself , new_stack )
2019-12-14 17:47:31 +01:00
local inventory = player : get_inventory ( )
2021-05-16 18:37:45 +02:00
local arrow_index
if throw_itself then
arrow_index = bow_index
else
if bow_index >= player : get_inventory ( ) : get_size ( " main " ) then
2019-12-14 17:47:31 +01:00
return false
end
2021-05-16 18:37:45 +02:00
arrow_index = bow_index + 1
2019-12-14 17:47:31 +01:00
end
2021-05-16 18:37:45 +02:00
local arrow_stack = inventory : get_stack ( " main " , arrow_index )
2019-12-14 17:47:31 +01:00
local arrow = arrow_stack : get_name ( )
2021-05-16 18:37:45 +02:00
local playerpos = player : get_pos ( )
2019-12-14 17:47:31 +01:00
local pos = { x = playerpos.x , y = playerpos.y + 1.5 , z = playerpos.z }
2021-05-16 18:37:45 +02:00
local obj = ( def.spawn_arrow_entity or throwing.spawn_arrow_entity ) ( pos , arrow , player )
2019-12-14 17:47:31 +01:00
local luaentity = obj : get_luaentity ( )
2021-05-16 18:37:45 +02:00
-- Set custom data in the entity
2019-12-14 17:47:31 +01:00
luaentity.player = player : get_player_name ( )
if not luaentity.item then
luaentity.item = arrow
end
2021-05-16 18:37:45 +02:00
luaentity.data = { }
luaentity.timer = 0
luaentity.toolranks = toolranks_data -- May be nil if toolranks is disabled
2019-12-14 17:47:31 +01:00
if luaentity.on_throw then
2021-05-16 18:37:45 +02:00
if luaentity : on_throw ( pos , player , arrow_stack , arrow_index , luaentity.data ) == false then
2019-12-14 17:47:31 +01:00
obj : remove ( )
return false
end
end
local dir = player : get_look_dir ( )
local vertical_acceleration = tonumber ( minetest.settings : get ( " throwing.vertical_acceleration " ) ) or - 10
2021-05-16 18:37:45 +02:00
local velocity_factor = tonumber ( minetest.settings : get ( " throwing.velocity_factor " ) ) or 19
local velocity_mode = minetest.settings : get ( " throwing.velocity_mode " ) or " strength "
2019-12-14 17:47:31 +01:00
2021-05-16 18:37:45 +02:00
local velocity
if velocity_mode == " simple " then
velocity = velocity_factor
elseif velocity_mode == " momentum " then
velocity = def.strength * velocity_factor / luaentity.mass
else
velocity = def.strength * velocity_factor
end
obj : set_velocity ( {
x = dir.x * velocity ,
y = dir.y * velocity ,
z = dir.z * velocity
} )
obj : set_acceleration ( { x = 0 , y = vertical_acceleration , z = 0 } )
obj : set_yaw ( player : get_look_horizontal ( ) - math.pi / 2 )
apply_realistic_acceleration ( obj , luaentity.mass )
2019-12-14 17:47:31 +01:00
if luaentity.on_throw_sound ~= " " then
minetest.sound_play ( luaentity.on_throw_sound or " throwing_sound " , { pos = playerpos , gain = 0.5 } )
end
if not minetest.settings : get_bool ( " creative_mode " ) then
2021-05-16 18:37:45 +02:00
inventory : set_stack ( " main " , arrow_index , new_stack )
2019-12-14 17:47:31 +01:00
end
return true
end
2021-05-16 18:37:45 +02:00
function throwing . arrow_step ( self , dtime )
if not self.timer or not self.player then
self.object : remove ( )
return
end
2019-12-14 17:47:31 +01:00
self.timer = self.timer + dtime
2021-05-16 18:37:45 +02:00
local pos = self.object : get_pos ( )
2019-12-14 17:47:31 +01:00
local node = minetest.get_node ( pos )
local logging = function ( message , level )
minetest.log ( level or " action " , " [throwing] Arrow " .. ( self.item or self.name ) .. " throwed by player " .. self.player .. " " .. tostring ( self.timer ) .. " s ago " .. message )
end
2021-05-16 18:37:45 +02:00
local hit = function ( pos1 , node1 , obj )
2019-12-14 17:47:31 +01:00
if obj then
if obj : is_player ( ) then
if obj : get_player_name ( ) == self.player then -- Avoid hitting the hitter
return false
end
end
end
local player = minetest.get_player_by_name ( self.player )
if not player then -- Possible if the player disconnected
return
end
local function hit_failed ( )
if not minetest.settings : get_bool ( " creative_mode " ) and self.item then
player : get_inventory ( ) : add_item ( " main " , self.item )
end
if self.on_hit_fails then
2021-05-16 18:37:45 +02:00
self : on_hit_fails ( pos1 , player , self.data )
2019-12-14 17:47:31 +01:00
end
end
if not self.last_pos then
logging ( " hitted a node during its first call to the step function " )
hit_failed ( )
return
end
2021-05-16 18:37:45 +02:00
if node1 and minetest.is_protected ( pos1 , self.player ) and not self.allow_protected then -- Forbid hitting nodes in protected areas
minetest.record_protection_violation ( pos1 , self.player )
2019-12-14 17:47:31 +01:00
logging ( " hitted a node into a protected area " )
return
end
if self.on_hit then
2021-05-16 18:37:45 +02:00
local ret , reason = self : on_hit ( pos1 , self.last_pos , node1 , obj , player , self.data )
2019-12-14 17:47:31 +01:00
if ret == false then
if reason then
logging ( " : on_hit function failed for reason: " .. reason )
else
logging ( " : on_hit function failed " )
end
hit_failed ( )
return
end
end
if self.on_hit_sound then
2021-05-16 18:37:45 +02:00
minetest.sound_play ( self.on_hit_sound , { pos = pos1 , gain = 0.8 } )
2019-12-14 17:47:31 +01:00
end
2021-05-16 18:37:45 +02:00
local identifier
if node1 then
identifier = " node " .. node1.name
2019-12-14 17:47:31 +01:00
elseif obj then
if obj : get_luaentity ( ) then
2021-05-16 18:37:45 +02:00
identifier = " luaentity " .. obj : get_luaentity ( ) . name
2019-12-14 17:47:31 +01:00
elseif obj : is_player ( ) then
2021-05-16 18:37:45 +02:00
identifier = " player " .. obj : get_player_name ( )
2019-12-14 17:47:31 +01:00
else
2021-05-16 18:37:45 +02:00
identifier = " unknown object "
end
end
if identifier then
logging ( " collided with " .. identifier .. " at " .. minetest.pos_to_string ( pos1 ) .. " ) " )
end
-- Toolranks support: update bow uses
if self.toolranks then
local inventory = player : get_inventory ( )
-- Check that the player did not move the bow
local current_stack = inventory : get_stack ( " main " , self.toolranks . index )
if current_stack : get_name ( ) == self.toolranks . name then
local new_itemstack = toolranks.new_afteruse ( current_stack , player , nil , { wear = self.toolranks . wear } )
inventory : set_stack ( " main " , self.toolranks . index , new_itemstack )
2019-12-14 17:47:31 +01:00
end
end
end
-- Collision with a node
if node.name == " ignore " then
self.object : remove ( )
logging ( " reached ignore. Removing. " )
return
elseif ( minetest.registered_items [ node.name ] or { } ) . drawtype ~= " airlike " then
if self.target ~= throwing.target_object then -- throwing.target_both, nil, throwing.target_node, or any invalid value
if hit ( pos , node , nil ) ~= false then
self.object : remove ( )
end
else
self.object : remove ( )
end
return
end
-- Collision with an object
local objs = minetest.get_objects_inside_radius ( pos , 1 )
for k , obj in pairs ( objs ) do
if obj : get_luaentity ( ) then
if obj : get_luaentity ( ) . name ~= self.name and obj : get_luaentity ( ) . name ~= " __builtin:item " then
if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value
if hit ( pos , nil , obj ) ~= false then
self.object : remove ( )
end
else
self.object : remove ( )
end
end
else
if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value
if hit ( pos , nil , obj ) ~= false then
self.object : remove ( )
end
else
self.object : remove ( )
end
end
end
-- Support for shining items using wielded light
if minetest.global_exists ( " wielded_light " ) and self.object then
wielded_light.update_light_by_item ( self.item , self.object : get_pos ( ) )
end
2021-05-16 18:37:45 +02:00
apply_realistic_acceleration ( self.object , self.mass ) -- Physics: air friction
2019-12-14 17:47:31 +01:00
self.last_pos = pos -- Used by the build arrow
end
2021-05-16 18:37:45 +02:00
-- Backwards compatibility
2019-12-14 17:47:31 +01:00
function throwing . make_arrow_def ( def )
2021-05-16 18:37:45 +02:00
def.on_step = throwing.arrow_step
2019-12-14 17:47:31 +01:00
return def
end
--[[
on_hit ( pos , last_pos , node , object , hitter )
Either node or object is nil , depending whether the arrow collided with an object ( luaentity or player ) or with a node .
No log message is needed in this function ( a generic log message is automatically emitted ) , except on error or warning .
Should return false or false , reason on failure .
on_throw ( pos , hitter )
Unlike on_hit , it is optional .
] ]
function throwing . register_arrow ( name , def )
throwing.arrows [ name ] = true
local registration_name = name
if name : sub ( 1 , 9 ) == " throwing: " then
registration_name = " : " .. name
end
if not def.groups then
def.groups = { }
end
if not def.groups . dig_immediate then
def.groups . dig_immediate = 3
end
2021-05-16 18:37:45 +02:00
2019-12-14 17:47:31 +01:00
def.inventory_image = def.tiles [ 1 ]
def.on_place = function ( itemstack , placer , pointed_thing )
if minetest.settings : get_bool ( " throwing.allow_arrow_placing " ) and pointed_thing.above then
local playername = placer : get_player_name ( )
if not minetest.is_protected ( pointed_thing.above , playername ) then
2021-05-16 18:37:45 +02:00
minetest.log ( " action " , " Player " .. playername .. " placed arrow " .. name .. " at " .. minetest.pos_to_string ( pointed_thing.above ) )
2019-12-14 17:47:31 +01:00
minetest.set_node ( pointed_thing.above , { name = name } )
itemstack : take_item ( )
return itemstack
else
2021-05-16 18:37:45 +02:00
minetest.log ( " warning " , " Player " .. playername .. " tried to place arrow " .. name .. " into a protected area at " .. minetest.pos_to_string ( pointed_thing.above ) )
2019-12-14 17:47:31 +01:00
minetest.record_protection_violation ( pointed_thing.above , playername )
return itemstack
end
else
return itemstack
end
end
def.drawtype = " nodebox "
def.paramtype = " light "
def.node_box = {
type = " fixed " ,
fixed = {
-- Shaft
{ - 6.5 / 17 , - 1.5 / 17 , - 1.5 / 17 , 6.5 / 17 , 1.5 / 17 , 1.5 / 17 } ,
-- Spitze
{ - 4.5 / 17 , 2.5 / 17 , 2.5 / 17 , - 3.5 / 17 , - 2.5 / 17 , - 2.5 / 17 } ,
{ - 8.5 / 17 , 0.5 / 17 , 0.5 / 17 , - 6.5 / 17 , - 0.5 / 17 , - 0.5 / 17 } ,
-- Federn
{ 6.5 / 17 , 1.5 / 17 , 1.5 / 17 , 7.5 / 17 , 2.5 / 17 , 2.5 / 17 } ,
{ 7.5 / 17 , - 2.5 / 17 , 2.5 / 17 , 6.5 / 17 , - 1.5 / 17 , 1.5 / 17 } ,
{ 7.5 / 17 , 2.5 / 17 , - 2.5 / 17 , 6.5 / 17 , 1.5 / 17 , - 1.5 / 17 } ,
{ 6.5 / 17 , - 1.5 / 17 , - 1.5 / 17 , 7.5 / 17 , - 2.5 / 17 , - 2.5 / 17 } ,
{ 7.5 / 17 , 2.5 / 17 , 2.5 / 17 , 8.5 / 17 , 3.5 / 17 , 3.5 / 17 } ,
{ 8.5 / 17 , - 3.5 / 17 , 3.5 / 17 , 7.5 / 17 , - 2.5 / 17 , 2.5 / 17 } ,
{ 8.5 / 17 , 3.5 / 17 , - 3.5 / 17 , 7.5 / 17 , 2.5 / 17 , - 2.5 / 17 } ,
{ 7.5 / 17 , - 2.5 / 17 , - 2.5 / 17 , 8.5 / 17 , - 3.5 / 17 , - 3.5 / 17 } ,
}
}
minetest.register_node ( registration_name , def )
2021-05-16 18:37:45 +02:00
minetest.register_entity ( registration_name .. " _entity " , {
2019-12-14 17:47:31 +01:00
physical = false ,
visual = " wielditem " ,
visual_size = { x = 0.125 , y = 0.125 } ,
textures = { name } ,
collisionbox = { 0 , 0 , 0 , 0 , 0 , 0 } ,
on_hit = def.on_hit ,
on_hit_sound = def.on_hit_sound ,
on_throw_sound = def.on_throw_sound ,
on_throw = def.on_throw ,
allow_protected = def.allow_protected ,
target = def.target ,
on_hit_fails = def.on_hit_fails ,
2021-05-16 18:37:45 +02:00
on_step = throwing.arrow_step ,
2019-12-14 17:47:31 +01:00
item = name ,
2021-05-16 18:37:45 +02:00
mass = def.mass or 1 ,
2019-12-14 17:47:31 +01:00
} )
end
---------- Bows -----------
2021-05-16 18:37:45 +02:00
if use_toolranks and minetest.get_modpath ( " toolranks_extras " ) and toolranks_extras.register_tool_type then
toolranks_extras.register_tool_type ( " bow " , S ( " bow " ) , S ( " Arrows thrown " ) )
end
2019-12-14 17:47:31 +01:00
function throwing . register_bow ( name , def )
2021-05-16 18:37:45 +02:00
local enable_toolranks = use_toolranks and not def.no_toolranks
def.name = name
2019-12-14 17:47:31 +01:00
if not def.allow_shot then
def.allow_shot = function ( player , itemstack , index )
if index >= player : get_inventory ( ) : get_size ( " main " ) and not def.throw_itself then
return false
end
2021-05-16 18:37:45 +02:00
return throwing.is_arrow ( itemstack ) or def.throw_itself
2019-12-14 17:47:31 +01:00
end
end
2021-05-16 18:37:45 +02:00
2019-12-14 17:47:31 +01:00
if not def.inventory_image then
def.inventory_image = def.texture
end
2021-05-16 18:37:45 +02:00
if not def.strength then
def.strength = 20
end
2019-12-14 17:47:31 +01:00
def.on_use = function ( itemstack , user , pointed_thing )
-- Cooldown
local meta = itemstack : get_meta ( )
local cooldown = def.cooldown or tonumber ( minetest.settings : get ( " throwing.bow_cooldown " ) ) or 0.2
if cooldown > 0 and meta : get_int ( " cooldown " ) > os.time ( )
or meta : get_int ( " delay " ) > os.time ( ) then
return
end
local bow_index = user : get_wield_index ( )
local arrow_index = ( def.throw_itself and bow_index ) or bow_index + 1
local res , new_stack = def.allow_shot ( user , user : get_inventory ( ) : get_stack ( " main " , arrow_index ) , arrow_index , false )
if not res then
return ( def.throw_itself and new_stack ) or itemstack
end
-- Sound
if def.sound then
minetest.sound_play ( def.sound , { to_player = user : get_player_name ( ) } )
end
meta : set_int ( " delay " , os.time ( ) + ( def.delay or 0 ) )
minetest.after ( def.delay or 0 , function ( )
-- Re-check that the arrow can be thrown. Overwrite the new_stack
local old_new_stack = new_stack
2021-05-16 18:37:45 +02:00
local arrow_stack = user : get_inventory ( ) : get_stack ( " main " , arrow_index )
res , new_stack = def.allow_shot ( user , arrow_stack , arrow_index , true )
if not res then
return
end
2019-12-14 17:47:31 +01:00
if not new_stack then
new_stack = old_new_stack
end
2021-05-16 18:37:45 +02:00
if not new_stack then
arrow_stack : take_item ( )
new_stack = arrow_stack
2019-12-14 17:47:31 +01:00
end
-- Shoot arrow
2021-05-16 18:37:45 +02:00
local uses = 65535 / ( def.uses or 50 )
local toolranks_data
if enable_toolranks then
toolranks_data = {
name = itemstack : get_name ( ) ,
index = bow_index ,
wear = uses
}
end
if shoot_arrow ( def , toolranks_data , user , bow_index , def.throw_itself , new_stack ) then
2019-12-14 17:47:31 +01:00
if not minetest.settings : get_bool ( " creative_mode " ) then
2021-05-16 18:37:45 +02:00
itemstack : add_wear ( uses )
2019-12-14 17:47:31 +01:00
end
end
if def.throw_itself then
-- This is a bug. If we return ItemStack(nil), the player punches the entity,
2021-05-16 18:37:45 +02:00
-- and if the entity is a __builtin:item, it gets back to his inventory.
2019-12-14 17:47:31 +01:00
minetest.after ( 0.1 , function ( )
user : get_inventory ( ) : remove_item ( " main " , itemstack )
end )
elseif cooldown > 0 then
meta : set_int ( " cooldown " , os.time ( ) + cooldown )
end
user : get_inventory ( ) : set_stack ( " main " , bow_index , itemstack )
end )
return itemstack
end
2021-05-16 18:37:45 +02:00
if enable_toolranks then
def.original_description = def.original_description or def.description
def.description = toolranks.create_description ( def.description )
2019-12-14 17:47:31 +01:00
end
2021-05-16 18:37:45 +02:00
minetest.register_tool ( name , def )
2019-12-14 17:47:31 +01:00
end