// ----------------------------------------------------------------------- //
//
// MODULE  : RotatingWorldModel.cpp
//
// PURPOSE : RotatingWorldModel implementation
//
// CREATED : 10/27/97
//
// ----------------------------------------------------------------------- //

#include "stdafx.h"
#include "RotatingWorldModel.h"
#include "iltserver.h"
#include "ServerUtilities.h"
#include "ClientServerShared.h"
#include "ServerSoundMgr.h"
#include "ObjectMsgs.h"
#include "SurfaceFunctions.h"

#include "keyframer_light.h"

BEGIN_CLASS(RotatingWorldModel)
	ADD_DESTRUCTIBLE_MODEL_AGGREGATE(PF_GROUP1, 0)
	ADD_VISIBLE_FLAG(1, 0)
	ADD_SOLID_FLAG(1, 0)
	ADD_GRAVITY_FLAG(0, 0)
    ADD_BOOLPROP(BlockLight, LTFALSE) // Used by pre-processor
    ADD_BOOLPROP(BoxPhysics, LTTRUE)
    ADD_BOOLPROP(StartOn, LTTRUE)
	ADD_STRINGPROP(SpinUpSound, "")
	ADD_STRINGPROP(BusySound, "")
	ADD_STRINGPROP(SpinDownSound, "")
	ADD_REALPROP_FLAG(SoundRadius, 1000.0f, PF_RADIUS)
	ADD_REALPROP(XAxisRevTime, 0.0f)
	ADD_REALPROP(XAxisSpinUpTime,   0.0f)
	ADD_REALPROP(XAxisSpinDownTime, 0.0f)
    ADD_BOOLPROP(XRotateForward, LTTRUE)
	ADD_REALPROP(YAxisRevTime, 0.0f)
	ADD_REALPROP(YAxisSpinUpTime,   0.0f)
	ADD_REALPROP(YAxisSpinDownTime, 0.0f)
    ADD_BOOLPROP(YRotateForward, LTTRUE)
	ADD_REALPROP(ZAxisRevTime, 0.0f)
	ADD_REALPROP(ZAxisSpinUpTime,   0.0f)
	ADD_REALPROP(ZAxisSpinDownTime, 0.0f)
    ADD_BOOLPROP(ZRotateForward, LTTRUE)
	ADD_STRINGPROP(ShadowLights, "")
	ADD_LONGINTPROP(LightFrames, 1)
	ADD_VECTORPROP_VAL(ShadowAxis, 0.0f, 1.0f, 0.0f)
    ADD_CHROMAKEY_FLAG(LTFALSE, 0)
    ADD_BOOLPROP(AICanShootThrough, LTFALSE)
    ADD_BOOLPROP(AICanSeeThrough, LTFALSE)
END_CLASS_DEFAULT_FLAGS_PLUGIN(RotatingWorldModel, GameBase, NULL, NULL, 0, CRotatingWorldModelPlugin)

#define RWM_UPDATE_DELTA	0.01f
#define TRIGGER_MSG_ON		"ON"
#define TRIGGER_MSG_OFF		"OFF"


void SetupRWMLightAnimName(char *pOutName, const char *pDoorName, uint32 iDoorLight)
{
	sprintf(pOutName, "RWM_%s_LA_%d", pDoorName, iDoorLight);
}

