﻿#include "stdafx.h"

#include "SoundEngine.h"
#include "..\Consoles_App.h"
#include "..\..\MultiplayerLocalPlayer.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
#include "..\..\Minecraft.World\leveldata.h"
#include "..\..\Minecraft.World\mth.h"
#include "..\..\TexturePackRepository.h"
#include "..\..\DLCTexturePack.h"
#include "Common\DLC\DLCAudioFile.h"




// take out Orbis until they are done

#if defined __PS3__
//extern const char* getPS3HomePath();
char SoundEngine::m_szSoundPath[]={"PS3/Sound/"};
char SoundEngine::m_szMusicPath[]={"music/"};
char SoundEngine::m_szRedistName[]={"redist"};

#define USE_SPURS

#ifdef USE_SPURS
#include <cell/spurs.h>
#else
#include <sys/spu_image.h>
#endif

#endif

F32 AILCALLBACK custom_falloff_function (HSAMPLE   S, 
										 F32       distance,
										 F32       rolloff_factor,
										 F32       min_dist,
										 F32       max_dist);

char *SoundEngine::m_szStreamFileA[eStream_Max]=
{
	"calm1",
	"calm2",
	"calm3",
	"hal1",
	"hal2",
	"hal3",
	"hal4",
	"nuance1",
	"nuance2",
	// add the new music tracks
	"creative1",
	"creative2",
	"creative3",
	"creative4",
	"creative5",
	"creative6",
	"menu1",
	"menu2",
	"menu3",
	"menu4",
	"piano1",
	"piano2",
	"piano3",

	// Nether
	"nether1",
	"nether2",
	"nether3",
	"nether4",
	// The End
	"the_end_dragon_alive",
	"the_end_end",
	// CDs
	"11",
	"13",
	"blocks",
	"cat",
	"chirp",
	"far",
	"mall",
	"mellohi",
	"stal",
	"strad",
	"ward",
	"where_are_we_now"
};

/////////////////////////////////////////////
//
//	ErrorCallback
//
/////////////////////////////////////////////
void AILCALL ErrorCallback(S64 i_Id, char const* i_Details)
{
	char *pchLastError=AIL_last_error();

	if(pchLastError[0]!=0)
	{
		app.DebugPrintf("\rErrorCallback Error Category: %s\n", pchLastError);
	}

	if (i_Details)
	{
		app.DebugPrintf("ErrorCallback - Details: %s\n", i_Details);
	}
}


/////////////////////////////////////////////
//
//	init
//
/////////////////////////////////////////////
void SoundEngine::init(Options *pOptions)
{
	app.DebugPrintf("---SoundEngine::init\n");
#ifdef __DISABLE_MILES__
	return;
#endif
#if defined __PS3__
	Register_RIB(BinkADec);
#endif

	char *redistpath;

#if (0 || 0)// || 0 )
	redistpath=AIL_set_redist_directory(m_szRedistName);
#endif

	app.DebugPrintf("---SoundEngine::init - AIL_startup\n");
	S32 ret = AIL_startup();

	int iNumberOfChannels=initAudioHardware(8);

	// Create a driver to render our audio - 44khz, 16 bit,
#if defined(__PS3__)
	//	On the Sony PS3, the driver is always opened in 48 kHz, 32-bit floating point. The only meaningful configurations are MSS_MC_STEREO, MSS_MC_51_DISCRETE, and MSS_MC_71_DISCRETE. 
	m_hDriver = AIL_open_digital_driver( 48000, 16, iNumberOfChannels, AIL_OPEN_DIGITAL_USE_SPU0 );
#else
	m_hDriver = AIL_open_digital_driver(44100, 16, MSS_MC_USE_SYSTEM_CONFIG, 0);
#endif
	if (m_hDriver == 0)
	{
		app.DebugPrintf("Couldn't open digital sound driver. (%s)\n", AIL_last_error());
		AIL_shutdown();
		return;
	}
	app.DebugPrintf("---SoundEngine::init - driver opened\n");


	AIL_set_event_error_callback(ErrorCallback);

	AIL_set_3D_rolloff_factor(m_hDriver,1.0);

	// Create an event system tied to that driver - let Miles choose memory defaults.
	//if (AIL_startup_event_system(m_hDriver, 0, 0, 0) == 0)
	// 4J-PB - Durango complains that the default memory (64k)isn't enough
	// Error: MilesEvent: Out of event system memory (pool passed to event system startup exhausted).
	// AP - increased command buffer from the default 5K to 20K for Vita

	if (AIL_startup_event_system(m_hDriver, 1024*20, 0, 1024*128) == 0)
	{
		app.DebugPrintf("Couldn't init event system (%s).\n", AIL_last_error());
		AIL_close_digital_driver(m_hDriver);
		AIL_shutdown();
		app.DebugPrintf("---SoundEngine::init - AIL_startup_event_system failed\n");
		return;
	}
	char szBankName[255];
#if defined __PS3__
	if(app.GetBootedFromDiscPatch())
	{
		char szTempSoundFilename[255];
		sprintf(szTempSoundFilename,"%s%s",m_szSoundPath, "Minecraft.msscmp" );

		app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) looking for %s\n",szTempSoundFilename);
		sprintf(szBankName,"%s/%s",app.GetBDUsrDirPath(szTempSoundFilename), m_szSoundPath );
		app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) music path - %s\n",szBankName);
	}
	else
	{
		sprintf(szBankName,"%s/%s",getUsrDirPath(), m_szSoundPath );
	}
	
#else
	strcpy((char *)szBankName,m_szSoundPath);
