#include "pch.hpp"
//===========================================================================//
// File:	 ForceFeedback.cpp												 //
// Project:  GameOS					                                         //
// Contents: ForceFeedback routines											 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             // 
// 03/09/99  JTR    Created													 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "ForceFeedback.hpp"
#include "ControlManager.hpp"
#include "DirectX.hpp"
#include "Globals.hpp"
#include "Debugger.hpp"
#include "ErrorHandler.hpp"

extern ControlManagerDataStruct g_CMData;
extern HGOSHEAP DefaultHeap;
extern DWORD LogMode;								// 0=Logging, 1=Playback
extern DWORD gLanguageDLL;

HGOSFORCEEFFECT gForceEffects = NULL;
BOOL CALLBACK DIEnumEffectsInFileCallback( LPCDIFILEEFFECT lpDiFileEf, LPVOID pvRef );


void __stdcall gosJoystick_PlayEffect(HGOSFORCEEFFECT effect, DWORD times)
{
	effect->Play(times);
}

void __stdcall gosJoystick_StopEffect(HGOSFORCEEFFECT effect)
{
	effect->Stop();
}

void __stdcall gosJoystick_CreateEffect(HGOSFORCEEFFECT * ff, DWORD joy, gosJoystick_ForceEffect * fe)
{
	*ff = new(DefaultHeap) gosForceEffect(fe, joy);
}

void __stdcall gosJoystick_LoadEffect(HGOSFORCEEFFECT * ff, DWORD joy, const char * file)
{
	*ff = new(DefaultHeap) gosForceEffect(file, joy);
}

bool __stdcall gosJoystick_IsEffectPlaying(HGOSFORCEEFFECT fe)
{
	bool result = false;
	if( 1==LogMode )
	{
//
// Playing back a log file
//
		if( DebuggerActive && InDebugger )
		{
			return 0;
		}
		
		return 0!=ReadBitStream(1,Log_JoystickEffectStatus);
	}
	//
	// Check all of the sub-effects associated with this effect
	//

	if (fe->bStatus == false)
		result =  false;
	else
	if (!(gos_GetMachineInformation( gos_Info_GetApplicationStatus) & 2))
		result = false;
	else
	if (gos_GetElapsedTime() - fe->startTime < fe->longestDuration)
		result = true;
	else
	{
		fe->bStatus = false;
		result = false;
	}
//
// Recording a log file
//		
	if( !InDebugger )
	{
		SPEW(( "GameOS_LogFile", "gosJoystick_IsEffectPlaying()" ));
		WriteBitStream( result, 1, Log_JoystickEffectStatus );
	}
	return result;
}

void __stdcall gosJoystick_UpdateEffect(HGOSFORCEEFFECT ff, gosJoystick_ForceEffect * fe)
{
	gosASSERT(ff);
	ff->Update(fe);
}

void __stdcall gosJoystick_DestroyEffect(HGOSFORCEEFFECT * ff)
{
	if (*ff)
		delete (HGOSFORCEEFFECT) *ff;
	*ff = 0;
}