LTRESULT CRotatingWorldModelPlugin::PreHook_Light(
    ILTPreLight *pInterface,
	HPREOBJECT hObject)
{
	GenericProp gProp;
	char shadowLights[256];
	char className[64];
	char rwmName[64];
	char animName[128];
	HPREOBJECT hShadowLight;
	ConParse cParse;
	KLProps theProps;
	DWORD iStep;
	float fPercent;
	int iAxis;
    uint32 iRwmLight;
	PreLightAnimFrameInfo *pFrame;
	PreLightInfo *pLightInfo;
	PreWorldModelInfo *pWorldModelInfo;
	PreLightAnimFrameInfo frames[MAX_RWM_LIGHT_STEPS+1];
	PreLightInfo lightInfos[MAX_RWM_LIGHT_STEPS+1];
	PreWorldModelInfo worldModelInfos[MAX_RWM_LIGHT_STEPS+1];
    uint32 uLightFrames;

	iAxis = 0;
	iRwmLight = 0;

	pInterface->GetPropGeneric(hObject, "ShadowAxis", &gProp);
	if (gProp.m_Vec.x) iAxis = 1;
	else if (gProp.m_Vec.y) iAxis = 2;
	else if (gProp.m_Vec.z) iAxis = 3;

	pInterface->GetPropGeneric(hObject, "Name", &gProp);

	if (!iAxis) {
#ifndef _FINAL
		pInterface->CPrint("No shadow axis set for RotatingWorldModel %s, won't light",
			gProp.m_String);
#endif
		return LT_OK;
	} else {
#ifndef _FINAL
		switch(iAxis)
		{
			case 1:
				pInterface->CPrint("Lighting for %s rotated on x axis.", gProp.m_String);
				break;
			case 2:
				pInterface->CPrint("Lighting for %s rotated on y axis.", gProp.m_String);
				break;
			case 3:
				pInterface->CPrint("Lighting for %s rotated on z axis.", gProp.m_String);
				break;
		}
#endif
	}

	if (pInterface->GetPropGeneric(hObject, "LightFrames", &gProp) == LT_OK)
		uLightFrames = gProp.m_Long;
	else
		uLightFrames = 1;

	// Go through the ShadowLights.
	iRwmLight = 0;
	pInterface->GetPropGeneric(hObject, "ShadowLights", &gProp);
	SAFE_STRCPY(shadowLights, gProp.m_String);
	cParse.Init(shadowLights);
	while(pInterface->Parse(&cParse) == LT_OK)
	{
		if(cParse.m_nArgs == 0)
			continue;

		// Look for the object and make sure it's a keyframer light.
		if(pInterface->FindObject(cParse.m_Args[0], hShadowLight) != LT_OK ||
			pInterface->GetClassName(hShadowLight, className, sizeof(className)) != LT_OK ||
			stricmp(className, KEYFRAMERLIGHT_CLASSNAME) != 0)
		{
			continue;
		}

		// Read in all the light properties.
		ReadKLProps(pInterface, hShadowLight, &theProps);

		// Get some of the door properties.
		pInterface->GetPropGeneric(hObject, "Name", &gProp);
		SAFE_STRCPY(rwmName, gProp.m_String);

		// Hinged doors don't look good unless they have an odd number of frames.
		if((uLightFrames & 1) == 0)
			++uLightFrames;

        uLightFrames = LTCLAMP(uLightFrames, 2, MAX_RWM_LIGHT_STEPS);
		for(iStep=0; iStep < (uLightFrames+1); iStep++)
		{
			pFrame = &frames[iStep];
			pLightInfo = &lightInfos[iStep];
			pWorldModelInfo = &worldModelInfos[iStep];
			fPercent = (float)iStep / (uLightFrames - 1);

            pFrame->m_bSunLight = LTFALSE;

			pFrame->m_Lights = pLightInfo;
			pFrame->m_nLights = 1;

			pFrame->m_WorldModels = pWorldModelInfo;
			pFrame->m_nWorldModels = 1;

			pLightInfo->m_bDirectional = theProps.m_bDirLight;
			pLightInfo->m_FOV = theProps.m_fFOV;
			pLightInfo->m_Radius = theProps.m_fRadius;
			pLightInfo->m_vInnerColor = theProps.m_vInnerColor;
			pLightInfo->m_vOuterColor = theProps.m_vOuterColor;
			pLightInfo->m_vDirection = theProps.m_vForwardVec;
			pLightInfo->m_vPos = theProps.m_vPos;
			pLightInfo->m_BrightScale = theProps.m_fBrightScale;

			SAFE_STRCPY(pWorldModelInfo->m_WorldModelName, rwmName);

			if(iStep == uLightFrames)
			{
				// The last frame is without the world model for when it's destroyed.
                pWorldModelInfo->m_bInvisible = LTTRUE;
			}
			else
			{
				pInterface->GetWorldModelRuntimePos(hObject, pWorldModelInfo->m_Pos);

				switch (iAxis)
				{
					case 1:
						pInterface->GetMathLT()->SetupEuler(pWorldModelInfo->m_Rot,
							MATH_CIRCLE * fPercent, 0.0f, 0.0f);
						break;
					case 2:
						pInterface->GetMathLT()->SetupEuler(pWorldModelInfo->m_Rot,
							0.0f, MATH_CIRCLE * fPercent, 0.0f);
						break;
					case 3:
						pInterface->GetMathLT()->SetupEuler(pWorldModelInfo->m_Rot,
							0.0f, 0.0f, MATH_CIRCLE * fPercent);
						break;
				}
			}
		}

		SetupRWMLightAnimName(animName, rwmName, iRwmLight);

		pInterface->CreateLightAnim(
			animName,
			frames,
			uLightFrames+1,
			theProps.m_bUseShadowMaps);

		++iRwmLight;
	}

	return LT_OK;
}

LTRESULT CRotatingWorldModelPlugin::PreHook_EditStringList(
	const char* szRezPath,
	const char* szPropName,
	char* const * aszStrings, 
    uint32* pcStrings,
    const uint32 cMaxStrings,
    const uint32 cMaxStringLength)
{
	if (m_DestructibleModelPlugin.PreHook_EditStringList(szRezPath,
		szPropName, aszStrings, pcStrings, cMaxStrings, cMaxStringLength) == LT_OK)
	{
		return LT_OK;
	}

	return LT_UNSUPPORTED;
}

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