#endif

	strcat((char *)szBankName,"Minecraft.msscmp");

	m_hBank=AIL_add_soundbank(szBankName, 0);

	if(m_hBank == NULL)
	{
		char *Error=AIL_last_error();
		app.DebugPrintf("Couldn't open soundbank: %s (%s)\n", szBankName, Error);
		AIL_close_digital_driver(m_hDriver);
		AIL_shutdown();
		return;
	}

	//#ifdef _DEBUG
	HMSSENUM token = MSS_FIRST;
	char const* Events[1] = {0};
	S32 EventCount = 0;
	while (AIL_enumerate_events(m_hBank, &token, 0, &Events[0]))
	{
		app.DebugPrintf(4,"%d - %s\n", EventCount, Events[0]);

		EventCount++;
	}
	//#endif

	U64 u64Result;
	u64Result=AIL_enqueue_event_by_name("Minecraft/CacheSounds");

	m_MasterMusicVolume=1.0f;
	m_MasterEffectsVolume=1.0f;

	//AIL_set_variable_float(0,"UserEffectVol",1);

	m_bSystemMusicPlaying = false;

	m_openStreamThread = NULL;


}


void SoundEngine::SetStreamingSounds(int iOverworldMin, int iOverWorldMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1)
{
	m_iStream_Overworld_Min=iOverworldMin;
	m_iStream_Overworld_Max=iOverWorldMax;
	m_iStream_Nether_Min=iNetherMin;
	m_iStream_Nether_Max=iNetherMax;
	m_iStream_End_Min=iEndMin;
	m_iStream_End_Max=iEndMax;
	m_iStream_CD_1=iCD1;

	// array to monitor recently played tracks
	if(m_bHeardTrackA)
	{
		delete [] m_bHeardTrackA;
	}
	m_bHeardTrackA = new bool[iEndMax+1];
	memset(m_bHeardTrackA,0,sizeof(bool)*iEndMax+1);
}

// AP - moved to a separate function so it can be called from the mixer callback on Vita
void SoundEngine::updateMiles()
{

	if( m_validListenerCount == 1 )
	{
		for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
		{
			// set the listener as the first player we find
			if( m_ListenerA[i].bValid )
			{
				AIL_set_listener_3D_position(m_hDriver,m_ListenerA[i].vPosition.x,m_ListenerA[i].vPosition.y,-m_ListenerA[i].vPosition.z);  // Flipped sign of z as Miles is expecting left handed coord system
				AIL_set_listener_3D_orientation(m_hDriver,-m_ListenerA[i].vOrientFront.x,m_ListenerA[i].vOrientFront.y,m_ListenerA[i].vOrientFront.z,0,1,0);   // Flipped sign of z as Miles is expecting left handed coord system
				break;
			}
		}
	}
	else
	{
		// 4J-PB - special case for splitscreen
		// the shortest distance between any listener and a sound will be used to play a sound a set distance away down the z axis.
		// The listener position will be set to 0,0,0, and the orientation will be facing down the z axis

		AIL_set_listener_3D_position(m_hDriver,0,0,0);
		AIL_set_listener_3D_orientation(m_hDriver,0,0,1,0,1,0);
	}

	AIL_begin_event_queue_processing();

	// Iterate over the sounds
	S32 StartedCount = 0, CompletedCount = 0, TotalCount = 0;
	HMSSENUM token = MSS_FIRST;
	MILESEVENTSOUNDINFO SoundInfo;
	int Playing = 0;
	while (AIL_enumerate_sound_instances(0, &token, 0, 0, 0, &SoundInfo))
	{
		AUDIO_INFO* game_data= (AUDIO_INFO*)( SoundInfo.UserBuffer );

		if( SoundInfo.Status == MILESEVENT_SOUND_STATUS_PLAYING )
		{
			Playing += 1;
		}

		if ( SoundInfo.Status != MILESEVENT_SOUND_STATUS_COMPLETE )
		{
			// apply the master volume
			// watch for the 'special' volume levels
			bool isThunder = false;
			if( game_data->volume == 10000.0f )
			{
				isThunder = true;
			}
			if(game_data->volume>1) 
			{
				game_data->volume=1;
			}
			AIL_set_sample_volume_levels( SoundInfo.Sample, game_data->volume*m_MasterEffectsVolume, game_data->volume*m_MasterEffectsVolume);

			float distanceScaler = 16.0f;
			switch(SoundInfo.Status)
			{
			case MILESEVENT_SOUND_STATUS_PENDING:
				// 4J-PB - causes the falloff to be calculated on the PPU instead of the SPU, and seems to resolve our distorted sound issue
				AIL_register_falloff_function_callback(SoundInfo.Sample,&custom_falloff_function);

				if(game_data->bIs3D)
				{			
					AIL_set_sample_is_3D( SoundInfo.Sample, 1 );

					int iSound = game_data->iSound - eSFX_MAX;
					switch(iSound)
					{
						// Is this the Dragon?
						case eSoundType_MOB_ENDERDRAGON_GROWL:
						case eSoundType_MOB_ENDERDRAGON_MOVE:
						case eSoundType_MOB_ENDERDRAGON_END:
						case eSoundType_MOB_ENDERDRAGON_HIT:
							distanceScaler=100.0f;
							break;
						case eSoundType_MOB_GHAST_MOAN:
						case eSoundType_MOB_GHAST_SCREAM:
						case eSoundType_MOB_GHAST_DEATH:
						case eSoundType_MOB_GHAST_CHARGE:
						case eSoundType_MOB_GHAST_FIREBALL:
							distanceScaler=30.0f;
							break;
					}

					// Set a special distance scaler for thunder, which we respond to by having no attenutation
					if( isThunder )
					{
						distanceScaler = 10000.0f;
					}
				}
				else
				{
					AIL_set_sample_is_3D( SoundInfo.Sample, 0 );
				}

				AIL_set_sample_3D_distances(SoundInfo.Sample,distanceScaler,1,0);
				// set the pitch
				if(!game_data->bUseSoundsPitchVal)
				{
					AIL_set_sample_playback_rate_factor(SoundInfo.Sample,game_data->pitch);
				}

				if(game_data->bIs3D)
				{			
					if(m_validListenerCount>1)
					{
						float fClosest=10000.0f;
						int iClosestListener=0;
						float fClosestX=0.0f,fClosestY=0.0f,fClosestZ=0.0f,fDist;
						// need to calculate the distance from the sound to the nearest listener - use Manhattan Distance as the decision
						for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
						{
							if( m_ListenerA[i].bValid )
							{
								float x,y,z;

								x=fabs(m_ListenerA[i].vPosition.x-game_data->x);
								y=fabs(m_ListenerA[i].vPosition.y-game_data->y);
								z=fabs(m_ListenerA[i].vPosition.z-game_data->z);
								fDist=x+y+z;

								if(fDist<fClosest)
								{
									fClosest=fDist;
									fClosestX=x;
									fClosestY=y;
									fClosestZ=z;
									iClosestListener=i;
								}
							}
						}

						// our distances in the world aren't very big, so floats rather than casts to doubles should be fine
						fDist=sqrtf((fClosestX*fClosestX)+(fClosestY*fClosestY)+(fClosestZ*fClosestZ));
						AIL_set_sample_3D_position( SoundInfo.Sample, 0, 0, fDist );

						//app.DebugPrintf("Playing sound %d %f from nearest listener [%d]\n",SoundInfo.EventID,fDist,iClosestListener);
					}
					else
					{
						AIL_set_sample_3D_position( SoundInfo.Sample, game_data->x, game_data->y, -game_data->z );  // Flipped sign of z as Miles is expecting left handed coord system
					}
				}
				break;

			default:
				if(game_data->bIs3D)
				{	
					if(m_validListenerCount>1)
					{
						float fClosest=10000.0f;
						int iClosestListener=0;
						float fClosestX=0.0f,fClosestY=0.0f,fClosestZ=0.0f,fDist;
						// need to calculate the distance from the sound to the nearest listener - use Manhattan Distance as the decision
						for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
						{
							if( m_ListenerA[i].bValid )
							{
								float x,y,z;

								x=fabs(m_ListenerA[i].vPosition.x-game_data->x);
								y=fabs(m_ListenerA[i].vPosition.y-game_data->y);
								z=fabs(m_ListenerA[i].vPosition.z-game_data->z);
								fDist=x+y+z;

								if(fDist<fClosest)
								{
									fClosest=fDist;
									fClosestX=x;
									fClosestY=y;
									fClosestZ=z;
									iClosestListener=i;
								}
							}
						}
						// our distances in the world aren't very big, so floats rather than casts to doubles should be fine
						fDist=sqrtf((fClosestX*fClosestX)+(fClosestY*fClosestY)+(fClosestZ*fClosestZ));
						AIL_set_sample_3D_position( SoundInfo.Sample, 0, 0, fDist );

						//app.DebugPrintf("Playing sound %d %f from nearest listener [%d]\n",SoundInfo.EventID,fDist,iClosestListener);
					}
					else
					{
						AIL_set_sample_3D_position( SoundInfo.Sample, game_data->x, game_data->y, -game_data->z );  // Flipped sign of z as Miles is expecting left handed coord system
					}
				}				
				break;
			}			
		}
	}
	AIL_complete_event_queue_processing();
}

