2021-05-28 00:34:58 +02:00
local S = minetest.get_translator ( minetest.get_current_modname ( ) )
2019-03-07 21:35:02 +01:00
2022-02-26 23:52:03 +01:00
local mod_target = minetest.get_modpath ( " mcl_target " )
2023-06-06 00:43:07 +02:00
local mod_campfire = minetest.get_modpath ( " mcl_campfires " )
2022-06-29 20:44:21 +02:00
local enable_pvp = minetest.settings : get_bool ( " enable_pvp " )
2022-02-26 23:52:03 +01:00
2021-05-23 01:06:34 +02:00
local math = math
local vector = vector
2018-05-08 00:38:00 +02:00
-- Time in seconds after which a stuck arrow is deleted
local ARROW_TIMEOUT = 60
2024-08-31 13:41:42 +02:00
2018-05-09 15:10:56 +02:00
-- Time after which stuck arrow is rechecked for being stuck
local STUCK_RECHECK_TIME = 5
local YAW_OFFSET = - math.pi / 2
2018-05-08 00:38:00 +02:00
2017-07-26 19:35:56 +02:00
local mod_awards = minetest.get_modpath ( " awards " ) and minetest.get_modpath ( " mcl_achievements " )
2018-05-09 15:10:56 +02:00
local mod_button = minetest.get_modpath ( " mesecons_button " )
2017-07-26 19:35:56 +02:00
2018-05-07 23:10:49 +02:00
minetest.register_craftitem ( " mcl_bows:arrow " , {
2019-03-07 21:35:02 +01:00
description = S ( " Arrow " ) ,
2020-02-19 04:54:17 +01:00
_tt_help = S ( " Ammunition " ) .. " \n " .. S ( " Damage from bow: 1-10 " ) .. " \n " .. S ( " Damage from dispenser: 3 " ) ,
2019-03-07 21:35:02 +01:00
_doc_items_longdesc = S ( " Arrows are ammunition for bows and dispensers. " ) .. " \n " ..
S ( " An arrow fired from a bow has a regular damage of 1-9. At full charge, there's a 20% chance of a critical hit dealing 10 damage instead. An arrow fired from a dispenser always deals 3 damage. " ) .. " \n " ..
S ( " Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons. " ) ,
_doc_items_usagehelp = S ( " To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it. " ) ,
2018-05-07 23:10:49 +02:00
inventory_image = " mcl_bows_arrow_inv.png " ,
2021-10-15 21:00:37 +02:00
groups = { ammo = 1 , ammo_bow = 1 , ammo_bow_regular = 1 , ammo_crossbow = 1 } ,
2018-02-01 22:45:19 +01:00
_on_dispense = function ( itemstack , dispenserpos , droppos , dropnode , dropdir )
-- Shoot arrow
local shootpos = vector.add ( dispenserpos , vector.multiply ( dropdir , 0.51 ) )
2018-05-09 15:10:56 +02:00
local yaw = math.atan2 ( dropdir.z , dropdir.x ) + YAW_OFFSET
2018-05-07 23:10:49 +02:00
mcl_bows.shoot_arrow ( itemstack : get_name ( ) , shootpos , dropdir , yaw , nil , 19 , 3 )
2018-02-01 22:45:19 +01:00
end ,
2015-06-29 19:55:56 +02:00
} )
2024-05-23 00:54:39 +02:00
-- Destroy arrow entity self at pos and drops it as an item
2024-08-31 13:41:42 +02:00
local arrow_entity = {
2024-05-23 00:54:39 +02:00
physical = true ,
pointable = false ,
visual = " mesh " ,
mesh = " mcl_bows_arrow.obj " ,
visual_size = { x =- 1 , y = 1 } ,
textures = { " mcl_bows_arrow.png " } ,
collisionbox = { - 0.19 , - 0.125 , - 0.19 , 0.19 , 0.125 , 0.19 } ,
collide_with_objects = false ,
2024-09-02 03:41:21 +02:00
liquid_drag = true ,
2024-05-23 00:54:39 +02:00
_fire_damage_resistant = true ,
2019-03-08 22:31:26 +01:00
2024-05-23 00:54:39 +02:00
_save_fields = {
2024-08-31 17:09:20 +02:00
" last_pos " , " startpos " , " damage " , " is_critical " , " stuck " , " stuckin " , " stuckin_player " , " time_in_air " ,
2024-05-23 00:54:39 +02:00
} ,
2020-08-19 14:45:37 +02:00
2024-05-23 00:54:39 +02:00
_startpos = nil ,
_damage = 1 , -- Damage on impact
_is_critical = false , -- Whether this arrow would deal critical damage
_stuck = false , -- Whether arrow is stuck
_stucktimer = nil , -- Amount of time (in seconds) the arrow has been stuck so far
_stuckrechecktimer = nil , -- An additional timer for periodically re-checking the stuck status of an arrow
_stuckin = nil , --Position of node in which arow is stuck.
_shooter = nil , -- ObjectRef of player or mob who shot it
_is_arrow = true ,
_in_player = false ,
_blocked = false ,
_viscosity = 0 , -- Viscosity of node the arrow is currently in
_deflection_cooloff = 0 , -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
2017-03-06 01:02:30 +01:00
2024-05-23 00:54:39 +02:00
_vl_projectile = {
survive_collision = true ,
2024-05-23 10:03:19 +02:00
sticks_in_players = true ,
2024-09-08 14:06:00 +02:00
damages_players = true ,
2024-05-23 10:03:19 +02:00
damage_groups = function ( self )
return { fleshy = self._damage }
end ,
2024-10-20 20:31:29 +02:00
hide_tracer = function ( self )
return self._stuck or self._damage < 9 or self._in_player
end ,
tracer_texture = " mobs_mc_arrow_particle.png " ,
2024-05-23 00:54:39 +02:00
behaviors = {
2024-10-20 22:42:00 +02:00
vl_projectile.sticks ,
2024-10-20 20:15:47 +02:00
vl_projectile.burns ,
2024-10-20 20:31:29 +02:00
vl_projectile.has_tracer ,
2024-10-20 20:15:47 +02:00
-- Custom arrow behaviors
function ( self , dtime )
local pos = self.object : get_pos ( )
self._allow_punch = self._allow_punch or not self._owner or not self._startpos or pos and vector.distance ( self._startpos , pos ) > 1.5
-- Give the arrows a maximum flight time
self._time_in_air = ( self._time_in_air or 0 ) + dtime
if self._time_in_air > ARROW_TIMEOUT then
self._removed = true
self.object : remove ( )
return true
end
if self._deflection_cooloff > 0 then
self._deflection_cooloff = self._deflection_cooloff - dtime
end
end ,
2024-05-23 00:54:39 +02:00
vl_projectile.collides_with_solids ,
2024-05-23 10:03:19 +02:00
vl_projectile.raycast_collides_with_entities ,
} ,
2024-09-02 03:51:08 +02:00
allow_punching = function ( self , entity_def , projectile_def , object )
2024-09-09 13:36:46 +02:00
if not self._allow_punch then return false end
2024-09-02 03:51:08 +02:00
local lua = object : get_luaentity ( )
2024-05-23 10:03:19 +02:00
if lua and lua.name == " mobs_mc:rover " then return false end
return true
end ,
sounds = {
2024-08-31 16:41:17 +02:00
on_entity_collision = function ( self , _ , _ , _ , obj )
2024-05-23 10:03:19 +02:00
if obj : is_player ( ) then
2024-09-09 13:36:46 +02:00
return { { name = " mcl_bows_hit_player " , gain = 0.1 } , { to_player = obj : get_player_name ( ) } , true }
2024-05-23 10:03:19 +02:00
end
return { { name = " mcl_bows_hit_other " , gain = 0.3 } , { pos = self.object : get_pos ( ) , max_hear_distance = 16 } , true }
end
2024-05-23 00:54:39 +02:00
} ,
on_collide_with_entity = function ( self , pos , obj )
local is_player = obj : is_player ( )
local lua = obj : get_luaentity ( )
2021-02-01 08:15:28 +01:00
2024-05-23 00:54:39 +02:00
-- Make sure collision is valid
2024-09-09 13:36:46 +02:00
if not ( is_player or ( lua and ( lua.is_mob or lua._hittable_by_projectile ) ) ) then
return
2024-05-23 00:54:39 +02:00
end
if obj : get_hp ( ) > 0 then
if lua then
local entity_name = lua.name
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
-- TODO: This achievement should be given for the kill, not just a hit
2024-09-09 13:36:46 +02:00
local shooter = self._vl_projectile . owner
if shooter and shooter : is_player ( ) and vector.distance ( pos , self._startpos ) >= 20 then
2024-05-23 00:54:39 +02:00
if mod_awards and ( entity_name == " mobs_mc:skeleton " or entity_name == " mobs_mc:stray " or entity_name == " mobs_mc:witherskeleton " ) then
2024-09-09 13:36:46 +02:00
awards.unlock ( shooter : get_player_name ( ) , " mcl:snipeSkeleton " )
2024-05-23 00:54:39 +02:00
end
2018-05-12 07:00:16 +02:00
end
2018-05-09 18:04:34 +02:00
end
2018-05-08 14:35:15 +02:00
end
2024-05-23 00:54:39 +02:00
2024-08-31 13:41:42 +02:00
-- Item definition entity collision hook
2024-08-31 00:40:01 +02:00
local item_def = minetest.registered_items [ self._arrow_item ]
local hook = item_def and item_def._on_collide_with_entity
if hook then hook ( self , pos , obj ) end
2024-09-28 03:09:37 +02:00
if ( self._piercing or 0 ) > 0 then
2024-09-28 01:49:25 +02:00
self._piercing = self._piercing - 1
return
end
2024-08-31 00:40:01 +02:00
-- Because arrows are flagged to survive collisions to allow sticking into blocks, manually remove it now that it
-- has collided with an entity
self._removed = true
self.object : remove ( )
2024-05-23 00:54:39 +02:00
end
} ,
2018-05-09 16:49:37 +02:00
2024-05-23 00:54:39 +02:00
-- Force recheck of stuck arrows when punched.
-- Otherwise, punching has no effect.
on_punch = function ( self )
if self._stuck then
self._stuckrechecktimer = STUCK_RECHECK_TIME
end
end ,
get_staticdata = function ( self )
local out = { }
local save_fields = self._save_fields
for i = 1 , # save_fields do
local field = save_fields [ i ]
out [ field ] = self [ " _ " .. field ]
2018-05-09 17:31:04 +02:00
end
2018-05-08 17:16:11 +02:00
2024-05-23 00:54:39 +02:00
if self._stuck then
-- If _stucktimer is missing for some reason, assume the maximum
if not self._stucktimer then
self._stucktimer = ARROW_TIMEOUT
2018-05-09 17:31:04 +02:00
end
2024-05-23 00:54:39 +02:00
out.stuckstarttime = minetest.get_gametime ( ) - self._stucktimer
end
2018-05-09 17:31:04 +02:00
2024-09-09 13:36:46 +02:00
if self._owner then
out._owner = self._owner : get_player_name ( )
2024-05-23 00:54:39 +02:00
end
2024-09-09 13:36:46 +02:00
2024-05-23 00:54:39 +02:00
return minetest.serialize ( out )
end ,
on_activate = function ( self , staticdata , dtime_s )
self.object : set_armor_groups ( { immortal = 1 } )
self._time_in_air = 1.0
local data = minetest.deserialize ( staticdata )
if not data then return end
-- Restore arrow state
local save_fields = self._save_fields
for i = 1 , # save_fields do
local field = save_fields [ i ]
self [ " _ " .. field ] = data [ field ]
end
2018-05-09 17:31:04 +02:00
2024-05-23 00:54:39 +02:00
if data.stuckstarttime then
-- First, check if the stuck arrow is aleady past its life timer.
-- If yes, delete it.
self._stucktimer = minetest.get_gametime ( ) - data.stuckstarttime
2018-05-09 15:10:56 +02:00
end
2018-05-09 17:31:04 +02:00
2024-05-23 00:54:39 +02:00
-- Perform a stuck recheck on the next step.
self._stuckrechecktimer = STUCK_RECHECK_TIME
2024-09-09 13:36:46 +02:00
local vl_projectile_data = { }
if data._owner then
vl_projectile_data.owner = minetest.get_player_by_name ( data._owner )
end
2024-09-22 02:21:25 +02:00
self._vl_projectile = vl_projectile_data
2024-09-09 13:36:46 +02:00
2018-05-08 17:16:11 +02:00
if data.shootername then
local shooter = minetest.get_player_by_name ( data.shootername )
if shooter and shooter : is_player ( ) then
self._shooter = shooter
end
end
2022-01-02 12:06:29 +01:00
if data.stuckin_player then
2024-08-31 00:40:01 +02:00
self._removed = true
2022-01-02 12:06:29 +01:00
self.object : remove ( )
end
2024-05-23 00:54:39 +02:00
end ,
2024-08-31 13:41:42 +02:00
}
-- Make the arrow entity available to other mods as a template
mcl_bows.arrow_entity = table.copy ( arrow_entity )
vl_projectile.register ( " mcl_bows:arrow_entity " , arrow_entity )
2018-05-08 17:16:11 +02:00
2022-01-02 12:06:29 +01:00
minetest.register_on_respawnplayer ( function ( player )
for _ , obj in pairs ( player : get_children ( ) ) do
local ent = obj : get_luaentity ( )
if ent and ent.name and string.find ( ent.name , " mcl_bows:arrow_entity " ) then
obj : remove ( )
end
end
end )
2017-07-26 19:35:56 +02:00
if minetest.get_modpath ( " mcl_core " ) and minetest.get_modpath ( " mcl_mobitems " ) then
minetest.register_craft ( {
2021-05-29 16:12:33 +02:00
output = " mcl_bows:arrow 4 " ,
2017-07-26 19:35:56 +02:00
recipe = {
2021-05-29 16:12:33 +02:00
{ " mcl_core:flint " } ,
{ " mcl_core:stick " } ,
{ " mcl_mobitems:feather " }
2017-07-26 19:35:56 +02:00
}
} )
end
2018-05-09 17:04:13 +02:00
2021-05-29 16:12:33 +02:00
if minetest.get_modpath ( " doc_identifier " ) then
2018-05-09 17:04:13 +02:00
doc.sub . identifier.register_object ( " mcl_bows:arrow_entity " , " craftitems " , " mcl_bows:arrow " )
end