#include "rp.h"
#pragma hdrstop

#include "demopack.h"
#include "..\munga\line.h"
#include "..\munga\explode.h"
#include "..\munga\boxsolid.h"
#include "..\munga\collasst.h"
#include "..\munga\app.h"
#include "..\munga\interest.h"
#include "vtv.h"
#include "..\munga\hostmgr.h"
#include "..\munga\notation.h"

//#############################################################################
// Shared Data Support
//
Derivation* DemolitionPack::GetClassDerivations()
{	static Derivation classDerivations(Rivet::GetClassDerivations(), "DemolitionPack");
	return &classDerivations;
}


DemolitionPack::SharedData
	DemolitionPack::DefaultData(
		DemolitionPack::GetClassDerivations(),
		DemolitionPack::GetMessageHandlers(),
		DemolitionPack::GetAttributeIndex(),
		DemolitionPack::StateCount,
		(Entity::MakeHandler)DemolitionPack::Make
	);

//#############################################################################
// Attribute Support
//
#if 0
const DemolitionPack::IndexEntry
	DemolitionPack::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(DemolitionPack, DamageData, damageData)
};

DemolitionPack::AttributeIndexSet& DemolitionPack::GetAttributeIndex()
{
	static DemolitionPack::AttributeIndexSet attributeIndex(ELEMENTS(DemolitionPack::AttributePointers), DemolitionPack::AttributePointers, Mover::GetAttributeIndex());
	return attributeIndex;
}
#endif