//#define DISTORTION_TEST
#ifdef DISTORTION_TEST
static float fVal=0.0f;
#endif
/////////////////////////////////////////////
//
//	tick
//
/////////////////////////////////////////////


void SoundEngine::tick(shared_ptr<Mob> *players, float a)
{
#ifdef __DISABLE_MILES__
	return;
#endif


	// update the listener positions
	int listenerCount = 0;
#ifdef DISTORTION_TEST
	float fX,fY,fZ;
#endif
	if( players )
	{
		bool bListenerPostionSet=false;
		for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
		{
			if( players[i] != NULL )
			{
				m_ListenerA[i].bValid=true;
				F32 x,y,z;
				x=players[i]->xo + (players[i]->x - players[i]->xo) * a;
				y=players[i]->yo + (players[i]->y - players[i]->yo) * a;
				z=players[i]->zo + (players[i]->z - players[i]->zo) * a;

				float yRot = players[i]->yRotO + (players[i]->yRot - players[i]->yRotO) * a;
				float yCos = (float)cos(-yRot * Mth::RAD_TO_GRAD - PI);
				float ySin = (float)sin(-yRot * Mth::RAD_TO_GRAD - PI);

				// store the listener positions for splitscreen
				m_ListenerA[i].vPosition.x = x;
				m_ListenerA[i].vPosition.y = y;
				m_ListenerA[i].vPosition.z = z;  

				m_ListenerA[i].vOrientFront.x = ySin;
				m_ListenerA[i].vOrientFront.y = 0;
				m_ListenerA[i].vOrientFront.z = yCos;

				listenerCount++;
			}
			else
			{
				m_ListenerA[i].bValid=false;
			}
		}
	}


	// If there were no valid players set, make up a default listener
	if( listenerCount == 0 )
	{
		m_ListenerA[0].vPosition.x = 0;
		m_ListenerA[0].vPosition.y = 0;
		m_ListenerA[0].vPosition.z = 0;
		m_ListenerA[0].vOrientFront.x = 0;
		m_ListenerA[0].vOrientFront.y = 0;
		m_ListenerA[0].vOrientFront.z = 1.0f;
		listenerCount++;
	}
	m_validListenerCount = listenerCount;

	updateMiles();
}

/////////////////////////////////////////////
//
//	SoundEngine
//
/////////////////////////////////////////////
SoundEngine::SoundEngine()
{
	random = new Random();
	m_hStream=0;
	m_StreamState=eMusicStreamState_Idle;
	m_iMusicDelay=0;
	m_validListenerCount=0; 

	m_bHeardTrackA=NULL;

	// Start the streaming music playing some music from the overworld
	SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3,
		eStream_Nether1,eStream_Nether4,
		eStream_end_dragon,eStream_end_end,
		eStream_CD_1);

	m_musicID=getMusicID(LevelData::DIMENSION_OVERWORLD);

	m_StreamingAudioInfo.bIs3D=false;
	m_StreamingAudioInfo.x=0;
	m_StreamingAudioInfo.y=0;
	m_StreamingAudioInfo.z=0;
	m_StreamingAudioInfo.volume=1;
	m_StreamingAudioInfo.pitch=1;

	memset(CurrentSoundsPlaying,0,sizeof(int)*(eSoundType_MAX+eSFX_MAX));
	memset(m_ListenerA,0,sizeof(AUDIO_LISTENER)*XUSER_MAX_COUNT);

}

