// ----------------------------------------------------------------------- //
//
// MODULE  : Keyframer.cpp
//
// PURPOSE : Keyframer implementation
//
// CREATED : 10/7/97
//
// ----------------------------------------------------------------------- //

#include "KeyFramer.h"
#include "cpp_server_de.h"
#include "RiotObjectUtilities.h"
#include "stdio.h"
#include "generic_msg_de.h"


BEGIN_CLASS(KeyFramer)
	ADD_STRINGPROP(ObjectName, "")
	ADD_STRINGPROP(BaseKeyName, "")
	ADD_BOOLPROP(StartActive, DFALSE)
	ADD_BOOLPROP(Looping, DFALSE)
END_CLASS_DEFAULT(KeyFramer, BaseClass, NULL, NULL)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::KeyFramer()
//
//	PURPOSE:	Initialize object
//
// ----------------------------------------------------------------------- //

KeyFramer::KeyFramer() : BaseClass(OT_NORMAL)
{
	m_hstrObjectName = NULL;
	m_hstrBaseKeyName = NULL;
	m_bStartActive = DFALSE;
	m_bLooping = DFALSE;

	m_bActive = DFALSE;
	m_pObjectList = NULL;
	m_pOffsets.SetMemCopyable(1);
	m_pOffsets.SetGrowBy(3);
	m_pRotations.SetMemCopyable(1);
	m_pRotations.SetGrowBy(3);
	
	m_pKeys = NULL;
	m_pCurKey = NULL;
	m_pPosition1 = NULL;
	m_pPosition2 = NULL;
	m_nNumKeys = 0;

	m_fCurTime = 0.0f;

	m_bFirstUpdate = DTRUE;
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::~KeyFramer()
//
//	PURPOSE:	Deallocate object
//
// ----------------------------------------------------------------------- //

KeyFramer::~KeyFramer()
{
	CServerDE* pServerDE = GetServerDE();
	if (pServerDE)
	{
		pServerDE->FreeString (m_hstrObjectName);
		pServerDE->FreeString (m_hstrBaseKeyName);
		if (m_pObjectList)
		{
			pServerDE->RelinquishList (m_pObjectList);
		}
	}

	while (m_pKeys)
	{
		KEYNODE* pNext = m_pKeys->pNext;
		delete m_pKeys;
		m_pKeys = pNext;
	}
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::GoActive
//
//	PURPOSE:	Start the KeyFramer going!
//
// ----------------------------------------------------------------------- //

void KeyFramer::GoActive()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || m_bActive || !m_pKeys || 
		!m_pObjectList || (m_pObjectList->m_nInList < 1)) return;
	
	// Go active

	m_bActive	= DTRUE;
	m_fCurTime	= 0.0f;
	m_pCurKey	= m_pKeys;
	pServerDE->SetNextUpdate(m_hObject, 0.001f);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::CreateKeyList
//
//	PURPOSE:	Create our list of keys.
//
// ----------------------------------------------------------------------- //

DBOOL KeyFramer::CreateKeyList()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hstrObjectName || m_pObjectList) return DFALSE;
	

	// Find the objects...

	char* pName = pServerDE->GetStringData(m_hstrObjectName);
	m_pObjectList = pServerDE->FindNamedObjects(pName);

	if (m_pObjectList)
	{
		if (m_pObjectList->m_nInList)
		{
			// Create links to all these objects so we know if/when they
			// get removed...

			ObjectLink* pLink = m_pObjectList->m_pFirstLink;
			while (pLink)
			{
				pServerDE->CreateInterObjectLink(m_hObject, pLink->m_hObject);
				pLink = pLink->m_pNext;
			}
		}
		else
		{
			pServerDE->RelinquishList(m_pObjectList);
			m_pObjectList = DNULL;
			return DFALSE;  // No objects, no reason to go on...
		}	
	}
	

	// Create the list of keys...

	int nNum = 0;
	DFLOAT fTime = 0.0f;
	KEYNODE* pNode = NULL;

	while (1)
	{
		// Create the keyname string...

		char strKey[128]; memset (strKey, 0, 128);
		char strNum[18]; memset (strNum, 0, 18);

		_itoa (nNum, strNum, 10);
		SAFE_STRCPY(strKey, pServerDE->GetStringData (m_hstrBaseKeyName));
		strcat (strKey, strNum);

		// Find the first key with that name...

		ObjectList* pTempKeyList = pServerDE->FindNamedObjects (strKey);
		ObjectLink* pLink = pTempKeyList->m_pFirstLink;
		
		DBOOL bFoundKey = DFALSE;
		while (pLink && !bFoundKey)
		{
			if (pServerDE->IsKindOf (pServerDE->GetObjectClass (pLink->m_hObject), pServerDE->GetClass ("Key")))
			{
				// Add this key to the list...

				if (!m_pKeys)
				{
					m_pKeys = new KEYNODE;
					pNode = m_pKeys;
				}
				else
				{
					pNode->pNext = new KEYNODE;
					pNode->pNext->pPrev = pNode;
					pNode = pNode->pNext;
				}

				
				// Copy the key...

				pNode->keyData.Copy((Key*)pServerDE->HandleToObject(pLink->m_hObject));

				fTime += pNode->keyData.m_fTimeStamp;
				pNode->keyData.m_fRealTime = fTime;

				
				// Remove the key from the world...No longer needed...
				
				pServerDE->RemoveObject(pLink->m_hObject);


				bFoundKey = DTRUE;

				m_nNumKeys++;
			}

			pLink = pLink->m_pNext;
		}

		pServerDE->RelinquishList (pTempKeyList);
		
		// If we didn't find a key, we're done looking...

		if (!pLink && !bFoundKey)
		{
			break;
		}

		// Increment the counter...

		nNum++;
	}

	
	// If we didn't find any keys, return...

	if (!m_pKeys)
	{
		return DFALSE;
	}

	
	// For each object we're controlling, save it's offset from the first key...

	KEYNODE* pFirstPosKey = GetNextPositionKey(m_pKeys);

	if (pFirstPosKey)
	{
		// Get the first position key's location

		DVector pos;
		DRotation rot;

		VEC_COPY(pos, pFirstPosKey->keyData.m_vPos);
		ROT_COPY(rot, pFirstPosKey->keyData.m_rRot);
		
		long i = 0;
		ObjectLink* pLink = m_pObjectList->m_pFirstLink;
		while (pLink)
		{
			DVector objpos;
			pServerDE->GetObjectPos(pLink->m_hObject, &objpos);
			VEC_SUB(m_pOffsets[i], objpos, pos);

			DRotation objrot;
			pServerDE->GetObjectRotation(pLink->m_hObject, &objrot);
			VEC_SUB(m_pRotations[i].m_Vec, objrot.m_Vec, rot.m_Vec);
			m_pRotations[i].m_Spin = objrot.m_Spin - rot.m_Spin;

			i++;
			pLink = pLink->m_pNext;
		}
	}
	
	return DTRUE;
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::GetNextPositionKey
//
//	PURPOSE:	Returns the next key with position type
//
// ----------------------------------------------------------------------- //