//#############################################################################
// Model support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	DemolitionPack::MoveAndHunt(Scalar time_slice)
{
	Line
		distance_traveled;
	distance_traveled	= localOrigin.linearPosition;

	//
	//-----------------------------------------------------------------
	// Apply the standard air resistance and gravity, then project the
	// Projectile motion through the time slice and look for collisions
	//-----------------------------------------------------------------
	//
	ApplyAirResistanceAndGravity();
	Point3D old_position(localOrigin.linearPosition);
	ApplyWorldAccelerations(time_slice);

	//
	//--------------------
	// Do a collision test
	//--------------------
	//
	MoveCollisionVolume();
	BoxedSolidCollisionList*
		collision_list = GetCurrentCollisions();

	//
	//---------------------------------------------------------------------
	// If our solid didn't hit anything, and we are going fast enough, do a
	// ray-cast
	//---------------------------------------------------------------------
	//
	if (!collision_list->GetCollisionCount())
	{
		Line
			line;
		line = localOrigin.linearPosition;
		Vector3D
			step;
		step.Subtract(localOrigin.linearPosition, old_position);
		line = step;
		Scalar
			diameter = collisionTemplate->maxX - collisionTemplate->minX;
		if (diameter < line.length)
		{
			CollideCenterOfMotion(&line, collision_list);
		}
	}

	//
	//-------------------------------
	// Process each of the collisions
	//-------------------------------
	//
	Damage collision_damage;
	ProcessCollisionList(
		collision_list,
		time_slice,
		old_position,
		&collision_damage
	);

	(this->*deadReckoner)();
	Vector3D
		error;
	error.Subtract(
		projectedOrigin.linearPosition,
		localOrigin.linearPosition
	);
	if (error.LengthSquared() > 0.5f)
	{
		ForceUpdate();
	}

	//
	//----------------------
	// Hunt for VTVs to kill
	//----------------------
	//
	Check_Fpu();
	Hunt(time_slice);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	DemolitionPack::Hunt(Scalar)
{
	if ((lastPerformance - creationTime) >= fuseTime)
	{
		//
		//---------------------------------
		// Test against the tangible movers
		//---------------------------------
		//
		Check(collisionAssistant);
		CollisionAssistant::MovingEntityIterator
			iterator(collisionAssistant);
		Entity
			*entity;
		Check(collisionVolume);
		while ((entity = iterator.ReadAndNext()) != NULL)
		{
			//
			//------------------------------------------
			// If the mover is not within range, no boom
			//------------------------------------------
			//
			Vector3D
				range;
			range.Subtract(
				entity->localOrigin.linearPosition,
				localOrigin.linearPosition
			);
			if ((range.LengthSquared() >= detectionRadiusSquared) && ((lastPerformance - creationTime) <= (fuseTime + 60.0f)))
			{
				continue;
			}

			//
			//----------------------------------
			// We got a boom!  Make an explosion
			//----------------------------------
			//
			CondemnToDeathRow();
			Check(application);
			ResourceFile	*resource_file = application->GetResourceFile();
			Check(resource_file);
			ResourceDescription
				*explosion_res = resource_file->
					FindResourceDescription(explosionResourceID);
			Check(explosion_res);
			explosion_res->Lock();

			Explosion::MakeMessage exp_message(
				Explosion::MakeMessageID,
				sizeof(Explosion::MakeMessage),
				ExplosionClassID,
				EntityID::Null,
				explosion_res->resourceID,
				Explosion::DefaultFlags,
				localOrigin,
				entity->GetEntityID(),
				shootingEntity->GetEntityID()
			);

			explosion_res->Unlock();

			#if DEBUG_LEVEL>0
				Explosion
					*exp =
			#endif
						Explosion::Make(&exp_message);
				Register_Object(exp);

			//
			//----------------------------------------------
			// Send a damage message to whoever we hit
			//----------------------------------------------
			//
         if ((lastPerformance - creationTime) <= (fuseTime + 60.0f))
         {
				iterator.First();
				while ((entity = iterator.ReadAndNext()) != NULL)
				{
					if (entity->GetClassID() == RegisteredClass::VTVClassID)
					{
						VTV *vtv = Cast_Object(VTV*, entity);
						Damage damage_data;
						damage_data.damageForce.Subtract(
								vtv->localOrigin.linearPosition,
								localOrigin.linearPosition
						);
						if (damage_data.damageForce.LengthSquared() > blastRadiusSquared)
						{
							continue;
						}
						damage_data.damageForce.Normalize(damage_data.damageForce);
						damage_data.damageForce *= concussiveForce;
						damage_data.damageAmount = damageData.damageAmount;
						damage_data.damageType = Damage::ExplosiveDamageType;
						//
						//------------------------------------------
						// Send a damage message to the thing we hit
						//------------------------------------------
						//
						Entity::TakeDamageMessage
							take_damage(
								Entity::TakeDamageMessageID,
								sizeof(Entity::TakeDamageMessage),
								shootingEntity->GetEntityID(),
								0,
								damage_data
							);
						entity->Dispatch(&take_damage);
					}
				}
				break;
      	}
		}
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	DemolitionPack::ProcessCollision(
		Scalar time_slice,
		BoxedSolidCollision &collision,
		const Point3D &old_position,
		Damage *damage
	)
{
	Scalar penetration,r;
	Scalar elasticity = elasticityCoefficient;
	Scalar friction = frictionCoefficient;

	//
	//-------------------------------------------------------------------------
	// If we have hit a mover, adjust our velocity relative to it, unless we
	// have collided with the VTV what dropped us, in which case we will ignore
	// the hit
	//-------------------------------------------------------------------------
	//
	BoxedSolid *box = collision.GetTreeVolume();
	Check(box);
	Simulation *sim = box->GetOwningSimulation();
	Mover *mover = NULL;
	if (sim && sim->IsDerivedFrom(*Mover::GetClassDerivations()))
	{
		mover = (Mover*)sim;
		Check(mover);
		if (mover == shootingEntity)
		{
			Check_Fpu();
			return;
		}
		worldLinearVelocity -= mover->worldLinearVelocity;
	}

	//
	//--------------------------------------------------------------------
	// If we really have a collision, do a static bounce off of the normal
	// generated
	//--------------------------------------------------------------------
	//
	if (
		collisionVolume->ProcessCollision(
			collision,
			worldLinearVelocity,
			lastCollisionList,
			&damage->surfaceNormal,
			&penetration
		)
	)
	{
		Max_Clamp(penetration, time_slice);
		r = penetration / time_slice;
		Check_Fpu();

		StaticBounce(
			old_position,
			time_slice,
			r,
			damage->surfaceNormal,
			&elasticity,
			minimumBounceSpeed,
			&friction
		);

		//
		//--------------------------
		// Determine collision state
		//--------------------------
		//
		if (!elasticity && !friction && !mover)
		{
			SetPerformance(&DemolitionPack::Hunt);
			worldLinearVelocity = Vector3D::Identity;
			worldLinearAcceleration = Vector3D::Identity;
			localVelocity = Motion::Identity;
			localAcceleration = Motion::Identity;
			ForceUpdate();
			mover = NULL;
		}
	}
	if (mover)
	{
		worldLinearVelocity += mover->worldLinearVelocity;
	}
	Check_Fpu();
}

//#############################################################################
// Construction and Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DemolitionPack::DemolitionPack(
	DemolitionPack::MakeMessage *creation_message,
	DemolitionPack::SharedData &virtual_data
):
	Rivet(creation_message, virtual_data)
{
	Check_Pointer(this);

	//
	//------------------------
	// Setup the initial state
	//------------------------
	//

	SetValidFlag();
	InterestZone	*interest_zone;

	Check(application);
	Check(application->GetInterestManager());
	interest_zone =
		application->GetInterestManager()->GetInterestZone(interestZoneID);
	Check(interest_zone);
	localEnvironment = interest_zone->GetEnvironment();
	Check(localEnvironment);

	//
	//
	SetDeadReckoner(&DemolitionPack::AcceleratedDeadReckoner);
	if (GetInstance() == ReplicantInstance)
	{
		SetPerformance(&DemolitionPack::DeadReckon);
	}
	else
	{
		SetPerformance(&DemolitionPack::MoveAndHunt);
	}

	//
	// Initialize the Projectile variables
	//
	ResourceDescription *res =
		application->GetResourceFile()->SearchList(
			resourceID,
			ResourceDescription::GameModelResourceType
		);
	Check(res);
	res->Lock();

	ModelResource* model = (ModelResource*)res->resourceAddress;
	Check_Pointer(model);
	blastRadiusSquared = model->blastRadiusSquared;
	detectionRadiusSquared = model->detectionRadiusSquared;
	fuseTime = model->fuseTime;
	concussiveForce = model->concussiveForce;
	damageData = model->damageData;
	explosionResourceID = model->explosionResourceID;

	res->Unlock();

	HostManager *host_mgr = application->GetHostManager();
	Check(host_mgr);
	shootingEntity =
		host_mgr->GetEntityPointer(creation_message->shootingEntity);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DemolitionPack*
	DemolitionPack::Make(DemolitionPack::MakeMessage *creation_message)
{
	return new DemolitionPack(creation_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DemolitionPack::~DemolitionPack()
{
	Check(this);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  CreateModelResource
//
int
	DemolitionPack::CreateModelResource(
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories,
		ModelResource* model
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	//
	//-----------------------------------------------------------------------
	// If we were not provided a buffer to write the model data into, we must
	// create it ourselves.  Then make sure that the mover stuff is read in
	//-----------------------------------------------------------------------
	//
	ModelResource *local_model = model;
	if (!local_model)
	{
		local_model = new ModelResource;
		Register_Pointer(local_model);
	}

	if(
		Rivet::CreateModelResource(
			resource_file,
			model_name,
			model_file,
			directories,
			local_model
			) == -1
		)
	{
Dump_And_Die:
		if (!model)
		{
			Unregister_Pointer(local_model);
			delete local_model;
		}
		Check_Fpu();
		return -1;
	}

	//
	//---------------------------------------
	// Read in the damage amount for the ammo
	//---------------------------------------
	//
	if(
		!model_file->GetEntry(
			"gamedata",
			"BlastRadius",
			&local_model->blastRadiusSquared
		)
	)
	{
		std::cout << model_name <<  " missing BlastRadius!\n";
		goto Dump_And_Die;
	}
	{
		 local_model->blastRadiusSquared *= local_model->blastRadiusSquared;
	}
	if(
		!model_file->GetEntry(
			"gamedata",
			"DetectionRadius",
			&local_model->detectionRadiusSquared
		)
	)
	{
		std::cout << model_name <<  " missing DetectionRadius!\n";
		goto Dump_And_Die;
	}
	else
	{
		local_model->detectionRadiusSquared *= local_model->detectionRadiusSquared;
	}
	if(
		!model_file->GetEntry(
			"gamedata",
			"ConcussiveForce",
			&local_model->concussiveForce
		)
	)
	{
		std::cout << model_name <<  " missing ConcussiveForce!\n";
		goto Dump_And_Die;
	}
	if(
		!model_file->GetEntry(
			"gamedata",
			"FuseTime",
			&local_model->fuseTime
		)
	)
	{
		std::cout << model_name <<  " missing FuseTime!\n";
		goto Dump_And_Die;
	}

		//
	//-------------------------------------------------------------------------
	// If we created the model buffer, then we have the responsibility to write
	// it out to the resource file
	//-------------------------------------------------------------------------
	//
	if (!model)
	{
		ResourceDescription *new_res =
			resource_file->AddResource(
				model_name,
				ResourceDescription::GameModelResourceType,
				1,
				ResourceDescription::Preload,
				local_model,
				sizeof(*local_model)
			);
		Unregister_Pointer(local_model);
		delete local_model;
		Check(new_res);
		Check_Fpu();
		return new_res->resourceID;
	}
	else
	{
		Check_Fpu();
		return 0;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	DemolitionPack::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}