gosForceEffect::gosForceEffect(gosJoystick_ForceEffect * fe, DWORD joy)
{
	SPEW((GROUP_FORCEFEEDBACK, "gosForceEffect::gosForceEffect()" ));
	fromFile = false;
	bStatus = false;
	dwStick = joy;
	ffEffectData = (FFEffectData *) gos_Malloc(sizeof(FFEffectData));
	ffEffectData->pNextEffect = NULL;
	ffEffectData->diEffectData.dwSamplePeriod = 0;
	ffEffectData->diEffectData.dwStartDelay = 0;
	ffEffectData->diEffect = NULL;
	ffEffectData->diEnvelope.dwSize = sizeof(DIENVELOPE);
	m_eType = fe->eType;
	ffEffectData->diEffectData.dwSize = sizeof(DIEFFECT);
	ffEffectData->diEffectData.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
	if (fe->fDurationSec == -1.0f)
		ffEffectData->diEffectData.dwDuration = INFINITE;
	else
		ffEffectData->diEffectData.dwDuration = (long) (1000000.0f * fe->fDurationSec);

	longestDuration = (double) fe->fDurationSec;

	ffEffectData->diEffectData.dwTriggerButton = DIEB_NOTRIGGER;
	ffEffectData->diEffectData.dwTriggerRepeatInterval = 0;
	ffEffectData->diEffectData.lpEnvelope = NULL;
	ffEffectData->diEffectData.cAxes = 0;
	ffEffectData->diEffectData.rgdwAxes = ffEffectData->rgdwAxes;
	ffEffectData->diEffectData.rglDirection = ffEffectData->rglDirection;
	for(int i=0; i < 12; i++)
	{
		if (fe->bAffectedAxes[i])
		{
			ffEffectData->diEffectData.rgdwAxes[ffEffectData->diEffectData.cAxes] = DIJOFS_X + i*4;
			ffEffectData->diEffectData.cAxes++;
		}
	}
	ffEffectData->diEffectData.rglDirection[0] = (long) fe->fXOrigin;
	ffEffectData->diEffectData.rglDirection[1] = (long) fe->fYOrigin;

	ffEffectData->diEffectData.dwGain = DI_FFNOMINALMAX;
	
	if (fe->lpEnvelope)
	{
		ffEffectData->diEnvelope.dwAttackTime = (LONG) (fe->lpEnvelope->fAttackTime * 1000.0f);
		ffEffectData->diEnvelope.dwAttackLevel = (LONG) (fe->lpEnvelope->fAttackLevel * 10000.0f);
		ffEffectData->diEnvelope.dwFadeTime = (LONG) (fe->lpEnvelope->fFadeTime * 1000.0f);
		ffEffectData->diEnvelope.dwFadeLevel = (LONG) (fe->lpEnvelope->fFadeLevel * 10000.0f);
		ffEffectData->diEffectData.lpEnvelope = &ffEffectData->diEnvelope;
	}
	else
	{
		ffEffectData->diEffectData.lpEnvelope = 0;
	}

	switch(m_eType)
	{
	case gosForce_Constant:
		{
			ffEffectData->diEffectData.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
			ffEffectData->diEffectData.lpvTypeSpecificParams = ffEffectData->diTypeSpecific;
			*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (10000.0f * fe->fMagnitude);
		}
		break;
	case gosForce_Ramp:
		{
			ffEffectData->diEffectData.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
			ffEffectData->diEffectData.lpvTypeSpecificParams = ffEffectData->diTypeSpecific;
			*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (fe->fStart * 10000.0f);
			*((LONG*)ffEffectData->diTypeSpecific+4) = (LONG) (fe->fEnd * 10000.0f);
		}
		break;
	default:
		{
			ffEffectData->diEffectData.cbTypeSpecificParams = sizeof(DIPERIODIC);
			ffEffectData->diEffectData.lpvTypeSpecificParams = ffEffectData->diTypeSpecific;
			*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (10000.0f * fe->fMagnitude);
			*((LONG*)ffEffectData->diTypeSpecific+4) = (LONG) (fe->fOffset * 10000.0f);
			*((LONG*)ffEffectData->diTypeSpecific+8) = fe->lPhase;
			*((LONG*)ffEffectData->diTypeSpecific+12) = (LONG) (fe->fPeriodSec * 1000.0f);
		}
		break;
	}

	if (g_CMData.m_bForceFeedback[joy])
	{
	
		AllowFail = 1;
		switch (m_eType)
		{
			case gosForce_Constant:
				SafeFPU();
				ffEffectData->diGUID = GUID_ConstantForce;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_ConstantForce, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_Ramp:
				SafeFPU();
				ffEffectData->diGUID = GUID_RampForce;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_RampForce, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_Square:
				SafeFPU();
				ffEffectData->diGUID = GUID_Square;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_Square, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_Sine:
				SafeFPU();
				ffEffectData->diGUID = GUID_Sine;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_Sine, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_Triangle:
				SafeFPU();
				ffEffectData->diGUID = GUID_Triangle;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_Triangle, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_SawtoothUp:
				SafeFPU();
				ffEffectData->diGUID = GUID_SawtoothUp;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_SawtoothUp, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
			case gosForce_SawtoothDown:
				SafeFPU();
				ffEffectData->diGUID = GUID_SawtoothDown;
				wCreateEffect( g_CMData.m_lpJoystick7[dwStick], GUID_SawtoothDown, &ffEffectData->diEffectData, &ffEffectData->diEffect, NULL );
				GameOSFPU();
				break;
		}
		AllowFail = 0;
	}
	else
	{
		SPEW((GROUP_FORCEFEEDBACK, "Stick doesn't support force feedback, ignoring create" ));
	}
	// add to master list (the master list is used to re-download when app loses focus 
	// and then gets it back)
	if (gForceEffects == NULL)
	{
		gForceEffects = this;
		pNext = NULL;
	}
	else
	{
		pNext = gForceEffects;
		gForceEffects = this;
	}
}