KEYNODE* KeyFramer::GetNextPositionKey(KEYNODE* pNode)
{
	if (!pNode) return NULL;

	while (pNode)
	{
		if (pNode->keyData.m_nKeyType & POSITION_KEY)
		{
			return pNode;
		}

		pNode = pNode->pNext;
	}

	return NULL;
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::ProcessKey
//
//	PURPOSE:	Processes the given key
//
// ----------------------------------------------------------------------- //

void KeyFramer::ProcessKey (KEYNODE* pNode)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return;
	
	if (pNode->keyData.m_nKeyType & POSITION_KEY)
	{
		m_pPosition1 = pNode;
		m_pPosition2 = GetNextPositionKey(pNode->pNext);
	}

	if (pNode->keyData.m_nKeyType & SOUND_KEY)
	{
		PlaySoundFromObject(m_hObject, pServerDE->GetStringData(pNode->keyData.m_hstrSoundName), pNode->keyData.m_fSoundRadius, SOUNDPRIORITY_MISC_HIGH );
	}

	if (pNode->keyData.m_nKeyType & MESSAGE_KEY)
	{
		SendTriggerMsgToObjects(this, pNode->keyData.m_hstrMessageTarget, pNode->keyData.m_hstrMessageName);
	}

	if (pNode->keyData.m_nKeyType & BPRINT_KEY)
	{
		pServerDE->BPrint(pServerDE->GetStringData(pNode->keyData.m_hstrBPrintMessage));
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD KeyFramer::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData)
{
	switch(messageID)
	{
		case MID_UPDATE:
		{
			Update((DVector *)pData);
			break;
		}

		case MID_PRECREATE:
		{
			if (fData == PRECREATE_WORLDFILE)
			{
				ReadProp((ObjectCreateStruct *)pData);
			}
			break;
		}

		case MID_INITIALUPDATE:
		{
			if (fData != INITIALUPDATE_SAVEGAME)
			{
				InitialUpdate((DVector *)pData);
			}
			break;
		}

		case MID_LINKBROKEN :
		{
			HOBJECT hLink = (HOBJECT)pData;
			if (m_pObjectList && hLink)
			{
				ObjectLink* pLink = m_pObjectList->m_pFirstLink;
				ObjectLink* pPrevious = DNULL;

				while (pLink)
				{
					if (pLink->m_hObject == hLink)
					{
						m_pObjectList->m_nInList--;

						if (pLink == m_pObjectList->m_pFirstLink)
						{
							m_pObjectList->m_pFirstLink = DNULL;
						}
						else if (pPrevious)
						{
							pPrevious->m_pNext = pLink->m_pNext;
						}

						// If the list is now empty, remove it...

						if (!m_pObjectList->m_nInList)
						{
							CServerDE* pServerDE = GetServerDE();
							if (pServerDE)
							{
								pServerDE->RelinquishList(m_pObjectList);
								m_pObjectList = DNULL;
							}
						}
						
						break;
					}
					pPrevious = pLink;
					pLink = pLink->m_pNext;
				}
			}
		}
		break;

		case MID_SAVEOBJECT:
		{
			Save((HMESSAGEWRITE)pData, (DDWORD)fData);
		}
		break;

		case MID_LOADOBJECT:
		{
			Load((HMESSAGEREAD)pData, (DDWORD)fData);
		}
		break;

		default : break;
	}


	return BaseClass::EngineMessageFn(messageID, pData, fData);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::ObjectMessageFn
//
//	PURPOSE:	Handle object messages
//
// ----------------------------------------------------------------------- //

DDWORD KeyFramer::ObjectMessageFn(HOBJECT hSender, DDWORD messageID, HMESSAGEREAD hRead)
{
	switch (messageID)
	{
		case MID_TRIGGER:
		{
			CServerDE* pServerDE = GetServerDE();
			if (!pServerDE) return 0;

			HSTRING hMsg = pServerDE->ReadFromMessageHString(hRead);
			char* pMsg = pServerDE->GetStringData(hMsg);
			if (!pMsg) return 0;

			if (_stricmp(pMsg, "ON") == 0)
			{
				CreateKeyList();
				GoActive();
			}
			else if (_stricmp(pMsg, "OFF") == 0)
			{
				m_bActive	= DFALSE;
				pServerDE->SetNextUpdate(m_hObject, 0.0f);			
			}

			pServerDE->FreeString(hMsg);
		}
		break;

		default : break;
	}
	
	return BaseClass::ObjectMessageFn(hSender, messageID, hRead);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::ReadProp
//
//	PURPOSE:	Set property value
//
// ----------------------------------------------------------------------- //

DBOOL KeyFramer::ReadProp(ObjectCreateStruct *)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return DFALSE;

	char buf[MAX_CS_FILENAME_LEN];

	buf[0] = '\0';
	pServerDE->GetPropString("ObjectName", buf, MAX_CS_FILENAME_LEN);
	if (buf[0]) m_hstrObjectName = pServerDE->CreateString(buf);

	buf[0] = '\0';
	pServerDE->GetPropString("BaseKeyName", buf, MAX_CS_FILENAME_LEN);
	if (buf[0]) m_hstrBaseKeyName = pServerDE->CreateString(buf);

	pServerDE->GetPropBool("StartActive", &m_bStartActive);
	pServerDE->GetPropBool("Looping", &m_bLooping);

	return DTRUE;
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::InitialUpdate()
//
//	PURPOSE:	First update
//
// ----------------------------------------------------------------------- //

DBOOL KeyFramer::InitialUpdate(DVector* pMovement)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return DFALSE;

	pServerDE->SetNextUpdate(m_hObject, 0.001f);

	return DTRUE;
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::Update()
//
//	PURPOSE:	Update
//
// ----------------------------------------------------------------------- //

DBOOL KeyFramer::Update(DVector* pMovement)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return DFALSE;


	// Need to check this here (instead of InitialUpdate)...This insures 
	// that all the Keys have been added to the world...

	if (m_bFirstUpdate)
	{
		m_bFirstUpdate = DFALSE;

		CreateKeyList();

		if (m_bStartActive)
		{
			GoActive();
		}
	}

	
	// See if we are even supposed to be here (today)...

	if (!m_bActive)
	{
		pServerDE->SetNextUpdate(m_hObject, 0.0f);
		return DTRUE;
	}
	else
	{
		pServerDE->SetNextUpdate(m_hObject, 0.001f);
	}


	// increment timer

	m_fCurTime += pServerDE->GetFrameTime();

	// process all keys that we might have passed by

	while (m_pCurKey)
	{
		if (m_pCurKey->keyData.m_fRealTime > m_fCurTime) break;

		ProcessKey (m_pCurKey);
		m_pCurKey = m_pCurKey->pNext;
	}

	// are we at the end of the key list?

	if (!m_pCurKey)
	{
		m_pCurKey = m_pKeys;
		m_fCurTime = 0.0f;

		if (!m_bLooping)
		{
			m_bActive = DFALSE;
			return DTRUE;
		}
	}

	// interpolate position

	if (m_pPosition1 && m_pPosition2)
	{
		if (m_fCurTime >= m_pPosition1->keyData.m_fRealTime && m_fCurTime <= m_pPosition2->keyData.m_fRealTime)
		{
			float t = (m_fCurTime - m_pPosition1->keyData.m_fRealTime) / (m_pPosition2->keyData.m_fRealTime - m_pPosition1->keyData.m_fRealTime);
			
			// get the new position from position 1's position

			DVector pos1;
			VEC_COPY(pos1, m_pPosition1->keyData.m_vPos);

			DVector pos2;
			VEC_COPY(pos2, m_pPosition2->keyData.m_vPos);
			
			DVector posNew;
			VEC_LERP(posNew, pos1, pos2, t);

			// get the new angle from position 1's angle

			DRotation rot1;
			ROT_COPY(rot1, m_pPosition1->keyData.m_rRot);
			
			DRotation rot2;
			ROT_COPY(rot2, m_pPosition2->keyData.m_rRot);

			DRotation rotNew;
			ROT_INIT(rotNew);
			pServerDE->InterpolateRotation(&rotNew, &rot1, &rot2, t);

			// Now add the relative position and rotation to every object in the list

			long i = 0;
			ObjectLink* pLink = m_pObjectList ? m_pObjectList->m_pFirstLink : DNULL;
			while (pLink)
			{
				pServerDE->GetObjectPos(pLink->m_hObject, &pos1);
				VEC_ADD (pos1, posNew, m_pOffsets[i]);

				pServerDE->GetObjectRotation(pLink->m_hObject, &rot1);
				VEC_ADD(rot1.m_Vec, rotNew.m_Vec, m_pRotations[i].m_Vec);
				rot1.m_Spin = rotNew.m_Spin + m_pRotations[i].m_Spin;

				pServerDE->SetObjectPos(pLink->m_hObject, &pos1);
				pServerDE->RotateObject(pLink->m_hObject, &rot1);

				i++;
				pLink = pLink->m_pNext;
			}
		}
	}

	return DTRUE;
}



// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::Save
//
//	PURPOSE:	Save the object
//
// ----------------------------------------------------------------------- //

void KeyFramer::Save(HMESSAGEWRITE hWrite, DDWORD dwSaveFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hWrite) return;

	// Make sure we created the keylist (if Save is called before the first
	// update, this may not have been created)...

	CreateKeyList();


	pServerDE->WriteToMessageFloat(hWrite, m_fCurTime);
	pServerDE->WriteToMessageHString(hWrite, m_hstrObjectName);
	pServerDE->WriteToMessageHString(hWrite, m_hstrBaseKeyName);
	pServerDE->WriteToMessageByte(hWrite, m_bStartActive);
	pServerDE->WriteToMessageByte(hWrite, m_bLooping);
	pServerDE->WriteToMessageByte(hWrite, m_bActive);
	pServerDE->WriteToMessageByte(hWrite, m_bFirstUpdate);
	pServerDE->WriteToMessageByte(hWrite, m_nNumKeys);


	// Determine the position in the list of m_pCurKey, m_pPosition1, and
	// m_pPosition2.  Also, save out m_pKeys...

	int nCurKeyIndex    = -1;
	int nPosition1Index = -1;
	int nPosition2Index = -1;
	
	KEYNODE* pCurKey = m_pKeys;
	for (int i=0; i < m_nNumKeys && pCurKey; i++)
	{
		if (m_pCurKey == pCurKey)
		{
			nCurKeyIndex = i;
		}
		if (m_pPosition1 == pCurKey)
		{
			nPosition1Index = i;
		}
		if (m_pPosition2 == pCurKey)
		{
			nPosition2Index = i;
		}

		pCurKey->keyData.Save(hWrite, dwSaveFlags);
		pCurKey = pCurKey->pNext;
	}
		
	// Save out the positions of our pointer data members...

	pServerDE->WriteToMessageFloat(hWrite, (DFLOAT) nCurKeyIndex);
	pServerDE->WriteToMessageFloat(hWrite, (DFLOAT) nPosition1Index);
	pServerDE->WriteToMessageFloat(hWrite, (DFLOAT) nPosition2Index);


	// Save the number of objects to be key-framed...

	DBYTE nNumInList = m_pObjectList ? m_pObjectList->m_nInList : 0;
	pServerDE->WriteToMessageByte(hWrite, nNumInList);

	// Save the offsets and rotations for each object...

	for (i=0; i < nNumInList; i++)
	{
		pServerDE->WriteToMessageVector(hWrite, &(m_pOffsets[i]));
		pServerDE->WriteToMessageRotation(hWrite, &(m_pRotations[i]));
	}	

	// Save the objects we're supposed to key-frame...

	if (m_pObjectList && nNumInList)
	{
		ObjectLink* pLink = m_pObjectList->m_pFirstLink;
		while (pLink)
		{
			pServerDE->WriteToLoadSaveMessageObject(hWrite, pLink->m_hObject);
			pLink = pLink->m_pNext;
		}
	}
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	KeyFramer::Load
//
//	PURPOSE:	Load the object
//
// ----------------------------------------------------------------------- //

void KeyFramer::Load(HMESSAGEREAD hRead, DDWORD dwLoadFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hRead) return;

	m_fCurTime			= pServerDE->ReadFromMessageFloat(hRead);
	m_hstrObjectName	= pServerDE->ReadFromMessageHString(hRead);
	m_hstrBaseKeyName	= pServerDE->ReadFromMessageHString(hRead);
	m_bStartActive		= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_bLooping			= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_bActive			= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_bFirstUpdate		= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_nNumKeys			= pServerDE->ReadFromMessageByte(hRead);


	// Build the m_pKeys data member...

	KEYNODE* pNode = DNULL;
	for (int i=0; i < m_nNumKeys; i++)
	{
		if (!m_pKeys)
		{
			m_pKeys = new KEYNODE;
			pNode = m_pKeys;
		}
		else if (pNode)
		{
			pNode->pNext = new KEYNODE;
			pNode->pNext->pPrev = pNode;
			pNode = pNode->pNext;
		}

		if (pNode)
		{
			pNode->keyData.Load(hRead, dwLoadFlags);
		}
	}
		
	// Determine the positions of our pointer data members...

	int nCurKeyIndex	= (int) pServerDE->ReadFromMessageFloat(hRead);
	int nPosition1Index	= (int) pServerDE->ReadFromMessageFloat(hRead);
	int nPosition2Index	= (int) pServerDE->ReadFromMessageFloat(hRead);

	KEYNODE* pCurKey = m_pKeys;
	for (i=0; i < m_nNumKeys && pCurKey; i++)
	{
		if (nCurKeyIndex == i)
		{
			m_pCurKey = pCurKey;
		}
		if (nPosition1Index == i)
		{
			m_pPosition1 = pCurKey;
		}
		if (nPosition2Index == i)
		{
			m_pPosition2 = pCurKey;
		}

		pCurKey = pCurKey->pNext;
	}

	// Load the number of objects we're supposed to key frame...

	DBYTE nNumInList = pServerDE->ReadFromMessageByte(hRead);

	// Load the offsets and rotations for each object...

	for (i=0; i < nNumInList; i++)
	{
		pServerDE->ReadFromMessageVector(hRead, &(m_pOffsets[i]));
		pServerDE->ReadFromMessageRotation(hRead, &(m_pRotations[i]));
	}	

	// Load the objects we're supposed to key-frame...

	if (nNumInList > 0)
	{
		if (m_pObjectList)
		{
			pServerDE->RelinquishList(m_pObjectList);
		}

		m_pObjectList = pServerDE->CreateObjectList();

		if (m_pObjectList)
		{
			HOBJECT* hObjectArray = new HOBJECT[nNumInList];

			HOBJECT hObj = DNULL;
			for (int i=0; i < nNumInList; i++)
			{
				pServerDE->ReadFromLoadSaveMessageObject(hRead, &hObj);
				if (hObj)
				{
					hObjectArray[i] = hObj;
				}
				else
				{
					hObjectArray[i] = DNULL;
				}
			}

			// Kind of assy, but need to add the objects in reverse order
			// since AddObjectToList always adds at the front of the list...

			for (i=nNumInList-1; i >= 0; i--)
			{
				if (hObjectArray[i])
				{
					pServerDE->AddObjectToList(m_pObjectList, hObjectArray[i]);
					pServerDE->CreateInterObjectLink(m_hObject, hObjectArray[i]);
				}
			}

			if (hObjectArray)
			{
				delete [] hObjectArray;
			}
		}
	}
}