void SoundEngine::destroy() {}

#ifdef _DEBUG
void SoundEngine::GetSoundName(char *szSoundName,int iSound)
{
	strcpy((char *)szSoundName,"Minecraft/");
	wstring name = wchSoundNames[iSound];
	char *SoundName = (char *)ConvertSoundPathToName(name);
	strcat((char *)szSoundName,SoundName);
}
#endif

/////////////////////////////////////////////
//
//	play
//
/////////////////////////////////////////////
void SoundEngine::play(int iSound, float x, float y, float z, float volume, float pitch)
{
	U8 szSoundName[256];

	if(iSound==-1)
	{
		app.DebugPrintf(6,"PlaySound with sound of -1 !!!!!!!!!!!!!!!\n");
		return;
	}

	// AP removed old counting system. Now relying on Miles' Play Count Limit
	/*	// if we are already playing loads of this sounds ignore this one
	if(CurrentSoundsPlaying[iSound+eSFX_MAX]>MAX_SAME_SOUNDS_PLAYING) 
	{
	// 		wstring name = wchSoundNames[iSound];
	// 		char *SoundName = (char *)ConvertSoundPathToName(name);
	// 		app.DebugPrintf("Too many %s sounds playing!\n",SoundName);
	return;
	}*/

	//if (iSound != eSoundType_MOB_IRONGOLEM_WALK) return;

	// build the name
	strcpy((char *)szSoundName,"Minecraft/");

#ifdef DISTORTION_TEST
	wstring name = wchSoundNames[eSoundType_MOB_ENDERDRAGON_GROWL];
#else
	wstring name = wchSoundNames[iSound];
#endif

	char *SoundName = (char *)ConvertSoundPathToName(name);
	strcat((char *)szSoundName,SoundName);

//	app.DebugPrintf(6,"PlaySound - %d - %s - %s (%f %f %f, vol %f, pitch %f)\n",iSound, SoundName, szSoundName,x,y,z,volume,pitch);

	AUDIO_INFO AudioInfo;
	AudioInfo.x=x;
	AudioInfo.y=y;
	AudioInfo.z=z;
	AudioInfo.volume=volume;
	AudioInfo.pitch=pitch;
	AudioInfo.bIs3D=true;
	AudioInfo.bUseSoundsPitchVal=false;
	AudioInfo.iSound=iSound+eSFX_MAX;
#ifdef _DEBUG
	strncpy(AudioInfo.chName,(char *)szSoundName,64);
#endif

	S32 token = AIL_enqueue_event_start();
	AIL_enqueue_event_buffer(&token, &AudioInfo, sizeof(AUDIO_INFO), 0);
	AIL_enqueue_event_end_named(token, (char *)szSoundName);
}

/////////////////////////////////////////////
//
//	playUI
//
/////////////////////////////////////////////
void SoundEngine::playUI(int iSound, float volume, float pitch) 
{
	U8 szSoundName[256];
	wstring name;
	// we have some game sounds played as UI sounds...
	// Not the best way to do this, but it seems to only be the portal sounds

	if(iSound>=eSFX_MAX)
	{
		// AP removed old counting system. Now relying on Miles' Play Count Limit
		/*		// if we are already playing loads of this sounds ignore this one
		if(CurrentSoundsPlaying[iSound+eSFX_MAX]>MAX_SAME_SOUNDS_PLAYING) return;*/

		// build the name
		strcpy((char *)szSoundName,"Minecraft/");
		name = wchSoundNames[iSound];
	}
	else
	{
		// AP removed old counting system. Now relying on Miles' Play Count Limit
		/*		// if we are already playing loads of this sounds ignore this one
		if(CurrentSoundsPlaying[iSound]>MAX_SAME_SOUNDS_PLAYING) return;*/

		// build the name
		strcpy((char *)szSoundName,"Minecraft/UI/");
		name = wchUISoundNames[iSound];
	}

	char *SoundName = (char *)ConvertSoundPathToName(name);
	strcat((char *)szSoundName,SoundName);
//	app.DebugPrintf("UI: Playing %s, volume %f, pitch %f\n",SoundName,volume,pitch);

	//app.DebugPrintf("PlaySound - %d - %s\n",iSound, SoundName);

	AUDIO_INFO AudioInfo;
	memset(&AudioInfo,0,sizeof(AUDIO_INFO));
	AudioInfo.volume=volume; // will be multiplied by the master volume
	AudioInfo.pitch=pitch;
	AudioInfo.bUseSoundsPitchVal=true;
	if(iSound>=eSFX_MAX)
	{
		AudioInfo.iSound=iSound+eSFX_MAX;
	}
	else
	{
		AudioInfo.iSound=iSound;
	}
#ifdef _DEBUG
	strncpy(AudioInfo.chName,(char *)szSoundName,64);
#endif

	// 4J-PB - not going to stop UI events happening based on the number of currently playing sounds
	S32 token = AIL_enqueue_event_start();
	AIL_enqueue_event_buffer(&token, &AudioInfo, sizeof(AUDIO_INFO), 0);
	AIL_enqueue_event_end_named(token, (char *)szSoundName);
}