gosForceEffect::gosForceEffect(const char * file, DWORD joy)
{
	UCHAR * image;
	DWORD size;
	DWORD written;
	fromFile = true;
	char tmpfile[MAX_PATH];
	bStatus = false;
	longestDuration = 0;

	if (g_CMData.m_bForceFeedback[joy])
	{
		ffEffectData = NULL;
//
// Create a temporary file
//
		char PathBuffer[MAX_PATH];
		GetTempPath( MAX_PATH, PathBuffer );
		GetTempFileName( PathBuffer, "gos", 0, tmpfile );
//
// Read in the joystick effect
//
		gos_PushCurrentHeap( DefaultHeap );
		gos_GetFile(file, &image, &size);
		gos_PopCurrentHeap();
		dwStick = joy;
//
// Save effect to the temp file
//
		HANDLE hFile;
		hFile = CreateFile( tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL );
		if( hFile!=INVALID_HANDLE_VALUE )
		{
			WriteFile(hFile, image, size, &written, NULL);
			CloseHandle(hFile);
			gos_Free(image);
//
// Enumerate all effects in the temp file
//
			int val = (int) this;
			g_CMData.m_lpJoystick7[dwStick]->EnumEffectsInFile(tmpfile, DIEnumEffectsInFileCallback, &val, DIFEF_INCLUDENONSTANDARD | DIFEF_MODIFYIFNEEDED );
//
// Delete the temporary file
//
			gos_DeleteFile( tmpfile );
		}
	}
	else
	{
		SPEW((GROUP_FORCEFEEDBACK, "Stick doesn't support force feedback, ignoring create" ));
		ffEffectData = NULL;
	}

	// add to master list (the master list is used to re-download when app loses focus 
	// and then gets it back)
	if (gForceEffects == NULL)
	{
		gForceEffects = this;
		pNext = NULL;
	}
	else
	{
		pNext = gForceEffects;
		gForceEffects = this;
	}
}

void gosForceEffect::ReCreate()
{
	if (ffEffectData->diEffect)
	{

		ffEffectData->diEffect->Unload();
		wRelease(ffEffectData->diEffect);
		ffEffectData->diEffect = NULL;
	}

	FFEffectData * itr = ffEffectData;
	while(itr)
	{
		SafeFPU();
		AllowFail = 1;
	    wCreateEffect( g_CMData.m_lpJoystick7[dwStick], itr->diGUID, &itr->diEffectData, &itr->diEffect, NULL );
		AllowFail = 0;
		GameOSFPU();
		itr = itr->pNextEffect;
	}
}

gosForceEffect::~gosForceEffect()
{
	SPEW((GROUP_FORCEFEEDBACK, "gosForceEffect::~gosForceEffect()" ));

	FFEffectData * itr = ffEffectData;
	FFEffectData * next = NULL;
	while(itr)
	{
		next = itr->pNextEffect;
		if (itr->diEffect)
		{
			wRelease(itr->diEffect);
			gos_Free(itr);
		}
		itr = next;
	}

	// remove this from the master list
	HGOSFORCEEFFECT itr2 = gForceEffects;
	HGOSFORCEEFFECT prev = NULL;
	while(itr2)
	{
		if (itr2 == this)
		{
			if (prev)
			{
				prev->pNext = itr2->pNext;
				break;
			}
			else
			{
				gForceEffects = itr2->pNext;
				break;
			}
		}
		prev = itr2;
		itr2 = itr2->pNext;
	}
}

