
enum eHammerAnims
{
	anim_Hammer_Pull  = anim_weaponFire,
	anim_Hammer_Shake = anim_weaponFireLoop,
	anim_Hammer_Fire  = anim_weaponFireCharged
}

enum eHammerFrames
{
	Hammer_SwapIn1  =   0, Hammer_SwapIn2  =  27,
	Hammer_SwapOut1 = 145, Hammer_SwapOut2 = 156,
	Hammer_Pull1    =  27, Hammer_Pull2    =  77,
	Hammer_Shake1   =  77, Hammer_Shake2   =  98,
	Hammer_Fire1    =  99, Hammer_Fire2    = 145,
	Hammer_Idle     =  27
}

final class TurokKnife : TurokWeapon
{
	array<kActor@> HitActors;
	float charge;

	TurokKnife( kWeapon@ a )
	{
		super( a );

		bool bNewGame;
		if ( GameVariables.GetBool( "g_newgame", bNewGame ) && bNewGame )
			Unreal_NewGame();
		Unreal_PreBeginLevel();
	}

	void OnPostBeginLevel()
	{
		Unreal_PostBeginLevel();
	}

	void OnEndLevel()
	{
		Unreal_EndLevel();
	}

	//==========================================================================

	void AnimProperties( int&out first, int&out last, float&out rate, bool&out bLoop )
	{
		rate = 60; bLoop = false;
		switch ( PlayingID() )
		{
			case anim_weaponSwapIn:  first = Hammer_SwapIn1;  last = Hammer_SwapIn2;  break;
			case anim_weaponSwapOut: first = Hammer_SwapOut1; last = Hammer_SwapOut2; break;
			case anim_Hammer_Pull:   first = Hammer_Pull1;    last = Hammer_Pull2;    break;
			case anim_Hammer_Shake:  first = Hammer_Shake1;   last = Hammer_Shake2;   bLoop = true; break;
			case anim_Hammer_Fire:   first = Hammer_Fire1;    last = Hammer_Fire2;    break;
			default: /* idle */      first =                  last = Hammer_Idle;     bLoop = true; break;
		}
	}

	void AnimEnd()
	{
		if ( IsPlaying(anim_Hammer_Pull) )
			PlayAnim( anim_Hammer_Shake );
		else
			TurokWeapon::AnimEnd();
	}

	bool Refire()
	{
		return IsPlaying( anim_Hammer_Fire );
	}

	void OnBeginFire()
	{
		// primary takes precedence if both are pressed
		if ( bFire1 )
		{
			PlayWeaponSound( "UT/sounds/Hammer/Charge_Start.ksnd" );
			PlayAnim( anim_Hammer_Pull );
		}
		else
		{
			self.PlaySound( "UT/sounds/Hammer/Fire_Quick.ksnd" );
			charge = 0;
			Punch( 10 );
		}
	}

	void OnTick()
	{
		TurokWeapon::OnTick();
		if ( self.Owner().Locked() )
		{
			Cancel();
			return;
		}
		float f = 0;
		switch ( PlayingID() )
		{
			case anim_Hammer_Shake:
				self.PlaySound( "UT/sounds/Hammer/Charge_Loop.ksnd" );
				f = (Hammer_Pull2 - Hammer_Pull1) / 60.0f;
				// fall through
			case anim_Hammer_Pull:
				OwnerP().LoudNoiseAlert();
				f += PlayTime();
				if ( !bFire1 )
				{
					StopWeaponSound(); // stop charge start sound
					self.StopLoopingSounds(); // stop charging sound
					self.PlaySound( "UT/sounds/Hammer/Fire_Charged.ksnd" );
					charge = f > 2 ? 1.0f : f / 2;
					// just enough to 1-shot raptors on hard with a full charge
					Punch( int( 10 + 62 * charge*charge ) );
				}
				break;
			case anim_Hammer_Fire:
				Recoil();
				// continue blocking for first half of fire anim
				if ( self.ModelVariation() < (Hammer_Fire1 + Hammer_Fire2) / 2 )
					break;
				// else fall through
			// cancel if interrupted by forced swap-out (climbing)
			case anim_weaponSwapOut:
			// jumping off a wall at just the wrong time can prevent forced swap-out from playing
			// so cancel everything during swap-in, too
			case anim_weaponSwapIn:
				Cancel();
		}
	}