/////////////////////////////////////////////
//
//	playStreaming
//
/////////////////////////////////////////////
void SoundEngine::playStreaming(const wstring& name, float x, float y , float z, float volume, float pitch, bool bMusicDelay)
{
	// This function doesn't actually play a streaming sound, just sets states and an id for the music tick to play it
	// Level audio will be played when a play with an empty name comes in
	// CD audio will be played when a named stream comes in

	m_StreamingAudioInfo.x=x;
	m_StreamingAudioInfo.y=y;
	m_StreamingAudioInfo.z=z;
	m_StreamingAudioInfo.volume=volume;
	m_StreamingAudioInfo.pitch=pitch;

	if(m_StreamState==eMusicStreamState_Playing)
	{
		m_StreamState=eMusicStreamState_Stop;
	}
	else if(m_StreamState==eMusicStreamState_Opening)
	{
		m_StreamState=eMusicStreamState_OpeningCancel;
	}

	if(name.empty())
	{
		// music, or stop CD
		m_StreamingAudioInfo.bIs3D=false;

		// we need a music id
		// random delay of up to 3 minutes for music
		m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10;

#ifdef _DEBUG
		m_iMusicDelay=0;
#endif
		Minecraft *pMinecraft=Minecraft::GetInstance();

		bool playerInEnd=false;
		bool playerInNether=false;

		for(unsigned int i=0;i<MAX_LOCAL_PLAYERS;i++)
		{
			if(pMinecraft->localplayers[i]!=NULL)
			{
				if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END)
				{
					playerInEnd=true;
				}
				else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER)
				{
					playerInNether=true;
				}
			}
		}
		if(playerInEnd)
		{
			m_musicID = getMusicID(LevelData::DIMENSION_END);
		}
		else if(playerInNether)
		{
			m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
		}
		else
		{
			m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
		}
	}
	else
	{
		// jukebox
		m_StreamingAudioInfo.bIs3D=true;
		m_musicID=getMusicID(name);
		m_iMusicDelay=0;
	}
}


int SoundEngine::GetRandomishTrack(int iStart,int iEnd)
{
	// 4J-PB - make it more likely that we'll get a track we've not heard for a while, although repeating tracks sometimes is fine

	// if all tracks have been heard, clear the flags
	bool bAllTracksHeard=true;
	int iVal=iStart;
	for(int i=iStart;i<=iEnd;i++)
	{
		if(m_bHeardTrackA[i]==false) 
		{
			bAllTracksHeard=false;
			app.DebugPrintf("Not heard all tracks yet\n");
			break;
		}
	}

	if(bAllTracksHeard)
	{
		app.DebugPrintf("Heard all tracks - resetting the tracking array\n");

		for(int i=iStart;i<=iEnd;i++)
		{
			m_bHeardTrackA[i]=false;
		}
	}

	// trying to get a track we haven't heard, but not too hard		
	for(int i=0;i<=((iEnd-iStart)/2);i++)
	{
		// random->nextInt(1) will always return 0
		iVal=random->nextInt((iEnd-iStart)+1)+iStart;
		if(m_bHeardTrackA[iVal]==false)
		{
			// not heard this
			app.DebugPrintf("(%d) Not heard track %d yet, so playing it now\n",i,iVal);
			m_bHeardTrackA[iVal]=true;
			break;
		}
		else
		{
			app.DebugPrintf("(%d) Skipping track %d already heard it recently\n",i,iVal);
		}
	}

	app.DebugPrintf("Select track %d\n",iVal);
	return iVal;
}
/////////////////////////////////////////////
//
//	getMusicID
//
/////////////////////////////////////////////
int SoundEngine::getMusicID(int iDomain)
{
	int iRandomVal=0;
	Minecraft *pMinecraft=Minecraft::GetInstance();

	// Before the game has started?
	if(pMinecraft==NULL)
	{
		// any track from the overworld
		return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
	}

	if(pMinecraft->skins->isUsingDefaultSkin())
	{
		switch(iDomain)
		{
		case LevelData::DIMENSION_END:
			// the end isn't random - it has different music depending on whether the dragon is alive or not, but we've not added the dead dragon music yet
			return m_iStream_End_Min;
		case LevelData::DIMENSION_NETHER:
			return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max);
			//return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min);
		default: //overworld
			//return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min);
			return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
		}
	}
	else
	{
		// using a texture pack - may have multiple End music tracks
		switch(iDomain)
		{
		case LevelData::DIMENSION_END:
			return GetRandomishTrack(m_iStream_End_Min,m_iStream_End_Max);
		case LevelData::DIMENSION_NETHER:
			//return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min);
			return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max);
		default: //overworld
			//return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min);
			return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max);
		}
	}
}

/////////////////////////////////////////////
//
//	getMusicID
//
/////////////////////////////////////////////
// check what the CD is
int SoundEngine::getMusicID(const wstring& name)
{
	int iCD=0;
	char *SoundName = (char *)ConvertSoundPathToName(name,true);

	// 4J-PB - these will always be the game cds, so use the m_szStreamFileA for this
	for(int i=0;i<12;i++)
	{
		if(strcmp(SoundName,m_szStreamFileA[i+eStream_CD_1])==0)
		{
			iCD=i;
			break;
		}
	}

	// adjust for cd start position on normal or mash-up pack
	return iCD+m_iStream_CD_1;
}

/////////////////////////////////////////////
//
//	getMasterMusicVolume
//
/////////////////////////////////////////////
float SoundEngine::getMasterMusicVolume()
{
	if( m_bSystemMusicPlaying )
	{
		return 0.0f;
	}
	else
	{
		return m_MasterMusicVolume;
	}
}

/////////////////////////////////////////////
//
//	updateMusicVolume
//
/////////////////////////////////////////////
void SoundEngine::updateMusicVolume(float fVal)
{
	m_MasterMusicVolume=fVal;
}

/////////////////////////////////////////////
//
//	updateSystemMusicPlaying
//
/////////////////////////////////////////////
void SoundEngine::updateSystemMusicPlaying(bool isPlaying)
{
	m_bSystemMusicPlaying = isPlaying;
}

/////////////////////////////////////////////
//
//	updateSoundEffectVolume
//
/////////////////////////////////////////////
void SoundEngine::updateSoundEffectVolume(float fVal) 
{
	m_MasterEffectsVolume=fVal;
	//AIL_set_variable_float(0,"UserEffectVol",fVal);
}