void gosForceEffect::Play(DWORD times)
{
	SPEW((GROUP_FORCEFEEDBACK, "gosForceEffect::Play()" ));
	if (LogMode == 1)
		return;
	
	if (!(gos_GetMachineInformation( gos_Info_GetApplicationStatus) & 2))
		return;

	if (!g_CMData.m_bForceFeedback[dwStick])
	{
		SPEW((GROUP_FORCEFEEDBACK, "Stick doesn't support force feedback, ignoring playback" ));
		return;
	}

	FFEffectData * itr = ffEffectData;
	while(itr)
	{
		if ( itr->diEffect )
		{
			SafeFPU();
			AllowFail = 1;
			if (itr->diEffect)
				wStart( itr->diEffect, times, 0 );
			AllowFail = 0;
			GameOSFPU();
		}
		itr = itr->pNextEffect;
	}
	bStatus = true;
	startTime = gos_GetElapsedTime();
}

void gosForceEffect::Stop()
{
 	SPEW((GROUP_FORCEFEEDBACK, "gosForceEffect::Stop()" ));
	FFEffectData * itr = ffEffectData;
	while(itr)
	{
		AllowFail = 1;
		if (itr->diEffect)
			wStop(itr->diEffect);
		AllowFail = 0;
		itr = itr->pNextEffect;
	}
	bStatus = false;
	startTime = 0;
}

void gosForceEffect::Update( gosJoystick_ForceEffect * fe )
{
 	SPEW((GROUP_FORCEFEEDBACK, "gosForceEffect::Update()" ));
#ifdef _DEBUG
	if (fromFile)
		STOP(("Effects loaded from disk cannot be modified"));
#endif
	// handle position
	ffEffectData->diEffectData.rglDirection[0] = (long) fe->fXOrigin;
	ffEffectData->diEffectData.rglDirection[1] = (long) fe->fYOrigin;
	
	ffEffectData->diEffectData.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
	for(int i=0; i < 12; i++)
	{
		if (fe->bAffectedAxes[i])
			ffEffectData->diEffectData.rgdwAxes[i] = DIJOFS_X + i*4;
	}
	// handle duration
	if (fe->fDurationSec == -1.0f)
		ffEffectData->diEffectData.dwDuration = INFINITE;
	else
		ffEffectData->diEffectData.dwDuration = (long) (1000000.0f * fe->fDurationSec);

	longestDuration = (double) fe->fDurationSec;

	// handle gain
 	ffEffectData->diEffectData.dwGain = (DWORD) ((fe->fScale) * 10000.0f);

	// handle magnitude
	if (fe->eType == gosForce_Constant)
		*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (10000.0f * fe->fMagnitude);
	else
	if (fe->eType == gosForce_Ramp)
	{
		*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (fe->fStart * 10000.0f);
		*((LONG*)ffEffectData->diTypeSpecific+4) = (LONG) (fe->fEnd * 10000.0f);
	}
	else
	if (fe->eType > gosForce_Ramp)
	{
		// Periodics
		*((LONG*)ffEffectData->diTypeSpecific) = (LONG) (10000.0f * fe->fMagnitude);
		*((LONG*)ffEffectData->diTypeSpecific+4) = (LONG) (fe->fOffset * 10000.0f);
		*((LONG*)ffEffectData->diTypeSpecific+8) = fe->lPhase;
		*((LONG*)ffEffectData->diTypeSpecific+12) = (LONG) (fe->fPeriodSec * 1000000.0f);
	}

	if (fe->lpEnvelope)
	{
		ffEffectData->diEnvelope.dwAttackTime = (LONG) (fe->lpEnvelope->fAttackTime * 1000.0f);
		ffEffectData->diEnvelope.dwAttackLevel = (LONG) (fe->lpEnvelope->fAttackLevel * 10000.0f);
		ffEffectData->diEnvelope.dwFadeTime = (LONG) (fe->lpEnvelope->fFadeTime * 1000.0f);
		ffEffectData->diEnvelope.dwFadeLevel = (LONG) (fe->lpEnvelope->fFadeLevel * 10000.0f);
		ffEffectData->diEffectData.lpEnvelope = &ffEffectData->diEnvelope;
	}
	else
	{
		ffEffectData->diEffectData.lpEnvelope = 0;
	}

	if (ffEffectData->diEffect)
	{
		ffEffectData->diEffect->SetParameters( &ffEffectData->diEffectData, DIEP_DIRECTION | DIEP_DURATION | DIEP_TYPESPECIFICPARAMS );
	}
}