	void Recoil()
	{
		float c = 0.5f + charge * 0.25f;
		float f = 1 - float(self.ModelVariation() - Hammer_Fire1) / ( (Hammer_Fire2 - Hammer_Fire1) * c );
		if ( f < 0 ) return;
		f *= f;
		float amp = 1 + charge;
		c = charge * charge;
		OwnerP().RecoilPitch() = Math::Sin( Math::pi * f ) * f * -0.03f * amp;
		OwnerP().Roll() = Math::Sin( Math::pi * ((2 + c) * f - c) ) * f * 0.03f * amp;
	}

	void Cancel()
	{
		self.StopLoopingSounds(); // stop charging sound
		OwnerP().ImpactType() = IT_FLESH_HUMAN; // stop deflecting
	}

	void Punch( int damage )
	{
		PlayAnim( anim_Hammer_Fire );
		OwnerP().ImpactType() = IT_STONE; // deflect projectiles
		OwnerP().LoudNoiseAlert();
		if ( UDamage() ) damage *= 3;

		float range = 5.75f*GAME_SCALE;
		kVec3 eye = EyePos();
		kVec3 dir = kVec3( 0, 1, 0 ) * OwnerP().Rotation();
		kVec3 origin = eye + dir*range;
		self.InteractActorsAtPosition( origin, "Impact", range, origin.x, origin.y, origin.z );

		if ( HitActors.length() < 1 )
		{
/*			origin = eye + kVec3( 0, 2*range, 0 ) * OwnerP().Rotation();
			if ( Hitscan( eye, origin ) )
			{
				Game.SpawnFx( "fx/jk/fistx.kfx", OwnerP(), CModel.InterceptVector(), CModel.ContactNormal().ToQuat() );
			}
*/			return;
		}

		origin = ( origin + eye ) / 2;

		kVec3 bestV;
		float bestDist = 0;
		kActor@ best;
		for ( uint i=0; i<HitActors.length(); ++i )
		{
			kActor@ a = HitActors[i];
			kVec3 v = a.Origin();
			v += ( eye - v ).Normalize() * a.Radius();
			v.z = origin.z;
			if ( v.z > a.Origin().z + a.Height() ) v.z = a.Origin().z + a.Height();
			else if ( v.z < a.Origin().z ) v.z = a.Origin().z;
			float dist = (eye-v).UnitSq();
			if ( best !is null && dist >= bestDist ) continue;
			@best = a;
			bestDist = dist;
			bestV = v;
		}
		// clear the list now that we've chosen the best
		HitActors.resize(0);

		// always 1-shot beetles
		if ( best.Type() == AT_INSECT && damage < 15 )
			MeleeDamage( best, 15 );
		else
			MeleeDamage( best, damage );

		origin = best.Origin();
		origin.z = bestV.z;
		kQuat q = ( (bestV - origin).Normalize() - dir ).ToQuat();
		switch ( best.ImpactType() )
		{
			case IT_DEFAULT: best.PlaySound( "sounds/shaders/bullet_impact_3.ksnd" );
				Game.SpawnFx( "fx/foliage.kfx",     best, bestV, q ); break;
			case IT_METAL: best.PlaySound( "sounds/shaders/bullet_impact_5.ksnd" );
				Game.SpawnFx( "fx/generic_251.kfx", best, bestV, q ); break;
			case IT_FLESH_HUMAN: best.PlaySound( "sounds/shaders/bullet_impact_13.ksnd" );
				Game.SpawnFx( "fx/blood.kfx",       best, bestV, q ); break;
			case IT_FLESH_CREATURE: best.PlaySound( "sounds/shaders/bullet_impact_14.ksnd" );
				Game.SpawnFx( "fx/greenblood.kfx",  best, bestV, q ); break;
			case IT_FORCEFIELD: best.PlaySound( "sounds/shaders/generic_219.ksnd" );
				Game.SpawnFx( "fx/generic_187.kfx", best, bestV, q ); break;
		}
	}

	void Impact( kActor@ a, const float range, const float x, const float y, const float z )
	{
		if ( a is null || a.IsStale() || a.Flags() & AF_HOLDTRIGGERANIM != 0 )
			return;
		if ( a is OwnerA() )
			return;
		if ( !OwnerP().CanSee( a, CF_IGNOREBLOCKERS ) )
			return;
		float f = range + a.Radius();
		if ( a.DistanceToPoint(x,y,z) > f*f )
			return;
		HitActors.insertLast( a );
	}

	void MeleeDamage( kActor@ target, int damage )
	{
		int health = target.Health();
		target.InflictGenericDamage( OwnerA(), damage );
		if ( target.Health() <= 0 && health > 0 && target.ScriptObject() !is null )
		{
			TurokEnemy@ E;
			if ( ( @E = cast<TurokEnemy@>(target.ScriptObject().obj) ) !is null )
				E.m_bMortallyWounded = true;
		}
	}
}