void SoundEngine::add(const wstring& name, File *file) {}
void SoundEngine::addMusic(const wstring& name, File *file) {}
void SoundEngine::addStreaming(const wstring& name, File *file) {}
bool SoundEngine::isStreamingWavebankReady() { return true; }

int SoundEngine::OpenStreamThreadProc( void* lpParameter )
{
#ifdef __DISABLE_MILES__
	return 0;
#endif
	SoundEngine *soundEngine = (SoundEngine *)lpParameter;
	soundEngine->m_hStream = AIL_open_stream(soundEngine->m_hDriver,soundEngine->m_szStreamName,0);
	return 0;
}

/////////////////////////////////////////////
//
//	playMusicTick
//
/////////////////////////////////////////////
void SoundEngine::playMusicTick() 
{
// AP - vita will update the music during the mixer callback
	playMusicUpdate();
}

// AP - moved to a separate function so it can be called from the mixer callback on Vita
void SoundEngine::playMusicUpdate() 
{
	//return;
	static bool firstCall = true;
	static float fMusicVol = 0.0f;
	if( firstCall )
	{
		fMusicVol = getMasterMusicVolume();
		firstCall = false;
	}

	switch(m_StreamState)
	{
	case eMusicStreamState_Idle:

		// start a stream playing
		if (m_iMusicDelay > 0)
		{
			m_iMusicDelay--;
			return;
		}

		if(m_musicID!=-1)
		{
			// start playing it


#if defined __PS3__

#ifdef __PS3__
			// 4J-PB - Need to check if we are a patched BD build
			if(app.GetBootedFromDiscPatch())
			{
				sprintf(m_szStreamName,"%s/%s",app.GetBDUsrDirPath(m_szMusicPath), m_szMusicPath );		
				app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) music path - %s",m_szStreamName);
			}
			else
			{
				sprintf(m_szStreamName,"%s/%s",getUsrDirPath(), m_szMusicPath );
			}
#else
			sprintf(m_szStreamName,"%s/%s",getUsrDirPath(), m_szMusicPath );
#endif

#else
			strcpy((char *)m_szStreamName,m_szMusicPath);
#endif
			// are we using a mash-up pack?
			//if(pMinecraft && !pMinecraft->skins->isUsingDefaultSkin() && pMinecraft->skins->getSelected()->hasAudio())
			if(Minecraft::GetInstance()->skins->getSelected()->hasAudio())
			{
				// It's a mash-up - need to use the DLC path for the music
				TexturePack *pTexPack=Minecraft::GetInstance()->skins->getSelected();
				DLCTexturePack *pDLCTexPack=(DLCTexturePack *)pTexPack;
				DLCPack *pack = pDLCTexPack->getDLCInfoParentPack();
				DLCAudioFile *dlcAudioFile = (DLCAudioFile *) pack->getFile(DLCManager::e_DLCType_Audio, 0);

				app.DebugPrintf("Mashup pack \n");

				// build the name

				// if the music ID is beyond the end of the texture pack music files, then it's a CD
				if(m_musicID<m_iStream_CD_1)
				{
					SetIsPlayingStreamingGameMusic(true);
					SetIsPlayingStreamingCDMusic(false);
					m_MusicType=eMusicType_Game;
					m_StreamingAudioInfo.bIs3D=false;
				
					wstring &wstrSoundName=dlcAudioFile->GetSoundName(m_musicID);
					char szName[255];
					wcstombs(szName,wstrSoundName.c_str(),255);

					string strFile="TPACK:\\Data\\" + string(szName) + ".binka";
					std::string mountedPath = StorageManager.GetMountedPath(strFile);
					strcpy(m_szStreamName,mountedPath.c_str());
				}
				else
				{
					SetIsPlayingStreamingGameMusic(false);
					SetIsPlayingStreamingCDMusic(true);
					m_MusicType=eMusicType_CD;
					m_StreamingAudioInfo.bIs3D=true;

					// Need to adjust to index into the cds in the game's m_szStreamFileA
					strcat((char *)m_szStreamName,"cds/");
					strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID-m_iStream_CD_1+eStream_CD_1]);
					strcat((char *)m_szStreamName,".binka");
				}
			}
			else
			{	
				// 4J-PB - if this is a PS3 disc patch, we have to check if the music file is in the patch data
#ifdef __PS3__
				if(app.GetBootedFromDiscPatch() && (m_musicID<m_iStream_CD_1))
				{
					// rebuild the path for the music
					strcpy((char *)m_szStreamName,m_szMusicPath);
					strcat((char *)m_szStreamName,"music/");
					strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID]);
					strcat((char *)m_szStreamName,".binka");

					// check if this is in the patch data
					sprintf(m_szStreamName,"%s/%s",app.GetBDUsrDirPath(m_szStreamName), m_szMusicPath );		
					strcat((char *)m_szStreamName,"music/");
					strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID]);
					strcat((char *)m_szStreamName,".binka");

					SetIsPlayingStreamingGameMusic(true);
					SetIsPlayingStreamingCDMusic(false);
					m_MusicType=eMusicType_Game;
					m_StreamingAudioInfo.bIs3D=false;
				}
				else if(m_musicID<m_iStream_CD_1)
				{
					SetIsPlayingStreamingGameMusic(true);
					SetIsPlayingStreamingCDMusic(false);
					m_MusicType=eMusicType_Game;
					m_StreamingAudioInfo.bIs3D=false;
					// build the name
					strcat((char *)m_szStreamName,"music/");
					strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID]);
					strcat((char *)m_szStreamName,".binka");
				}

				else
				{
					SetIsPlayingStreamingGameMusic(false);
					SetIsPlayingStreamingCDMusic(true);
					m_MusicType=eMusicType_CD;
					m_StreamingAudioInfo.bIs3D=true;
					// build the name
					strcat((char *)m_szStreamName,"cds/");
					strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID]);
					strcat((char *)m_szStreamName,".binka");
				}