RotatingWorldModel::RotatingWorldModel() : GameBase(OT_WORLDMODEL)
{
	AddAggregate(&m_damage);

    m_hstrSpinUpSound   = LTNULL;
    m_hstrBusySound     = LTNULL;
    m_hstrSpinDownSound = LTNULL;
	m_fSoundRadius		= 1000.0f;
    m_sndLastSound      = LTNULL;
    m_bBoxPhysics       = LTTRUE;

	VEC_INIT(m_vVelocity);
	VEC_INIT(m_vSaveVelocity);
	VEC_INIT(m_vSpinUpTime);
	VEC_INIT(m_vSpinDownTime);
	VEC_INIT(m_vSpinTimeLeft);
	VEC_SET(m_vSign, 1.0f, 1.0f, 1.0f);

	m_fLastTime		= 0.0f;
	m_fStartTime	= 0.0f;

	m_eState		= RWM_NORMAL;

	m_fPitch		= 0.0f;
	m_fYaw			= 0.0f;
	m_fRoll			= 0.0f;

	m_hShadowLightsString = NULL;
	m_nLightAnims = 0;
	m_nShadowAxis = 0;
    m_bFirstUpdate = LTTRUE;

	m_bCanShootThrough = LTFALSE;
	m_bCanSeeThrough = LTFALSE;
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::~RotatingWorldModel()
//
//	PURPOSE:	Destroy object
//
// ----------------------------------------------------------------------- //

RotatingWorldModel::~RotatingWorldModel()
{
 	if (m_hstrSpinUpSound)
	{
		g_pLTServer->FreeString(m_hstrSpinUpSound);
	}
	if (m_hstrSpinDownSound)
	{
		g_pLTServer->FreeString(m_hstrSpinDownSound);
	}
	if (m_hstrBusySound)
	{
		g_pLTServer->FreeString(m_hstrBusySound);
	}
	if (m_hShadowLightsString)
	{
		g_pLTServer->FreeString(m_hShadowLightsString);
	}

	if (m_sndLastSound)
	{
		g_pLTServer->KillSound(m_sndLastSound);
	}

	SetLightAnimRemoved();
}


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

uint32 RotatingWorldModel::EngineMessageFn(uint32 messageID, void *pData, LTFLOAT fData)
{
	switch(messageID)
	{
		case MID_UPDATE:
		{
			Update();
			break;
		}

		case MID_PRECREATE:
		{
            uint32 dwRet = GameBase::EngineMessageFn(messageID, pData, fData);

			ObjectCreateStruct* pStruct = (ObjectCreateStruct*)pData;

			if (pStruct)
			{
				if (fData == PRECREATE_WORLDFILE)
				{
					ReadProp(pStruct);
				}

				pStruct->m_ObjectType = OT_WORLDMODEL;
				SAFE_STRCPY(pStruct->m_Filename, pStruct->m_Name);
				pStruct->m_SkinName[0] = '\0';

				pStruct->m_Flags |= FLAG_FULLPOSITIONRES | FLAG_GOTHRUWORLD | FLAG_DONTFOLLOWSTANDING/*| (m_bBoxPhysics ? FLAG_BOXPHYSICS : 0)*/;

				// Don't go through world if gravity is set...

				if (pStruct->m_Flags & FLAG_GRAVITY)
				{
					pStruct->m_Flags &= ~FLAG_GOTHRUWORLD;
				}
			}

			return dwRet;
		}

		case MID_INITIALUPDATE:
		{
			if (fData != INITIALUPDATE_SAVEGAME)
			{
				InitialUpdate();
			}

			CacheFiles();
            m_bFirstUpdate = LTTRUE;
			break;
		}

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

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

		default : break;
	}


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


// --------------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::ObjectMessageFn()
//
//	PURPOSE:	Handler for server object messages.
//
// --------------------------------------------------------------------------- //

uint32 RotatingWorldModel::ObjectMessageFn(HOBJECT hSender, uint32 messageID, HMESSAGEREAD hRead)
{
	switch (messageID)
	{
		case MID_TRIGGER:
		{
			HSTRING hMsg = g_pLTServer->ReadFromMessageHString(hRead);
			HandleTrigger(hSender, hMsg);
			g_pLTServer->FreeString(hMsg);
		}
		break;
	}

	return GameBase::ObjectMessageFn(hSender, messageID, hRead);
}


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

LTBOOL RotatingWorldModel::ReadProp(ObjectCreateStruct *)
{
 	GenericProp gProp;

    if (!g_pLTServer) return LTFALSE;

	g_pLTServer->GetPropBool("BoxPhysics", &m_bBoxPhysics);

    LTFLOAT fRealVal;
	g_pLTServer->GetPropReal("XAxisRevTime", &fRealVal);
	if (fRealVal > 0.0f) m_vVelocity.x = MATH_CIRCLE / fRealVal;

	g_pLTServer->GetPropReal("YAxisRevTime", &fRealVal);
	if (fRealVal > 0.0f) m_vVelocity.y = MATH_CIRCLE / fRealVal;

	g_pLTServer->GetPropReal("ZAxisRevTime", &fRealVal);
	if (fRealVal > 0.0f) m_vVelocity.z = MATH_CIRCLE / fRealVal;

    LTBOOL bVal = LTFALSE;
	g_pLTServer->GetPropBool("XRotateForward", &bVal);
    m_vSign.x = (bVal == LTTRUE) ? 1.0f : -1.0f;

    bVal = LTFALSE;
	g_pLTServer->GetPropBool("YRotateForward", &bVal);
    m_vSign.y = (bVal == LTTRUE) ? 1.0f : -1.0f;

    bVal = LTFALSE;
	g_pLTServer->GetPropBool("ZRotateForward", &bVal);
    m_vSign.z = (bVal == LTTRUE) ? 1.0f : -1.0f;

    bVal = LTFALSE;
	g_pLTServer->GetPropBool("StartOn", &bVal);
	if (!bVal) m_eState = RWM_OFF;

	g_pLTServer->GetPropReal("SoundRadius", &m_fSoundRadius);

	g_pLTServer->GetPropReal("XAxisSpinUpTime", &fRealVal);
	m_vSpinUpTime.x = fRealVal;

	g_pLTServer->GetPropReal("XAxisSpinDownTime", &fRealVal);
	m_vSpinDownTime.x = fRealVal;

	g_pLTServer->GetPropReal("YAxisSpinUpTime", &fRealVal);
	m_vSpinUpTime.y = fRealVal;

	g_pLTServer->GetPropReal("YAxisSpinDownTime", &fRealVal);
	m_vSpinDownTime.y = fRealVal;

	g_pLTServer->GetPropReal("ZAxisSpinUpTime", &fRealVal);
	m_vSpinUpTime.z = fRealVal;

	g_pLTServer->GetPropReal("ZAxisSpinDownTime", &fRealVal);
	m_vSpinDownTime.z = fRealVal;

	char buf[MAX_CS_FILENAME_LEN];
    if (g_pLTServer->GetPropString("SpinUpSound", buf, MAX_CS_FILENAME_LEN) == LT_OK)
	{
		if(buf[0])
			m_hstrSpinUpSound = g_pLTServer->CreateString(buf);
	}

    if (g_pLTServer->GetPropString("BusySound", buf, MAX_CS_FILENAME_LEN) == LT_OK)
	{
		if(buf[0])
			m_hstrBusySound = g_pLTServer->CreateString(buf);
	}

    if (g_pLTServer->GetPropString("SpinDownSound", buf, MAX_CS_FILENAME_LEN) == LT_OK)
	{
		if(buf[0])
			m_hstrSpinDownSound = g_pLTServer->CreateString(buf);
	}

    if (g_pLTServer->GetPropGeneric("LightFrames", &gProp) == LT_OK)
		m_nLightFrames = gProp.m_Long;
	else
		m_nLightFrames = 1;

    if (g_pLTServer->GetPropString("ShadowLights", buf, MAX_CS_FILENAME_LEN) == LT_OK)
	{
		if(buf[0])
	        m_hShadowLightsString = g_pLTServer->CreateString(buf);
	}

	if (g_pLTServer->GetPropGeneric("ShadowAxis",&gProp) == LT_OK)
	{
		if (gProp.m_Vec.x) m_nShadowAxis = 1;
		else if (gProp.m_Vec.y) m_nShadowAxis = 2;
		else if (gProp.m_Vec.z) m_nShadowAxis = 3;
	}
	else
		m_nShadowAxis = 1;

    if (g_pLTServer->GetPropGeneric("AICanShootThrough", &gProp) == LT_OK)
	{
		 m_bCanShootThrough = gProp.m_Bool;
	}

    if (g_pLTServer->GetPropGeneric("AICanSeeThrough", &gProp) == LT_OK)
	{
		 m_bCanSeeThrough = gProp.m_Bool;
	}

	return LTTRUE;
}


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

void RotatingWorldModel::InitialUpdate()
{
    uint32 dwUsrFlgs = g_pLTServer->GetObjectUserFlags(m_hObject);
	g_pLTServer->SetObjectUserFlags(m_hObject, dwUsrFlgs | USRFLG_MOVEABLE);

	if (m_eState != RWM_OFF)
	{
		SetNextUpdate(RWM_UPDATE_DELTA, RWM_UPDATE_DELTA + 1.0f);
		SetNormalRotation();
	}

	// Can't push this guy around.
	m_damage.SetMass(INFINITE_MASS);

	VEC_COPY(m_vSaveVelocity, m_vVelocity);
}

void RotatingWorldModel::FirstUpdate()
{
	ConParse cParse;
	LAInfo info;

	if (!m_bFirstUpdate) return;
	    m_bFirstUpdate = LTFALSE;

	// Init our light animations.
	if(m_hShadowLightsString)
	{
		char *pShadowLights = g_pLTServer->GetStringData(m_hShadowLightsString);
		if (!pShadowLights)
			return;

		const char *pObjectName = g_pLTServer->GetObjectName(m_hObject);

		char animName[256];

		ConParse cParse;

		cParse.Init(pShadowLights);
        while(g_pLTServer->Common()->Parse(&cParse) == LT_OK)
		{
			SetupRWMLightAnimName(animName, pObjectName, m_nLightAnims);

			m_hLightAnims[m_nLightAnims] = INVALID_LIGHT_ANIM;
            g_pLTServer->GetLightAnimLT()->FindLightAnim(animName, m_hLightAnims[m_nLightAnims]);
			m_nLightAnims++;

			if(m_nLightAnims >= MAX_RWM_LIGHT_ANIMS)
				break;
		}
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::SetNormalRotation()
//
//	PURPOSE:	Set the model to normal rotation state
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::SetNormalRotation()
{
 	m_eState = RWM_NORMAL;

	m_fLastTime = g_pLTServer->GetTime();

    StartSound(m_hstrBusySound, LTTRUE);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::SetOff()
//
//	PURPOSE:	Set the model to off state
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::SetOff()
{
	m_eState = RWM_OFF;

	if (m_sndLastSound)
	{
		g_pLTServer->KillSound(m_sndLastSound);
        m_sndLastSound = LTNULL;
	}
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::SetSpinUp()
//
//	PURPOSE:	Set the model to the spin up state
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::SetSpinUp()
{
	m_eState = RWM_SPINUP;

	m_fStartTime = g_pLTServer->GetTime();

	VEC_COPY(m_vSpinTimeLeft, m_vSpinUpTime);

    StartSound(m_hstrSpinUpSound, LTFALSE);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::SetSpinDown()
//
//	PURPOSE:	Set the model to the spin up state
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::SetSpinDown()
{
 	m_eState = RWM_SPINDOWN;

	m_fStartTime = g_pLTServer->GetTime();

	VEC_COPY(m_vSpinTimeLeft, m_vSpinDownTime);

    StartSound(m_hstrSpinDownSound, LTFALSE);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::Update()
//
//	PURPOSE:	Update the model
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::Update()
{
	// Handle the first update
	if (m_bFirstUpdate)
		FirstUpdate();

    LTFLOAT fUpdateDelta = RWM_UPDATE_DELTA;

	switch (m_eState)
	{
		case RWM_SPINUP:
			UpdateSpinUp();
		break;

		case RWM_SPINDOWN:
			UpdateSpinDown();
		break;

		case RWM_OFF:
			fUpdateDelta = 0.0f;
		break;

		case RWM_NORMAL:
		default:
			UpdateNormalRotation();
		break;
	}

	SetNextUpdate(fUpdateDelta);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::UpdateNormalRotation()
//
//	PURPOSE:	Update normal rotation
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::UpdateNormalRotation()
{
    LTRotation rRot;
	g_pLTServer->GetObjectRotation(m_hObject, &rRot);

    LTFLOAT fTime = g_pLTServer->GetTime();
    LTFLOAT fDeltaTime = fTime - m_fLastTime;

	if (m_vVelocity.x > 0.0f)
	{
		m_fPitch += (m_vSign.x * (m_vVelocity.x * fDeltaTime));
	}

	if (m_vVelocity.y > 0.0f)
	{
		m_fYaw += (m_vSign.y * (m_vVelocity.y * fDeltaTime));
	}

	if (m_vVelocity.z > 0.0f)
	{
		m_fRoll += (m_vSign.z * (m_vVelocity.z * fDeltaTime));
	}

	//Cap the bitz!
	CapRotation();
	
	g_pLTServer->SetupEuler(&rRot, m_fPitch, m_fYaw, m_fRoll);
	g_pLTServer->SetObjectRotation(m_hObject, &rRot);

	if (m_nLightAnims)
	{
        LTFLOAT fPercent, fTemp, fTemp2;
        uint32 nTemp;

		fTemp = 0.0f;

		switch(m_nShadowAxis)
		{
			case 1:
				fTemp = m_fPitch;
				break;
			case 2:
				fTemp = m_fYaw;
				break;
			case 3:
				fTemp = m_fRoll;
				break;
			default:
				fPercent = 0.0f;
				break;
		}

		if (!fTemp)
		{
			SetLightAnimPercent(fPercent);
		}
		else
		{
			/* Black magic to discover what percent off of 'true' we're at! */
			fTemp2 = fTemp / MATH_CIRCLE;
            nTemp = (uint32)fTemp2;
			if (nTemp)
				fTemp2 = fTemp - (MATH_CIRCLE * (float)nTemp);
			else
				fTemp2 = fTemp;

			fPercent = fTemp2 / MATH_CIRCLE;

			SetLightAnimPercent(fPercent);
		}
	}

	m_fLastTime = fTime;
}

void RotatingWorldModel::SetupLoopedLightAnimPosition(LAInfo &info, uint32 nTotalFrames, float fPercent)
{
	float frame0Percent, frame1Percent;

	// Figure out where we are.
	info.m_iFrames[0] = (DWORD)(fPercent * (nTotalFrames - 1));
	info.m_iFrames[1] = info.m_iFrames[0] + 1;
	if(info.m_iFrames[0] >= nTotalFrames)
		info.m_iFrames[0] = 0;

	if(info.m_iFrames[1] >= nTotalFrames)
		info.m_iFrames[1] = 0;

	frame0Percent = (float)info.m_iFrames[0] / (nTotalFrames - 1);
	frame1Percent = (float)info.m_iFrames[1] / (nTotalFrames - 1);

	info.m_fPercentBetween = 0.0f;
	if(frame1Percent - frame0Percent > 0.01f)
		info.m_fPercentBetween = (fPercent - frame0Percent) / (frame1Percent - frame0Percent);
}

void RotatingWorldModel::SetLightAnimPercent(float percent)
{
    uint32 iAnim, nFrames;
	HLIGHTANIM hAnim;
    ILTLightAnim *pLightAnimLT;
	LAInfo info;

    pLightAnimLT = g_pLTServer->GetLightAnimLT();
	for(iAnim=0; iAnim < m_nLightAnims; iAnim++)
	{
		hAnim = m_hLightAnims[iAnim];

		if (hAnim != INVALID_LIGHT_ANIM)
		{
			if(pLightAnimLT->GetLightAnimInfo(hAnim, info) == LT_OK &&
				pLightAnimLT->GetNumFrames(hAnim, nFrames) == LT_OK &&
				nFrames > 0)
			{
				SetupLoopedLightAnimPosition(info, nFrames-1, percent);
				pLightAnimLT->SetLightAnimInfo(hAnim, info);
			}
		}
	}
}

void RotatingWorldModel::SetLightAnimRemoved()
{
    uint32 iAnim, nFrames;
	HLIGHTANIM hAnim;
    ILTLightAnim *pLightAnimLT;
	LAInfo info;


    pLightAnimLT = g_pLTServer->GetLightAnimLT();
	for(iAnim=0; iAnim < m_nLightAnims; iAnim++)
	{
		hAnim = m_hLightAnims[iAnim];

		if (hAnim != INVALID_LIGHT_ANIM)
		{
			if(pLightAnimLT->GetLightAnimInfo(hAnim, info) == LT_OK &&
				pLightAnimLT->GetNumFrames(hAnim, nFrames) == LT_OK &&
				nFrames > 0)
			{
				info.m_iFrames[0] = info.m_iFrames[1] = nFrames - 1;
				info.m_fPercentBetween = 0.0f;
				pLightAnimLT->SetLightAnimInfo(hAnim, info);
			}
		}
	}
}



// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::UpdateSpinUp()
//
//	PURPOSE:	Update spin up
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::UpdateSpinUp()
{
    LTBOOL bXDone = LTFALSE, bYDone = LTFALSE, bZDone = LTFALSE;

	// Calculate current velocity...

	m_vSpinTimeLeft.x -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.x > 0.0f && m_vSpinTimeLeft.x >= 0.0f)
	{
		m_vVelocity.x = m_vSaveVelocity.x * (m_vSpinUpTime.x - m_vSpinTimeLeft.x) / m_vSpinUpTime.x;
	}
	else
	{
		m_vVelocity.x = m_vSaveVelocity.x;
        bXDone = LTTRUE;
	}

	m_vSpinTimeLeft.y -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.y > 0.0f && m_vSpinTimeLeft.y >= 0.0f)
	{
		m_vVelocity.y = m_vSaveVelocity.y * (m_vSpinUpTime.y - m_vSpinTimeLeft.y) / m_vSpinUpTime.y;
	}
	else
	{
		m_vVelocity.y = m_vSaveVelocity.y;
        bYDone = LTTRUE;
	}

	m_vSpinTimeLeft.z -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.z > 0.0f && m_vSpinTimeLeft.z >= 0.0f)
	{
		m_vVelocity.z = m_vSaveVelocity.z * (m_vSpinUpTime.z - m_vSpinTimeLeft.z) / m_vSpinUpTime.z;
	}
	else
	{
		m_vVelocity.z = m_vSaveVelocity.z;
        bZDone = LTTRUE;
	}

	// Call normal update to do the work...

	UpdateNormalRotation();

	if (bXDone && bYDone && bZDone)
	{
		SetNormalRotation();
	}
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::UpdateSpinDown()
//
//	PURPOSE:	Update spin down
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::UpdateSpinDown()
{
    LTBOOL bXDone = LTFALSE, bYDone = LTFALSE, bZDone = LTFALSE;

	// Calculate current velocity...

	m_vSpinTimeLeft.x -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.x > 0.0f && m_vSpinTimeLeft.x >= 0.0f)
	{
		m_vVelocity.x = m_vSaveVelocity.x - (m_vSaveVelocity.x * (m_vSpinDownTime.x - m_vSpinTimeLeft.x) / m_vSpinDownTime.x);
	}
	else
	{
		m_vVelocity.x = 0.0f;
        bXDone = LTTRUE;
	}

	m_vSpinTimeLeft.y -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.y > 0.0f && m_vSpinTimeLeft.y >= 0.0f)
	{
		m_vVelocity.y = m_vSaveVelocity.y - (m_vSaveVelocity.y * (m_vSpinDownTime.y - m_vSpinTimeLeft.y) / m_vSpinDownTime.y);
	}
	else
	{
		m_vVelocity.y = 0.0f;
        bYDone = LTTRUE;
	}

	m_vSpinTimeLeft.z -= g_pLTServer->GetFrameTime();
	if (m_vSaveVelocity.z > 0.0f && m_vSpinTimeLeft.z >= 0.0f)
	{
		m_vVelocity.z = m_vSaveVelocity.z - (m_vSaveVelocity.z * (m_vSpinDownTime.z - m_vSpinTimeLeft.z) / m_vSpinDownTime.z);
	}
	else
	{
		m_vVelocity.z = 0.0f;
        bZDone = LTTRUE;
	}

	// Call normal update to do the work...

	UpdateNormalRotation();

	if (bXDone && bYDone && bZDone)
	{
		SetOff();
	}
}


// --------------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::TriggerMsg()
//
//	PURPOSE:	Handler trigger messages
//
// --------------------------------------------------------------------------- //

void RotatingWorldModel::HandleTrigger(HOBJECT hSender, HSTRING hMsg)
{
	char* pMsg = g_pLTServer->GetStringData(hMsg);
	if (!pMsg || !pMsg[0]) return;

	if (m_eState == RWM_OFF && stricmp(pMsg, TRIGGER_MSG_ON) == 0)
	{
		SetSpinUp();
		SetNextUpdate(RWM_UPDATE_DELTA);
	}
	else if (m_eState == RWM_NORMAL && stricmp(pMsg, TRIGGER_MSG_OFF) == 0)
	{
		SetSpinDown();
		SetNextUpdate(RWM_UPDATE_DELTA);
	}
}

// --------------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::StartSound()
//
//	PURPOSE:	Start the specified sound
//
// --------------------------------------------------------------------------- //

void RotatingWorldModel::StartSound(HSTRING hstrSoundName, LTBOOL bLoop)
{
 	// Stop the last sound if there is one...

	if (m_sndLastSound)
	{
		g_pLTServer->KillSound(m_sndLastSound);
        m_sndLastSound = LTNULL;
	}

	if (!hstrSoundName) return;

	char *pSoundName = g_pLTServer->GetStringData(hstrSoundName);
	if (!pSoundName) return;


    uint32 dwFlags = bLoop ? PLAYSOUND_LOOP | PLAYSOUND_GETHANDLE : 0;

	// Save the handle of the sound (if bLoop)...

    LTVector vPos;
    g_pLTServer->GetObjectPos(m_hObject, &vPos);
 
	m_sndLastSound = g_pServerSoundMgr->PlaySoundFromPos(vPos, pSoundName,
		m_fSoundRadius, SOUNDPRIORITY_MISC_MEDIUM, dwFlags);
}



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

void RotatingWorldModel::Save(HMESSAGEWRITE hWrite, uint32 dwSaveFlags)
{
 	if (!hWrite) return;

	g_pLTServer->WriteToMessageVector(hWrite, &m_vVelocity);
	g_pLTServer->WriteToMessageVector(hWrite, &m_vSaveVelocity);
	g_pLTServer->WriteToMessageVector(hWrite, &m_vSign);
	g_pLTServer->WriteToMessageVector(hWrite, &m_vSpinUpTime);
	g_pLTServer->WriteToMessageVector(hWrite, &m_vSpinDownTime);
	g_pLTServer->WriteToMessageVector(hWrite, &m_vSpinTimeLeft);
	g_pLTServer->WriteToMessageFloat(hWrite, m_fLastTime - g_pLTServer->GetTime());
	g_pLTServer->WriteToMessageFloat(hWrite, m_fStartTime - g_pLTServer->GetTime());
	g_pLTServer->WriteToMessageFloat(hWrite, m_fPitch);
	g_pLTServer->WriteToMessageFloat(hWrite, m_fYaw);
	g_pLTServer->WriteToMessageFloat(hWrite, m_fRoll);
	g_pLTServer->WriteToMessageFloat(hWrite, m_fSoundRadius);
	g_pLTServer->WriteToMessageByte(hWrite, m_eState);
	g_pLTServer->WriteToMessageByte(hWrite, m_bBoxPhysics);
	g_pLTServer->WriteToMessageHString(hWrite, m_hstrBusySound);
	g_pLTServer->WriteToMessageHString(hWrite, m_hstrSpinUpSound);
	g_pLTServer->WriteToMessageHString(hWrite, m_hstrSpinDownSound);
	g_pLTServer->WriteToMessageHString(hWrite, m_hShadowLightsString);
	g_pLTServer->WriteToMessageByte(hWrite, (uint8)m_nShadowAxis);

	g_pLTServer->WriteToMessageByte(hWrite, m_bCanShootThrough);
	g_pLTServer->WriteToMessageByte(hWrite, m_bCanSeeThrough);

}


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

void RotatingWorldModel::Load(HMESSAGEREAD hRead, uint32 dwLoadFlags)
{
	if (!hRead) return;

	g_pLTServer->ReadFromMessageVector(hRead, &m_vVelocity);
	g_pLTServer->ReadFromMessageVector(hRead, &m_vSaveVelocity);
	g_pLTServer->ReadFromMessageVector(hRead, &m_vSign);
	g_pLTServer->ReadFromMessageVector(hRead, &m_vSpinUpTime);
	g_pLTServer->ReadFromMessageVector(hRead, &m_vSpinDownTime);
	g_pLTServer->ReadFromMessageVector(hRead, &m_vSpinTimeLeft);
	m_fLastTime			= g_pLTServer->ReadFromMessageFloat(hRead) + g_pLTServer->GetTime();
	m_fStartTime		= g_pLTServer->ReadFromMessageFloat(hRead) + g_pLTServer->GetTime();
	m_fPitch			= g_pLTServer->ReadFromMessageFloat(hRead);
	m_fYaw				= g_pLTServer->ReadFromMessageFloat(hRead);
	m_fRoll				= g_pLTServer->ReadFromMessageFloat(hRead);
	m_fSoundRadius		= g_pLTServer->ReadFromMessageFloat(hRead);
	m_eState			= (RWMState) g_pLTServer->ReadFromMessageByte(hRead);
    m_bBoxPhysics       = (LTBOOL) g_pLTServer->ReadFromMessageByte(hRead);
	m_hstrBusySound		= g_pLTServer->ReadFromMessageHString(hRead);
	m_hstrSpinUpSound	= g_pLTServer->ReadFromMessageHString(hRead);
	m_hstrSpinDownSound = g_pLTServer->ReadFromMessageHString(hRead);
	m_hShadowLightsString = g_pLTServer->ReadFromMessageHString(hRead);
	m_nShadowAxis		= (uint32)g_pLTServer->ReadFromMessageByte(hRead);

    m_bCanShootThrough     = (LTBOOL) g_pLTServer->ReadFromMessageByte(hRead);
    m_bCanSeeThrough       = (LTBOOL) g_pLTServer->ReadFromMessageByte(hRead);

	if (m_eState == RWM_NORMAL)
	{
        StartSound(m_hstrBusySound, LTTRUE);
	}
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::CacheFiles
//
//	PURPOSE:	Cache resources associated with this object
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::CacheFiles()
{
     char* pFile = LTNULL;
	if (m_hstrBusySound)
	{
		pFile = g_pLTServer->GetStringData(m_hstrBusySound);
		if (pFile)
		{
			g_pLTServer->CacheFile(FT_SOUND, pFile);
		}
	}

	if (m_hstrSpinUpSound)
	{
		pFile = g_pLTServer->GetStringData(m_hstrSpinUpSound);
		if (pFile)
		{
			g_pLTServer->CacheFile(FT_SOUND, pFile);
		}
	}

	if (m_hstrSpinDownSound)
	{
		pFile = g_pLTServer->GetStringData(m_hstrSpinDownSound);
		if (pFile)
		{
			g_pLTServer->CacheFile(FT_SOUND, pFile);
		}
	}

}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	RotatingWorldModel::CapRotation
//
//	PURPOSE:	Cap the rotation values...
//
// ----------------------------------------------------------------------- //

void RotatingWorldModel::CapRotation()
{
	if(m_fPitch > MATH_CIRCLE)
	{
		do
		{
			m_fPitch -= MATH_CIRCLE;
		}while(m_fPitch > MATH_CIRCLE);
	}
	if(m_fYaw > MATH_CIRCLE)
	{
		do
		{
			m_fYaw -= MATH_CIRCLE;
		}while(m_fYaw > MATH_CIRCLE);
	}
	if(m_fRoll > MATH_CIRCLE)
	{
		do
		{
			m_fRoll -= MATH_CIRCLE;
		}while(m_fRoll > MATH_CIRCLE);
	}

	if(m_fPitch < -MATH_CIRCLE)
	{
		do
		{
			m_fPitch += MATH_CIRCLE;
		}while(m_fPitch < -MATH_CIRCLE);
	}
	if(m_fYaw < -MATH_CIRCLE)
	{
		do
		{
			m_fYaw += MATH_CIRCLE;
		}while(m_fYaw < -MATH_CIRCLE);
	}
	if(m_fRoll < -MATH_CIRCLE)
	{
		do
		{
			m_fRoll += MATH_CIRCLE;
		}while(m_fRoll < -MATH_CIRCLE);
	}
}