BOOL CALLBACK DIEnumEffectsInFileCallback( LPCDIFILEEFFECT lpDiFileEf, LPVOID theEffect )
{
	//
	// Get a pointer to the gosFoceEffect that's using this effect
	//
	gosForceEffect * lpFE = (gosForceEffect *) *((DWORD*)theEffect);
	
	//
	// Allocate a node to describe the effect
	//
	FFEffectData * newEffect = ( FFEffectData * ) gos_Malloc(sizeof(FFEffectData));
	newEffect->diEffect = NULL;

	//
	// We need to store all information on the effect in case we need to recreate it
	//
	newEffect->diGUID = lpDiFileEf->GuidEffect;
	newEffect->diEffectData.cAxes = lpDiFileEf->lpDiEffect->cAxes;
	newEffect->diEffectData.cbTypeSpecificParams = lpDiFileEf->lpDiEffect->cbTypeSpecificParams;
	newEffect->diEffectData.dwDuration = lpDiFileEf->lpDiEffect->dwDuration;
	newEffect->diEffectData.dwFlags = lpDiFileEf->lpDiEffect->dwFlags;
	newEffect->diEffectData.dwGain = lpDiFileEf->lpDiEffect->dwGain;
	newEffect->diEffectData.dwSamplePeriod = lpDiFileEf->lpDiEffect->dwSamplePeriod;
	newEffect->diEffectData.dwSize = lpDiFileEf->lpDiEffect->dwSize;
	newEffect->diEffectData.dwTriggerButton = lpDiFileEf->lpDiEffect->dwTriggerButton;
	newEffect->diEffectData.dwTriggerRepeatInterval = lpDiFileEf->lpDiEffect->dwTriggerRepeatInterval;
	newEffect->diEffectData.dwStartDelay = lpDiFileEf->lpDiEffect->dwStartDelay;

	if (lpFE->longestDuration < (newEffect->diEffectData.dwDuration + newEffect->diEffectData.dwStartDelay) / 1000000)
		lpFE->longestDuration = (newEffect->diEffectData.dwDuration + newEffect->diEffectData.dwStartDelay) / 1000000;

	if (lpDiFileEf->lpDiEffect->lpEnvelope)
	{
		memcpy(&newEffect->diEnvelope, lpDiFileEf->lpDiEffect->lpEnvelope, sizeof(DIENVELOPE));
		newEffect->diEffectData.lpEnvelope = &newEffect->diEnvelope;
	}
	else
		newEffect->diEffectData.lpEnvelope = 0;

	memcpy(newEffect->rgdwAxes, lpDiFileEf->lpDiEffect->rgdwAxes, sizeof(DWORD) *lpDiFileEf->lpDiEffect->cAxes);
	memcpy(newEffect->rglDirection, lpDiFileEf->lpDiEffect->rglDirection, sizeof(LONG) *lpDiFileEf->lpDiEffect->cAxes);
	newEffect->diEffectData.rgdwAxes = newEffect->rgdwAxes;
	newEffect->diEffectData.rglDirection = newEffect->rglDirection;

	memcpy(newEffect->diTypeSpecific, lpDiFileEf->lpDiEffect->lpvTypeSpecificParams, lpDiFileEf->lpDiEffect->cbTypeSpecificParams );
	newEffect->diEffectData.lpvTypeSpecificParams = newEffect->diTypeSpecific;

	//
	// Maintain the list of "sub-effects"
	//
	if (lpFE->ffEffectData)
	{
		newEffect->pNextEffect = lpFE->ffEffectData;
		lpFE->ffEffectData = newEffect;
	}
	else
	{
		lpFE->ffEffectData = newEffect;
		newEffect->pNextEffect = NULL;
	}

	//
	// And create the effect
	//

	//
	// Create the effect on the proper stick
	//
	SafeFPU();
	AllowFail = 1;
    wCreateEffect( g_CMData.m_lpJoystick7[lpFE->dwStick], lpDiFileEf->GuidEffect, lpDiFileEf->lpDiEffect, &newEffect->diEffect, NULL );
	AllowFail = 0;
	GameOSFPU();

	return DIENUM_CONTINUE;
}