#else						
				if(m_musicID<m_iStream_CD_1)
				{
					SetIsPlayingStreamingGameMusic(true);
					SetIsPlayingStreamingCDMusic(false);
					m_MusicType=eMusicType_Game;
					m_StreamingAudioInfo.bIs3D=false;
					// build the name
					strcat((char *)m_szStreamName,"music/");
				}
				else
				{
					SetIsPlayingStreamingGameMusic(false);
					SetIsPlayingStreamingCDMusic(true);
					m_MusicType=eMusicType_CD;
					m_StreamingAudioInfo.bIs3D=true;
					// build the name
					strcat((char *)m_szStreamName,"cds/");
				}
				strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID]);
				strcat((char *)m_szStreamName,".binka");
				
#endif
			}

			// 			wstring name = m_szStreamFileA[m_musicID];
			// 			char *SoundName = (char *)ConvertSoundPathToName(name);
			// 			strcat((char *)szStreamName,SoundName);


			app.DebugPrintf("Starting streaming - %s\n",m_szStreamName);

			// Don't actually open in this thread, as it can block for ~300ms. 
			m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc");
			m_openStreamThread->Run();
			m_StreamState = eMusicStreamState_Opening;
		}
		break;

	case eMusicStreamState_Opening:
		// If the open stream thread is complete, then we are ready to proceed to actually playing
		if( !m_openStreamThread->isRunning() )
		{
			delete m_openStreamThread;
			m_openStreamThread = NULL;

			HSAMPLE hSample = AIL_stream_sample_handle( m_hStream); 

			// 4J-PB - causes the falloff to be calculated on the PPU instead of the SPU, and seems to resolve our distorted sound issue
			AIL_register_falloff_function_callback(hSample,&custom_falloff_function);

			if(m_StreamingAudioInfo.bIs3D)
			{
				AIL_set_sample_3D_distances(hSample,64.0f,1,0);		// Larger distance scaler for music discs
				if(m_validListenerCount>1)
				{
					float fClosest=10000.0f;
					int iClosestListener=0;
					float fClosestX=0.0f,fClosestY=0.0f,fClosestZ=0.0f,fDist;
					// need to calculate the distance from the sound to the nearest listener - use Manhattan Distance as the decision
					for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
					{
						if( m_ListenerA[i].bValid )
						{
							float x,y,z;

							x=fabs(m_ListenerA[i].vPosition.x-m_StreamingAudioInfo.x);
							y=fabs(m_ListenerA[i].vPosition.y-m_StreamingAudioInfo.y);
							z=fabs(m_ListenerA[i].vPosition.z-m_StreamingAudioInfo.z);
							fDist=x+y+z;

							if(fDist<fClosest)
							{
								fClosest=fDist;
								fClosestX=x;
								fClosestY=y;
								fClosestZ=z;
								iClosestListener=i;
							}
						}
					}

					// our distances in the world aren't very big, so floats rather than casts to doubles should be fine
					fDist=sqrtf((fClosestX*fClosestX)+(fClosestY*fClosestY)+(fClosestZ*fClosestZ));
					AIL_set_sample_3D_position( hSample, 0, 0, fDist );
				}
				else
				{
					AIL_set_sample_3D_position( hSample, m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, -m_StreamingAudioInfo.z );  // Flipped sign of z as Miles is expecting left handed coord system
				}
			}
			else
			{
				// clear the 3d flag on the stream after a jukebox finishes and streaming music starts
				AIL_set_sample_is_3D( hSample, 0 );	
			}
			// set the pitch
			app.DebugPrintf("Sample rate:%d\n", AIL_sample_playback_rate(hSample));
			AIL_set_sample_playback_rate_factor(hSample,m_StreamingAudioInfo.pitch);
			// set the volume		
			AIL_set_sample_volume_levels( hSample, m_StreamingAudioInfo.volume*getMasterMusicVolume(), m_StreamingAudioInfo.volume*getMasterMusicVolume());

			AIL_start_stream( m_hStream );

			m_StreamState=eMusicStreamState_Playing;
		}
		break;
	case eMusicStreamState_OpeningCancel:
		if( !m_openStreamThread->isRunning() )
		{
			delete m_openStreamThread;
			m_openStreamThread = NULL;
			m_StreamState = eMusicStreamState_Stop;
		}
		break;
	case eMusicStreamState_Stop:
		// should gradually take the volume down in steps
		AIL_pause_stream(m_hStream,1);
		AIL_close_stream(m_hStream);
		m_hStream=0;
		SetIsPlayingStreamingCDMusic(false);
		SetIsPlayingStreamingGameMusic(false);
		m_StreamState=eMusicStreamState_Idle;
		break;
	case eMusicStreamState_Stopping:
		break;
	case eMusicStreamState_Play:
		break;
	case eMusicStreamState_Playing:
		if(GetIsPlayingStreamingGameMusic())
		{
			//if(m_MusicInfo.pCue!=NULL)
			{
				bool playerInEnd = false;
				bool playerInNether=false;
				Minecraft *pMinecraft = Minecraft::GetInstance();
				for(unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i)
				{
					if(pMinecraft->localplayers[i]!=NULL)
					{
						if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END)
						{
							playerInEnd=true;
						}
						else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER)
						{
							playerInNether=true;
						}
					}
				}

				if(playerInEnd && !GetIsPlayingEndMusic())
				{
					m_StreamState=eMusicStreamState_Stop;

					// Set the end track
					m_musicID = getMusicID(LevelData::DIMENSION_END);
					SetIsPlayingEndMusic(true);
					SetIsPlayingNetherMusic(false);					
				}
				else if(!playerInEnd && GetIsPlayingEndMusic())
				{
					if(playerInNether)
					{
						m_StreamState=eMusicStreamState_Stop;

						// Set the end track
						m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
						SetIsPlayingEndMusic(false);
						SetIsPlayingNetherMusic(true);					
					}
					else
					{
						m_StreamState=eMusicStreamState_Stop;

						// Set the end track
						m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
						SetIsPlayingEndMusic(false);
						SetIsPlayingNetherMusic(false);					
					}
				}
				else if (playerInNether && !GetIsPlayingNetherMusic())
				{
					m_StreamState=eMusicStreamState_Stop;
					// set the Nether track
					m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
					SetIsPlayingNetherMusic(true);
					SetIsPlayingEndMusic(false);
				}
				else if(!playerInNether && GetIsPlayingNetherMusic())
				{
					if(playerInEnd)
					{
						m_StreamState=eMusicStreamState_Stop;
						// set the Nether track
						m_musicID = getMusicID(LevelData::DIMENSION_END);
						SetIsPlayingNetherMusic(false);
						SetIsPlayingEndMusic(true);
					}
					else
					{
						m_StreamState=eMusicStreamState_Stop;
						// set the Nether track
						m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
						SetIsPlayingNetherMusic(false);
						SetIsPlayingEndMusic(false);
					}
				}

				// volume change required?
				if(fMusicVol!=getMasterMusicVolume())
				{
					fMusicVol=getMasterMusicVolume();
					HSAMPLE hSample = AIL_stream_sample_handle( m_hStream); 
					//AIL_set_sample_3D_position( hSample, m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, m_StreamingAudioInfo.z );
					AIL_set_sample_volume_levels( hSample, fMusicVol, fMusicVol);
				}
			}
		}
		else
		{
			// Music disc playing - if it's a 3D stream, then set the position - we don't have any streaming audio in the world that moves, so this isn't
			// required unless we have more than one listener, and are setting the listening position to the origin and setting a fake position
			// for the sound down  the z axis
			if(m_StreamingAudioInfo.bIs3D)
			{
				if(m_validListenerCount>1)
				{
					float fClosest=10000.0f;
					int iClosestListener=0;
					float fClosestX=0.0f,fClosestY=0.0f,fClosestZ=0.0f,fDist;

					// need to calculate the distance from the sound to the nearest listener - use Manhattan Distance as the decision
					for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
					{
						if( m_ListenerA[i].bValid )
						{
							float x,y,z;

							x=fabs(m_ListenerA[i].vPosition.x-m_StreamingAudioInfo.x);
							y=fabs(m_ListenerA[i].vPosition.y-m_StreamingAudioInfo.y);
							z=fabs(m_ListenerA[i].vPosition.z-m_StreamingAudioInfo.z);
							fDist=x+y+z;

							if(fDist<fClosest)
							{
								fClosest=fDist;
								fClosestX=x;
								fClosestY=y;
								fClosestZ=z;
								iClosestListener=i;
							}
						}
					}

					// our distances in the world aren't very big, so floats rather than casts to doubles should be fine
					HSAMPLE hSample = AIL_stream_sample_handle( m_hStream); 
					fDist=sqrtf((fClosestX*fClosestX)+(fClosestY*fClosestY)+(fClosestZ*fClosestZ));
					AIL_set_sample_3D_position( hSample, 0, 0, fDist );
				}
			}
		}

		break;

	case eMusicStreamState_Completed:
		{	
			// random delay of up to 3 minutes for music
			m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10;
			// Check if we have a local player in The Nether or in The End, and play that music if they are
			Minecraft *pMinecraft=Minecraft::GetInstance();
			bool playerInEnd=false;
			bool playerInNether=false;

			for(unsigned int i=0;i<MAX_LOCAL_PLAYERS;i++)
			{
				if(pMinecraft->localplayers[i]!=NULL)
				{
					if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END)
					{
						playerInEnd=true;
					}
					else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER)
					{
						playerInNether=true;
					}
				}
			}
			if(playerInEnd)
			{
				m_musicID = getMusicID(LevelData::DIMENSION_END);
				SetIsPlayingEndMusic(true);
				SetIsPlayingNetherMusic(false);
			}
			else if(playerInNether)
			{
				m_musicID = getMusicID(LevelData::DIMENSION_NETHER);
				SetIsPlayingNetherMusic(true);
				SetIsPlayingEndMusic(false);
			}
			else
			{
				m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD);
				SetIsPlayingNetherMusic(false);
				SetIsPlayingEndMusic(false);
			}

			m_StreamState=eMusicStreamState_Idle;
		}
		break;
	}

	// check the status of the stream - this is for when a track completes rather than is stopped by the user action

	if(m_hStream!=0)
	{
		if(AIL_stream_status(m_hStream)==SMP_DONE ) // SMP_DONE
		{
			AIL_close_stream(m_hStream);
			m_hStream=0;
			SetIsPlayingStreamingCDMusic(false);
			SetIsPlayingStreamingGameMusic(false);

			m_StreamState=eMusicStreamState_Completed;
		}
	}
}


