2018-08-20 10:07:04 +02:00
throwing = { }
throwing.arrows = { }
throwing.target_object = 1
throwing.target_node = 2
throwing.target_both = 3
throwing.modname = minetest.get_current_modname ( )
2020-02-12 16:00:46 +01:00
local use_toolranks = minetest.get_modpath ( " toolranks " )
2020-02-12 17:41:47 +01:00
2018-08-20 10:07:04 +02: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
obj = minetest.add_entity ( pos , " __builtin:item " , arrow )
end
end
local function shoot_arrow ( itemstack , player , index , throw_itself , new_stack )
local inventory = player : get_inventory ( )
if not throw_itself then
if index >= player : get_inventory ( ) : get_size ( " main " ) then
return false
end
index = index + 1
end
local arrow_stack = inventory : get_stack ( " main " , index )
local arrow = arrow_stack : get_name ( )
2019-10-27 21:46:35 +01:00
local playerpos = player : get_pos ( )
2018-08-20 10:07:04 +02:00
local pos = { x = playerpos.x , y = playerpos.y + 1.5 , z = playerpos.z }
local obj = ( minetest.registered_items [ itemstack : get_name ( ) ] . spawn_arrow_entity or throwing.spawn_arrow_entity ) ( pos , arrow , player )
local luaentity = obj : get_luaentity ( )
2020-02-16 18:20:37 +01:00
-- Set custom data in the entity
2018-08-20 10:07:04 +02:00
luaentity.player = player : get_player_name ( )
if not luaentity.item then
luaentity.item = arrow
end
2020-02-16 18:20:37 +01:00
luaentity.data = { }
luaentity.timer = 0
2018-08-20 10:07:04 +02:00
if luaentity.on_throw then
if luaentity : on_throw ( pos , player , arrow_stack , index , luaentity.data ) == false then
obj : remove ( )
return false
end
end
local dir = player : get_look_dir ( )
local velocity_factor = tonumber ( minetest.settings : get ( " throwing.velocity_factor " ) ) or 19
local horizontal_acceleration_factor = tonumber ( minetest.settings : get ( " throwing.horizontal_acceleration_factor " ) ) or - 3
local vertical_acceleration = tonumber ( minetest.settings : get ( " throwing.vertical_acceleration " ) ) or - 10
2019-10-27 21:46:35 +01:00
obj : set_velocity ( { x = dir.x * velocity_factor , y = dir.y * velocity_factor , z = dir.z * velocity_factor } )
obj : set_acceleration ( { x = dir.x * horizontal_acceleration_factor , y = vertical_acceleration , z = dir.z * horizontal_acceleration_factor } )
obj : set_yaw ( player : get_look_horizontal ( ) - math.pi / 2 )
2018-08-20 10:07:04 +02: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
if new_stack then
inventory : set_stack ( " main " , index , new_stack )
else
local stack = inventory : get_stack ( " main " , index )
stack : take_item ( )
inventory : set_stack ( " main " , index , stack )
end
end
return true
end
2020-02-18 14:45:30 +01:00
function throwing . arrow_step ( self , dtime )
if not self.timer or not self.player then
self.object : remove ( )
return
end
2018-08-20 10:07:04 +02:00
self.timer = self.timer + dtime
2019-10-27 21:46:35 +01:00
local pos = self.object : get_pos ( )
2018-08-20 10:07:04 +02: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
local hit = function ( pos , node , obj )
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
self : on_hit_fails ( pos , player , self.data )
end
end
if not self.last_pos then
logging ( " hitted a node during its first call to the step function " )
hit_failed ( )
return
end
if node and minetest.is_protected ( pos , self.player ) and not self.allow_protected then -- Forbid hitting nodes in protected areas
minetest.record_protection_violation ( pos , self.player )
logging ( " hitted a node into a protected area " )
return
end
if self.on_hit then
local ret , reason = self : on_hit ( pos , self.last_pos , node , obj , player , self.data )
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
minetest.sound_play ( self.on_hit_sound , { pos = pos , gain = 0.8 } )
end
if node then
logging ( " collided with node " .. node.name .. " at ( " .. pos.x .. " , " .. pos.y .. " , " .. pos.z .. " ) " )
elseif obj then
if obj : get_luaentity ( ) then
logging ( " collided with luaentity " .. obj : get_luaentity ( ) . name .. " at ( " .. pos.x .. " , " .. pos.y .. " , " .. pos.z .. " ) " )
elseif obj : is_player ( ) then
logging ( " collided with player " .. obj : get_player_name ( ) .. " at ( " .. pos.x .. " , " .. pos.y .. " , " .. pos.z .. " ) " )
else
logging ( " collided with object at ( " .. pos.x .. " , " .. pos.y .. " , " .. pos.z .. " ) " )
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
self.last_pos = pos -- Used by the build arrow
end
2020-02-18 14:45:30 +01:00
-- Backwards compatibility
function throwing . make_arrow_def ( def )
def.on_step = throwing.arrow_step
return def
end
2018-08-20 10:07:04 +02:00
--[[
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
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
minetest.log ( " action " , " Player " .. playername .. " placed arrow " .. name .. " at ( " .. pointed_thing.above . x .. " , " .. pointed_thing.above . y .. " , " .. pointed_thing.above . z .. " ) " )
minetest.set_node ( pointed_thing.above , { name = name } )
itemstack : take_item ( )
return itemstack
else
minetest.log ( " warning " , " Player " .. playername .. " tried to place arrow " .. name .. " into a protected area at ( " .. pointed_thing.above . x .. " , " .. pointed_thing.above . y .. " , " .. pointed_thing.above . z .. " ) " )
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 )
2020-02-16 18:20:37 +01:00
minetest.register_entity ( registration_name .. " _entity " , {
2018-08-20 10:07:04 +02: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 ,
2020-02-18 14:45:30 +01:00
on_step = throwing.arrow_step ,
2018-08-20 10:07:04 +02:00
item = name ,
} )
end
---------- Bows -----------
2020-02-12 17:50:38 +01:00
function throwing . register_bow ( name , def , no_toolranks )
2018-08-20 10:07:04 +02: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
2020-02-16 18:38:49 +01:00
return throwing.is_arrow ( itemstack ) or def.throw_itself
2018-08-20 10:07:04 +02:00
end
end
if not def.inventory_image then
def.inventory_image = def.texture
end
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
res , new_stack = def.allow_shot ( user , user : get_inventory ( ) : get_stack ( " main " , arrow_index ) , arrow_index , true )
if not new_stack then
new_stack = old_new_stack
end
if not res then
return
end
-- Shoot arrow
if shoot_arrow ( itemstack , user , bow_index , def.throw_itself , new_stack ) then
if not minetest.settings : get_bool ( " creative_mode " ) then
2020-02-12 17:41:47 +01:00
local uses = 65535 / ( def.uses or 50 )
itemstack : add_wear ( uses )
2020-02-12 17:50:38 +01:00
if use_toolranks and not no_toolranks then
2020-02-12 17:41:47 +01:00
toolranks.new_afteruse ( itemstack , user , nil , { wear = uses } )
2020-02-12 16:35:33 +01:00
end
2018-08-20 10:07:04 +02:00
end
end
if def.throw_itself then
-- This is a bug. If we return ItemStack(nil), the player punches the entity,
-- and if the entity if a __builtin:item, it gets back to his inventory.
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
minetest.register_tool ( name , def )
2020-02-12 17:50:38 +01:00
if use_toolranks and not no_toolranks then
2020-02-12 17:41:47 +01:00
if name : sub ( 1 , 1 ) == " : " then
local sub_name = name : sub ( 2 )
end
minetest.override_item ( sub_name or name , {
2020-02-12 16:00:46 +01:00
original_description = def.description ,
description = toolranks.create_description ( def.description , 0 , 1 ) ,
} )
end
2018-08-20 10:07:04 +02:00
end