2014-04-26 16:45:39 -07:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Player.h"
2014-04-27 17:03:06 -07:00
#include "ArrowEntity.h"
2014-04-26 16:45:39 -07:00
#include "../Chunk.h"
cArrowEntity :: cArrowEntity ( cEntity * a_Creator , double a_X , double a_Y , double a_Z , const Vector3d & a_Speed ) :
2014-04-27 17:03:06 -07:00
super ( pkArrow , a_Creator , a_X , a_Y , a_Z , 0.5 , 0.5 ),
m_PickupState ( psNoPickup ),
m_DamageCoeff ( 2 ),
m_IsCritical ( false ),
m_Timer ( 0 ),
m_HitGroundTimer ( 0 ),
2014-05-09 16:56:29 +01:00
m_HasTeleported ( false ),
2014-04-27 17:03:06 -07:00
m_bIsCollected ( false ),
m_HitBlockPos ( Vector3i ( 0 , 0 , 0 ))
2014-04-26 16:45:39 -07:00
{
SetSpeed ( a_Speed );
SetMass ( 0.1 );
2015-03-30 19:42:32 -04:00
SetGravity ( - 20.0f );
SetAirDrag ( 0.2f );
2014-04-26 16:45:39 -07:00
SetYawFromSpeed ();
SetPitchFromSpeed ();
LOGD ( "Created arrow %d with speed {%.02f, %.02f, %.02f} and rot {%.02f, %.02f}" ,
2014-06-16 22:57:13 +02:00
m_UniqueID , GetSpeedX (), GetSpeedY (), GetSpeedZ (),
GetYaw (), GetPitch ()
);
2014-04-26 16:45:39 -07:00
}
cArrowEntity :: cArrowEntity ( cPlayer & a_Player , double a_Force ) :
2014-04-27 17:03:06 -07:00
super ( pkArrow , & a_Player , a_Player . GetThrowStartPos (), a_Player . GetThrowSpeed ( a_Force * 1.5 * 20 ), 0.5 , 0.5 ),
m_PickupState ( psInSurvivalOrCreative ),
m_DamageCoeff ( 2 ),
m_IsCritical (( a_Force >= 1 )),
m_Timer ( 0 ),
m_HitGroundTimer ( 0 ),
m_HasTeleported ( false ),
m_bIsCollected ( false ),
m_HitBlockPos ( 0 , 0 , 0 )
2014-04-26 16:45:39 -07:00
{
2014-06-16 22:57:13 +02:00
if ( a_Player . IsGameModeCreative ())
{
m_PickupState = psInCreative ;
}
2015-03-30 19:42:32 -04:00
SetGravity ( - 20.0f );
2015-03-31 11:03:35 -04:00
SetAirDrag ( 0.01f );
2014-04-26 16:45:39 -07:00
}
bool cArrowEntity :: CanPickup ( const cPlayer & a_Player ) const
{
switch ( m_PickupState )
{
case psNoPickup : return false ;
case psInSurvivalOrCreative : return ( a_Player . IsGameModeSurvival () || a_Player . IsGameModeCreative ());
case psInCreative : return a_Player . IsGameModeCreative ();
}
ASSERT ( ! "Unhandled pickup state" );
2016-08-02 13:12:34 +02:00
#ifndef __clang__
return false ;
#endif
2014-04-26 16:45:39 -07:00
}
2014-08-19 17:57:32 +02:00
2014-04-26 16:45:39 -07:00
void cArrowEntity :: OnHitSolidBlock ( const Vector3d & a_HitPos , eBlockFace a_HitFace )
2014-07-17 22:50:58 +02:00
{
2014-06-22 20:44:01 +01:00
Vector3d Hit = a_HitPos ;
2015-03-20 22:32:32 +00:00
Hit += GetSpeed (). NormalizeCopy () / 100000 ; // Make arrow sink into block a bit so it lodges (TODO: investigate how to stop them going so far so that they become black clientside)
2014-06-22 20:44:01 +01:00
super :: OnHitSolidBlock ( Hit , a_HitFace );
2014-07-01 22:39:37 +01:00
Vector3i BlockHit = Hit . Floor ();
2014-06-30 19:21:21 +01:00
2014-07-01 22:39:37 +01:00
int X = BlockHit . x , Y = BlockHit . y , Z = BlockHit . z ;
2014-06-22 20:44:01 +01:00
m_HitBlockPos = Vector3i ( X , Y , Z );
2015-05-28 17:45:47 +03:00
2014-04-26 16:45:39 -07:00
// Broadcast arrow hit sound
2017-02-15 07:05:24 +02:00
m_World -> BroadcastSoundEffect ( "entity.arrow.hit" , static_cast < double > ( X ), static_cast < double > ( Y ), static_cast < double > ( Z ), 0.5f , static_cast < float > ( 0.75 + ( static_cast < float > (( GetUniqueID () * 23 ) % 32 )) / 64 ));
2014-08-19 12:38:15 +02:00
2014-08-21 12:08:38 +02:00
if (( m_World -> GetBlock ( Hit ) == E_BLOCK_TNT ) && IsOnFire ())
2014-08-19 12:38:15 +02:00
{
m_World -> SetBlock ( X , Y , Z , E_BLOCK_AIR , 0 );
m_World -> SpawnPrimedTNT ( X , Y , Z );
}
2014-04-26 16:45:39 -07:00
}
void cArrowEntity :: OnHitEntity ( cEntity & a_EntityHit , const Vector3d & a_HitPos )
2014-07-17 22:50:58 +02:00
{
2016-01-11 21:34:41 +02:00
super :: OnHitEntity ( a_EntityHit , a_HitPos );
2015-07-29 09:04:03 -06:00
int Damage = static_cast < int > ( GetSpeed (). Length () / 20 * m_DamageCoeff + 0.5 );
2014-04-26 16:45:39 -07:00
if ( m_IsCritical )
{
Damage += m_World -> GetTickRandomNumber ( Damage / 2 + 2 );
}
2014-08-19 12:38:15 +02:00
2015-05-19 19:32:10 +01:00
unsigned int PowerLevel = m_CreatorData . m_Enchantments . GetLevel ( cEnchantments :: enchPower );
2014-08-19 12:38:15 +02:00
if ( PowerLevel > 0 )
{
2015-07-29 09:04:03 -06:00
int ExtraDamage = static_cast < int > ( ceil ( 0.25 * ( PowerLevel + 1 )));
2014-09-01 14:29:13 +02:00
Damage += ExtraDamage ;
2014-08-19 12:38:15 +02:00
}
2014-08-19 16:08:17 +02:00
2015-05-28 17:45:47 +03:00
// int KnockbackAmount = 1;
2015-05-19 19:32:10 +01:00
unsigned int PunchLevel = m_CreatorData . m_Enchantments . GetLevel ( cEnchantments :: enchPunch );
2014-08-19 16:08:17 +02:00
if ( PunchLevel > 0 )
{
2014-08-21 12:08:38 +02:00
Vector3d LookVector = GetLookVector ();
2014-08-19 16:08:17 +02:00
Vector3f FinalSpeed = Vector3f ( 0 , 0 , 0 );
switch ( PunchLevel )
{
2014-08-21 12:08:38 +02:00
case 1 : FinalSpeed = LookVector * Vector3d ( 5 , 0.3 , 5 ); break ;
case 2 : FinalSpeed = LookVector * Vector3d ( 8 , 0.3 , 8 ); break ;
2014-08-19 16:08:17 +02:00
default : break ;
}
a_EntityHit . SetSpeed ( FinalSpeed );
}
2015-05-28 17:45:47 +03:00
// a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, KnockbackAmount); // TODO fix knockback.
2016-01-22 20:55:46 +02:00
a_EntityHit . TakeDamage ( dtRangedAttack , GetCreatorUniqueID (), Damage , 0 ); // Until knockback is fixed.
2015-05-28 17:45:47 +03:00
2014-08-21 12:08:38 +02:00
if ( IsOnFire () && ! a_EntityHit . IsSubmerged () && ! a_EntityHit . IsSwimming ())
2014-08-19 12:38:15 +02:00
{
a_EntityHit . StartBurning ( 100 );
}
2014-04-26 16:45:39 -07:00
// Broadcast successful hit sound
2017-02-15 07:05:24 +02:00
GetWorld () -> BroadcastSoundEffect ( "entity.arrow.hit_player" , GetPosX (), GetPosY (), GetPosZ (), 0.5 , static_cast < float > ( 0.75 + ( static_cast < float > (( GetUniqueID () * 23 ) % 32 )) / 64 ));
2016-01-11 21:34:41 +02:00
2014-04-26 16:45:39 -07:00
Destroy ();
}
2014-10-15 19:01:55 +02:00
void cArrowEntity :: CollectedBy ( cPlayer & a_Dest )
2014-04-26 16:45:39 -07:00
{
2014-10-15 19:01:55 +02:00
if ( m_IsInGround && ! m_bIsCollected && CanPickup ( a_Dest ))
2014-04-26 16:45:39 -07:00
{
2014-06-26 18:42:28 +02:00
// Do not add the arrow to the inventory when the player is in creative:
2014-10-15 19:01:55 +02:00
if ( ! a_Dest . IsGameModeCreative ())
2014-04-26 16:45:39 -07:00
{
2014-10-15 19:01:55 +02:00
int NumAdded = a_Dest . GetInventory (). AddItem ( E_ITEM_ARROW );
2014-06-16 22:57:13 +02:00
if ( NumAdded == 0 )
{
// No space in the inventory
return ;
}
2014-04-26 16:45:39 -07:00
}
2014-06-26 18:42:28 +02:00
2016-12-15 20:21:43 +01:00
GetWorld () -> BroadcastCollectEntity ( * this , a_Dest , 1 );
2017-02-15 07:05:24 +02:00
GetWorld () -> BroadcastSoundEffect ( "entity.item.pickup" , GetPosX (), GetPosY (), GetPosZ (), 0.5 , static_cast < float > ( 0.75 + ( static_cast < float > (( GetUniqueID () * 23 ) % 32 )) / 64 ));
2014-06-16 22:57:13 +02:00
m_bIsCollected = true ;
2014-04-26 16:45:39 -07:00
}
}
2015-01-11 21:12:26 +00:00
void cArrowEntity :: Tick ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2014-04-26 16:45:39 -07:00
{
super :: Tick ( a_Dt , a_Chunk );
2016-09-03 14:31:27 +03:00
if ( ! IsTicking ())
{
// The base class tick destroyed us
return ;
}
2015-01-16 13:13:23 +00:00
m_Timer += a_Dt ;
2016-01-11 21:34:41 +02:00
2014-04-26 16:45:39 -07:00
if ( m_bIsCollected )
{
2015-01-16 13:13:23 +00:00
if ( m_Timer > std :: chrono :: milliseconds ( 500 ))
2014-04-26 16:45:39 -07:00
{
Destroy ();
return ;
}
}
2015-01-16 13:13:23 +00:00
else if ( m_Timer > std :: chrono :: minutes ( 5 ))
2014-04-26 16:45:39 -07:00
{
Destroy ();
return ;
}
2016-01-11 21:34:41 +02:00
2014-04-26 16:45:39 -07:00
if ( m_IsInGround )
2015-03-21 14:07:16 +01:00
{
2014-07-17 22:15:34 +02:00
if ( ! m_HasTeleported ) // Sent a teleport already, don't do again
2014-04-26 16:45:39 -07:00
{
2015-01-16 13:13:23 +00:00
if ( m_HitGroundTimer > std :: chrono :: milliseconds ( 500 ))
2014-04-26 16:45:39 -07:00
{
m_World -> BroadcastTeleportEntity ( * this );
m_HasTeleported = true ;
}
else
{
2015-01-16 13:13:23 +00:00
m_HitGroundTimer += a_Dt ;
2014-04-26 16:45:39 -07:00
}
}
2016-01-11 21:34:41 +02:00
2014-04-26 16:45:39 -07:00
int RelPosX = m_HitBlockPos . x - a_Chunk . GetPosX () * cChunkDef :: Width ;
int RelPosZ = m_HitBlockPos . z - a_Chunk . GetPosZ () * cChunkDef :: Width ;
cChunk * Chunk = a_Chunk . GetRelNeighborChunkAdjustCoords ( RelPosX , RelPosZ );
2016-01-11 21:34:41 +02:00
2014-10-20 21:55:07 +01:00
if ( Chunk == nullptr )
2014-04-26 16:45:39 -07:00
{
// Inside an unloaded chunk, abort
return ;
}
2016-01-11 21:34:41 +02:00
2014-07-17 22:15:34 +02:00
if ( Chunk -> GetBlock ( RelPosX , m_HitBlockPos . y , RelPosZ ) == E_BLOCK_AIR ) // Block attached to was destroyed?
2014-04-26 16:45:39 -07:00
{
2014-07-17 22:15:34 +02:00
m_IsInGround = false ; // Yes, begin simulating physics again
2014-04-26 16:45:39 -07:00
}
}
2014-04-27 09:42:31 -07:00
}
2014-10-15 19:01:55 +02:00