/////////////////////////////////////////////
//
//	ConvertSoundPathToName
//
/////////////////////////////////////////////
char *SoundEngine::ConvertSoundPathToName(const wstring& name, bool bConvertSpaces)
{
	static char buf[256];
	assert(name.length()<256);
	for(unsigned int i = 0; i < name.length(); i++ )
	{
		wchar_t c = name[i];
		if(c=='.') c='/';
		if(bConvertSpaces)
		{
			if(c==' ') c='_';
		}
		buf[i] = (char)c;
	}
	buf[name.length()] = 0;
	return buf;
}



F32 AILCALLBACK custom_falloff_function (HSAMPLE   S, 
										 F32       distance,
										 F32       rolloff_factor,
										 F32       min_dist,
										 F32       max_dist)
{
	F32 result;

	// This is now emulating the linear fall-off function that we used on the Xbox 360. The parameter which is passed as "max_dist" is the only one actually used,
	// and is generally used as CurveDistanceScaler is used on XACT on the Xbox. A special value of 10000.0f is passed for thunder, which has no attenuation

	if( max_dist == 10000.0f )
	{
		return 1.0f;
	}

	result = 1.0f - ( distance / max_dist );
	if( result < 0.0f ) result = 0.0f;
	if( result > 1.0f ) result = 1.0f;

	return result;
}
