/*--
		HitCheck.c
		Authors: Newton, Boni
	
		Effect for hit checking.
		Facilitates any hit check of a projectile. The Projectile hits anything
		which is either alive or returns for IsProjectileTarget(object projectile,
		object shooter) true. If the projectile hits something, it calls
		HitObject(object target) in the projectile.
--*/

global func FxHitCheckStart(object target, proplist effect, int temp, object by_obj, bool never_shooter)
{
	if (temp)
		return;
	effect.x = target->GetX();
	effect.y = target->GetY();
	if (!by_obj || GetType(by_obj) != C4V_C4Object)
		by_obj = target;
	if (by_obj->Contained())
		by_obj = by_obj->Contained();
	effect.shooter = by_obj;
	effect.live = false;
	effect.never_shooter = never_shooter;
	
	// C4D_Object has a hitcheck too -> change to vehicle to supress that.
	if (target->GetCategory() & C4D_Object)
		target->SetCategory((target->GetCategory() - C4D_Object) | C4D_Vehicle);
	return;
}

global func FxHitCheckStop(object target, proplist effect, int reason, bool temp)
{
	if (temp)
		return;
	
	target->SetCategory(target->GetID()->GetCategory());
	return;
}

global func FxHitCheckDoCheck(object target, proplist effect)
{
	var obj;
	// rather search in front of the projectile, since a hit might delete the effect,
	// and clonks can effectively hide in front of walls.
	var oldx = target->GetX();
	var oldy = target->GetY();
	var newx = target->GetX() + target->GetXDir() / 10;
	var newy = target->GetY() + target->GetYDir() / 10;
	var dist = Distance(oldx, oldy, newx, newy);
	
	var shooter = effect.shooter;
	var live = effect.live;
	
	if (live)
		shooter = target;
	
	if (dist <= Max(1, Max(Abs(target->GetXDir()), Abs(target->GetYDir()))) * 2)
	{
		// We search for objects along the line on which we moved since the last check
		// and sort by distance (closer first).
		for (obj in FindObjects(Find_OnLine(oldx, oldy, newx, newy),
								Find_NoContainer(),
								Find_Layer(target->GetObjectLayer()),
								Find_PathFree(target),
								Sort_Distance(oldx, oldy)))
		{
			// Excludes
			if(obj == target) continue;
			if(obj == shooter) continue;

			// Unlike in hazard, there is no NOFF rule (yet)
			// CheckEnemy
			//if(!CheckEnemy(obj,target)) continue;

			// IsProjectileTarget or Alive will be hit
			if (obj->~IsProjectileTarget(target, shooter) || obj->GetOCF() & OCF_Alive)
			{
				target->~HitObject(obj);
				if (!target)
					return;
			}
		}
	}
	return;
}

global func FxHitCheckEffect(string newname)
{
	if (newname == "HitCheck")
		return -2;
	return;
}

global func FxHitCheckAdd(object target, proplist effect, string neweffectname, int newtimer, by_obj, never_shooter)
{
	effect.x = target->GetX();
	effect.y = target->GetY();
	if (!by_obj)
		by_obj = target;
	if (by_obj->Contained())
		by_obj = by_obj->Contained();
	effect.shooter = by_obj;
	effect.live = false;
	effect.never_shooter = never_shooter;
	return;
}

global func FxHitCheckTimer(object target, proplist effect, int time)
{
	EffectCall(target, effect, "DoCheck");
	// It could be that it hit something and removed itself. thus check if target is still there.
	// The effect will be deleted right after this.
	if (!target)
		return -1;
	
	effect.x = target->GetX();
	effect.y = target->GetY();
	var live = effect.live;
	var never_shooter = effect.never_shooter;
	var shooter = effect.shooter;

	// The projectile will be only switched to "live", meaning that it can hit the
	// shooter himself when the shot exited the shape of the shooter one time.
	if (!never_shooter)
	{
		if (!live)
		{
			var ready = true;
			// We search for all objects with the id of our shooter.
			for (var foo in FindObjects(Find_AtPoint(target->GetX(), target->GetY()), Find_ID(shooter->GetID())))
			{
				// If its the shooter...
				if(foo == shooter)
					// we may not switch to "live" yet.
					ready = false;
			}
			// Otherwise, the shot will be live.
			if (ready)
				effect.live = true;
		}
	}
	return;
}

// flags for the ProjectileHit function
static const ProjectileHit_tumble=1; // the target tumbles
static const ProjectileHit_no_query_catch_blow_callback=2; // if you want to call QueryCatchBlow manually
static const ProjectileHit_exact_damage=4; // exact damage, factor 1000
static const ProjectileHit_no_on_projectile_hit_callback=8;

global func ProjectileHit(object obj, int dmg, int flags, int damage_type)
{
	if(flags == nil) flags=0;
	if(!damage_type) damage_type=FX_Call_EngObjHit;
	var tumble=flags & ProjectileHit_tumble;
	
	if (!this || !obj)
		return;
	
	if(!(flags & ProjectileHit_no_query_catch_blow_callback))
	{
		if (obj->GetAlive())
			if (obj->~QueryCatchBlow(this))
				return;
				
		if (!this || !obj)
			return;
	}
	
	if(!(flags & ProjectileHit_no_on_projectile_hit_callback))
	{
		obj->~OnProjectileHit(this);
		if (!this || !obj)
			return;
	}
	
	this->~OnStrike(obj);
	if (!this || !obj)
		return;
	if (obj->GetAlive())
	{
		obj->DoEnergy(-dmg, (flags & ProjectileHit_exact_damage), damage_type, GetController());
		if(!obj) return;
		
		var true_dmg=dmg;
		if(flags & ProjectileHit_exact_damage) true_dmg/=1000;
		obj->~CatchBlow(-true_dmg, this);
	}
	else
	{
		var true_dmg=dmg;
		if(flags & ProjectileHit_exact_damage) true_dmg/=1000;
		
		obj->DoDamage(true_dmg, damage_type, GetController());
	}
	// Target could have done something with this projectile.
	if (!this || !obj)
		return;
	
	// Tumble target.
	if (obj->GetAlive() && tumble)
	{
		obj->SetAction("Tumble");
		// Constrained by conservation of linear momentum, unrealism != 1 for unrealistic behaviour.
		var unrealism = 3;
		var mass = GetMass();
		var obj_mass = obj->GetMass();
		obj->SetXDir((obj->GetXDir() * obj_mass + GetXDir() * mass * unrealism) / (mass + obj_mass));		
		obj->SetYDir((obj->GetYDir() * obj_mass + GetYDir() * mass * unrealism) / (mass + obj_mass));
	}
	return;
}
