// vim:ts=4:sw=4:cindent
/***********************************************************************

Script object to change ambient sounds and light levels based on the
location the player is in. Does also support script calls on location
entry and exit.

***********************************************************************/

#ifndef __LOCATION_SETTINGS__
#define __LOCATION_SETTINGS__

object speaker_zone_ambient 
{
	/**
	* Time period between updates (in seconds)
	**/
	float	m_updatePeriod;
	/**
	* Name of current local sound that's playing
	**/
	string	m_ambientName;
	/**
	* Name of the location player was in when we last checked (and after it's updated, the name of the location the player is in now)
	**/
	string	m_prevLocation;
	/* Entity matching m_prevLocation for calling scripts */
	entity	m_prevLocationEnt;
	/**
	* Name of the previous-previous location, not that the player just left, but the one he left before that (to handle backtracking situations)
	**/
	string prevPrevLocation;	
	/**
	* Name of the location the player just left (holds the value for prevPrevLocation to get later); used after m_prevLocation gets updated
	**/
	string prevLocation;

	float channel_state; 	// used to manage separate snd_channels so the fade-ins/fade-outs 
        			  		// can blend together, each on a separate channel
	float channel1;
	float channel2;			// the names of the snd_channels used with channel_state

	float foipstart_cs1;	// the previous-previous ambient's fade-out start time on channel state 1 (i.e., snd_channel_body)
							// (foip stands for 'fade out in progress')
	float foipstart_cs2;	// the previous-previous ambient's fade-out start time on channel state 2 (i.e., snd_channel_body2)
	float foduration_cs1;	// the fade out duration for the previous-previous ambient on channel_state1 
	float foduration_cs2;	// ditto, for channel_state2
	float cutofftime;		// This is the actual time a new ambient will cut-off a previous-previous ambient fade-out if
							// it's not already out
	float foipstart; 		// the start-time for the current ambient fading-out, to address the reload-volume bug
	float foTimeLeft;		// This is the time left for the troublesome 'prev-prev fade-out' to be complete
	float fodurationFOIP;	// This is a quicker fade out duration for the prev-prev ambient fade out  
	float fidurationFOIP;	// This is a quicker fade in duration of a new incoming ambient (in a '2 fade outs in progress' situation).  
	float backtrack;		// Acts like a boolean for if there's been a backtrack or not.  
	boolean speaker_on; 	// checks the turning on/off of channels so that channel-1 only gets turned off once when needed, first time after a transition (not every round)
	float volumeFactor;		// A placeholder variable for the decibel-decimal conversion math
	float db_factor;		// Pre-computes the conversion factor from ln to log2 for decibel conversion.
	
	boolean	m_invalid_zone;	// to run some actions only the first time we enter an invalid location

	// Tels: save the old value of the tdm_music_volume CVAR so we can track when it changes in a location
	float music_volume;

	/* Tels: Additional members for the ambient light changing: */
	boolean m_al_no_fade;
	float   m_al_fade_time;	
	float   m_al_fade_time_default;

    // When did we enter the new room?
	float   m_al_fade_start;

	/* time in seconds to wait before fade to the new color */
	float   m_al_fade_delay;
	float   m_al_fade_delay_default;

	// When no "ambient_light" is set on the current info_location, the value
	// is determined by the _color spawnarg on the "ambient_world" entity.
	vector  m_al_default_color;

	// the old "base" ambient light in this PVS.
	vector	m_al_base_old;
	// the current "base" ambient lights, as the target color for the fade
	vector	m_al_base_target;
	// the current "base" ambient lights, somewhere between base_old and base_target:
	vector	m_al_base;

	// the current "dynamic" ambient light in this PVS, added to m_al_base:
	vector	m_al_dynamic;
	// the old "dynamic" part
	vector m_al_dynamic_old;

	// default value if info_location doesn't specify anything
	float   m_al_falloff_default;
	float   m_al_dist_scale_default;

	// the constructor for this script object
	void		init();

	// Tels: fix #2417, this function will be called upon idEntity::Restore():
	void		RestoreScriptObject();
	float		getCurrentVolume(entity locEnt);		// compute volume of current playing ambient from "volume" spawnarg and menu cvar
	void		updateLoop();
	void		updateAmbientLight( entity locEnt );						// update the settings on location change
	void		fadeAmbientLight();											// compute new light color based on location and time
	void		fadeAmbientLightColor(vector targetColor, float fadeTime);	// fade straight to the new color
};

void speaker_zone_ambient::init() 
{
	sys.println ("speaker_zone_ambient::init() called");

	m_invalid_zone = false;

	m_updatePeriod = getFloatKey( "update_period" );
	// if == 0?
    if (m_updatePeriod < 0.001)
	{
		m_updatePeriod = 0.2;
	}
	m_ambientName = "";
	m_prevLocation = "";
	m_prevLocationEnt = $null_entity;
	channel_state = 1;
	foipstart_cs1 = 0;
	foipstart_cs2 = 0;
	foipstart = 0;
	foduration_cs1 = 4;
	foduration_cs2 = 4;
	foTimeLeft = 1;
	backtrack = 0;
	speaker_on = 0;
	db_factor = 10 / sys.log(2);
	
	// normalize -40 ..0 => 0 .. 1 (the slider goes from -40 to 0)
	music_volume = (sys.strToFloat( sys.getcvar("tdm_music_volume") ) + 40) / 40;

	/* Tels: Init the ambient light part: */
	m_al_no_fade = false;
	boolean global_no_fade = false;
	if (getBoolKey( "no_ambient_light_fade" ))
	{
		m_al_no_fade = true;
		global_no_fade = true;
		sys.println ("Global no_ambient_light_fade = true, will not fade the main ambient light.");
		// but continue, because the current location might want to fade the light again
	}
	// the default time
	m_al_fade_time_default = getFloatKey( "ambient_light_fade_time" );
	// set a default
	m_al_fade_delay_default = getFloatKey( "ambient_light_fade_delay" );

	m_al_falloff_default = getFloatKey( "ambient_light_falloff" );
	m_al_dist_scale_default = getFloatKey( "ambient_light_dist_scale" );

	// grayman #3132 - the main ambient light doesn't have to be named 'ambient_world',
	// so ask which one has been assigned the job, if any
	entity ambientWorld = sys.getMainAmbientLight();
	if ( ambientWorld != $null_entity )
	{
		// the default color (greebo: use the _color spawnarg, the entity colour might have changed already)
		m_al_default_color = ambientWorld.getVectorKey("_color");
		sys.println("Color read from main ambient light '" + ambientWorld.getName() + "': " + m_al_default_color);
	}
	else
	{
		m_al_default_color = '0 0 0';
		sys.println("No main ambient light, so color assigned: " + m_al_default_color);
	}

	m_al_base = m_al_default_color;
    // the dynamic part starts at 0
	m_al_dynamic_x = 0;
	m_al_dynamic_y = 0;
	m_al_dynamic_z = 0;
	m_al_dynamic_old = m_al_dynamic;

	entity curLoc = $player1.getLocation();
	// skip if we start in an invalid zone
	if (curLoc != $null_entity)
	{
		updateAmbientLight( curLoc );
		if (global_no_fade && m_al_no_fade)
		{
			// we started in a location that has "no_ambient_light_fade", so set the value at least once
			sys.println("Starting location has no_ambient_light_fade true, setting ambient light once to " + m_al_base_target);
			vector al_def = curLoc.getVectorKey("ambient_light");
			fadeAmbientLightColor( al_def, 0 );
		}
	}
	else
	{
		m_invalid_zone = true;
	}

	// now run in a loop:
	updateLoop();	
}
	
float speaker_zone_ambient::getCurrentVolume(entity locEnt)
{
	float fadeInVolume = locEnt.getFloatKey( "volume" ); 	// should be -60..0, default is 0 (positive values possible with caveats)

	if ( fadeInVolume < -60 )
	{
		fadeInVolume = -60;
	}
	/*  Maximum volume level (to deal with previous FMs and misunderstandings) currently commented out
	if ( fadeInVolume > 20 ) 
	{
		fadeInVolume = 0;
	}
	*/
	// conversion decimal to decibels: decibels = (10 * log2(decimal#)), NB sys.log is in base-e, so divide answer by ln(2) = sys.log(2) = db_factor
	// conversion decibel to decimal: decimal = 2^(decibels/10) 
	// conversion method: convert "volume" spawnarg decibel to decimal, multiply by slider cvar decimal, convert result to decibel for fadeSound
	volumeFactor = fadeInVolume/10;
	fadeInVolume = sys.pow( 2, volumeFactor);  // convert spawnarg to decimal
	// tels: read out the current volume value from menu slider (-40 .. 0), normalize it to 0 .. 1 (decimal)
	float music_volume = (sys.strToFloat( sys.getcvar("tdm_music_volume") ) + 40) / 40;
	//sys.println("volume=" + fadeInVolume + " music_volume=" + music_volume);
	// multiply slider value by fadeInVolume spawnarg (decimal) 
	fadeInVolume *= music_volume;
	if ( fadeInVolume < .015625 ) // This is the lowest boundary of volume, to avoid log() going to -infinity
		{
			fadeInVolume = .015625;
		}
	fadeInVolume = sys.log(fadeInVolume) * db_factor; // convert to decibels 
	// sys.println("in RestoreScriptObject: fadeInVolume=" + fadeInVolume);

	return fadeInVolume;
}

void speaker_zone_ambient::RestoreScriptObject() 
{
	// sys.println ("speaker_zone_ambient::RestoreScriptObject() called");

	// we need to wait a bit, or the sound cuts off again
	sys.wait( m_updatePeriod + 0.01);

	entity locEnt = $player1.getLocation();

	// if in invalid location, just exit, the sound will be restored when the
	// player enters a valid zone again:
	if (locEnt == $null_entity)
	{
		return;
	}

	float fadeInVolume = getCurrentVolume( locEnt );
	string newAmbient = locEnt.getKey("ambient");

	sys.println ("Restarting ambient sound " + newAmbient + "'(" + getKey(newAmbient) + ") with volume " + fadeInVolume);

	// fix #2417: fade the incoming sound to the current volume rather quickly
	startSound( newAmbient, channel2, false );  // starts the new ambient on channel 2
	fadeSound( channel2, -60, .001 ); 			// immediately fades-out new ambient on channel 2 to prime for fade-in
	fadeSound( channel2, fadeInVolume, 0.1 );	// finally fade in
}


// player entered new location, so update ambient base color and fade time/delay
void speaker_zone_ambient::updateAmbientLight( entity locEnt )
{
	// sys.println( "Setting ambient in location " + locEnt.getName() );
	//sys.print( "Setting old base to '" + m_al_base + "'\n");

	// store the current base and add the old dynamic so we don't experience a sudden drop/raise (bug #2326)
	m_al_base_old = m_al_base + m_al_dynamic_old;

	// fade to the new location's color
	m_al_base_target = locEnt.getVectorKey("ambient_light");

	if (m_al_base_target == '0 0 0')
	{
		// no value found, use the default
		// sys.println( "No new target, using default color '" + m_al_default_color + "'");
		m_al_base_target = m_al_default_color;
	}

	//sys.print( "Fading from old base '" + m_al_base_old );
	//sys.print( "' plus old dynamic '" + m_al_dynamic_old );
	//sys.print( "' to new base '" + locEnt.getVectorKey("ambient_light") );
	//sys.print( "' plus new dynamic '" + m_al_dynamic + "'.\n");

	// does this room specify a fade_time?
	m_al_fade_time = locEnt.getFloatKey("ambient_light_fade_time");
	// if fade_time == -1, use the default value
	if (m_al_fade_time == -1)
	{
		m_al_fade_time = m_al_fade_time_default;
	}

	m_al_fade_delay = locEnt.getFloatKey( "ambient_light_fade_delay" );
	// use default?
    if (m_al_fade_delay == -1)
	{
		m_al_fade_delay = m_al_fade_delay_default;
	}
    // time that has elapsed going from base_old to base_target
	m_al_fade_start = sys.getTime();

	// See if we need to start or stop fading in this location
	boolean no_fade = false;
	string fade_key = locEnt.getKey("no_ambient_light_fade");
	if (fade_key == "" || fade_key == "-" || fade_key == "-1")
	{
		// not defined, use the global setting
		if (getBoolKey("no_ambient_light_fade"))
		{
			no_fade = true;
		}
	}
	else
	{
		if (fade_key != "0")
		{
			no_fade = true;
		}
	}

	// setting changed in this location?
	if (no_fade && !m_al_no_fade)
	{
		sys.println ("speaker_zone_ambient: no_ambient_light_fade = true, stopping fading ambient light.");
		m_al_no_fade = true;
		// If we stop fading the ambient light, at least set the ambient for the new zone, so we
		// are not stuck with the old values. After this initial set here, the ambient light will be
		// left alone and can be controlled by scripts:
		m_al_base = m_al_base_target;
		// avoid getting stuck at the old dynamic value
		m_al_dynamic = '0 0 0';
		fadeAmbientLightColor( m_al_base, m_updatePeriod * 2 );	// avoid too abrupt changes
	}
	else
	{
		if (!no_fade && m_al_no_fade)
		{
			sys.println ("speaker_zone_ambient: no_ambient_light_fade = false, start fading ambient light again.");
			m_al_no_fade = false;
		}
	}

}
	
// fade the ambient light to the new color (or set it for "fast ambient")
void speaker_zone_ambient::fadeAmbientLightColor(vector targetColor, float fadeTime)
{
	if ( sys.getcvar("tdm_ambient_method") == "0")
	{
		// grayman #3132 - retrieve the main ambient light
		entity ambientWorld = sys.getMainAmbientLight();
		if ( ambientWorld != $null_entity )
		{
			ambientWorld.fadeToLight( targetColor, fadeTime );
		}
	}
    // Tels: if the "fast ambient" method is used, do not fade the light, or
    // 		 it would be turned on and interfere:
    else
	{
		// C++ code has this:
		// gameLocal.globalShaderParms[2] = ambient_color.x * 1.5f;
		// gameLocal.globalShaderParms[3] = ambient_color.y * 1.5f;
		// gameLocal.globalShaderParms[4] = ambient_color.z * 1.5f;
		// so do the same here:
		sys.setShaderParm( 2, targetColor_x * 1.5 );
		sys.setShaderParm( 3, targetColor_y * 1.5 );
		sys.setShaderParm( 4, targetColor_z * 1.5 );
	}
}

// compute the current base ambient light based on: old color, target color, elapsed time
void speaker_zone_ambient::fadeAmbientLight( )
{
	// compute the current light from base + dynamic
	float time_elapsed = sys.getTime() - m_al_fade_start;

	// the fade is over, set target color directly
	if (time_elapsed > m_al_fade_delay + m_al_fade_time)
	{
		//sys.print("Fading time already elapsed, setting '" + m_al_base_target + "' directly.\n");
		m_al_base = m_al_base_target;
	}
	// bug #2327: if fade_delay is zero, do not enter this branch
	else if (time_elapsed <= m_al_fade_delay && m_al_fade_delay > 0)
	{
		//sys.print("Fading time not yet started, setting '" + m_al_base_old + "' directly.\n");
		// the fade delay has not yet started
		m_al_base = m_al_base_old;
		// do not add dynamic twice
		m_al_base = m_al_base - m_al_dynamic;
	}
	// the delay has elapsed, so compute how far in the fade we are
	else
	{
		if (m_al_fade_time > 0)
		{
			float factor = (time_elapsed - m_al_fade_delay) / m_al_fade_time;
			// include the new dynamic, too
			m_al_base = m_al_base_old + ((( m_al_base_target + m_al_dynamic ) - m_al_base_old) * factor);
			// that will get added below, so subtract it
			m_al_base = m_al_base - m_al_dynamic;
			//sys.print("Fading time > 0, setting to '" + m_al_base + "' directly.\n");
		}
		else
		{
			//sys.print("Fading time = 0, setting '" + m_al_base_target + "' directly.\n");
			// no fade time, set directly
			m_al_base = m_al_base_target;
		}
	}

	// bug #2326: if m_al_fade_time is shorter than m_updatePeriod, use it instead
	float f_time = m_updatePeriod;
	if (m_al_fade_time < f_time)
	{
		f_time = m_al_fade_time;
	}
	//sys.print( "Fading light to '" + m_al_base);
	//sys.print( "' plus '" + m_al_dynamic );
	//sys.print( "' in '" + f_time + "' seconds.\n");
	fadeAmbientLightColor( m_al_base + m_al_dynamic, f_time );
/*
	sys.print( "Old ambient light is: '" + m_al_base_old + "'. \n" );
	sys.print( "New target ambient light is: '" + m_al_base_target + "'. \n" );
	sys.print( "Current base ambient light is: '" + m_al_base + "'. \n" );
	sys.print( "Current ambient light is: '" + m_al_base + m_al_dynamic + "'. \n" );
	sys.print( "Elapsed time: '" + time_elapsed + "'. \n" );
	sys.print( "Fade time: " + m_al_fade_time + " seconds.\n");
	sys.print( "Fade delay: " + m_al_fade_delay + " seconds.\n");
*/
}

void speaker_zone_ambient::updateLoop()
{
	while(1)
	{
		entity locEnt;
		locEnt = $player1.getLocation();

		// bug #2326
		if (locEnt == $null_entity)
		{
		 	if (!m_invalid_zone)
		   	{
				// Tels: #3694: when first entering an invalid zone, fade the ambient light back to the default value
				sys.println("Entered invalid zone - missing no info_location entity!\n Setting ambient light:" + m_al_default_color);
			 	m_invalid_zone = true;
				m_al_dynamic = '0 0 0';
				m_al_dynamic_old = '0 0 0';
				m_al_base =	m_al_default_color;
				m_al_base_target = m_al_default_color;
				fadeAmbientLightColor( m_al_default_color, m_updatePeriod * 2 );	// avoid too abrupt changes
				// TODO: fade out music?
			}
			// invalid zone, wait and try again
			wait( m_updatePeriod );
			continue;
		}

		if (m_invalid_zone)
		{
			sys.println("Left invalid zone, entered " + locEnt.getName());
			// TODO: fade music in again if we faded it out?
			m_invalid_zone = false;
		}
		
		// sys.println( "Ambient script check, music_volume " + music_volume + " in location " + locEnt.getName() );

		// now fade the ambient light until the next update
		if (!m_al_no_fade)
		{
			// Tels: query here the ambient_light_dynamic factor:
			vector dyn = locEnt.getVectorKey("ambient_light_dynamic");

			if (dyn != '0 0 0')
			{
				// save the old dynamic value for a smoother fade (bug #2327)
				m_al_dynamic_old = m_al_dynamic;

				// parameters: falloff, distance_scaling:
				//  falloff == 0: no falloff with distance
				//  falloff == 0.5: sqrt(linear) falloff	(dist 100 => 1/10)
				//  falloff == 1: linear falloff			(dist 100 => 1/100)
				//  falloff == 2: square falloff			(dist 100 => 1/10000)
				//  distance scaling: factor to scale the distance, can be used to lower/raise distance factor
				//   after the linear or square scaling has been used.
				float falloff;
			   	falloff	= locEnt.getFloatKey("ambient_light_falloff");		// default is 1
				if (falloff == -1)
				{
					falloff = m_al_falloff_default;
				}
				float dynScale;
				dynScale = locEnt.getFloatKey("ambient_light_dist_scale");
				if (dynScale == -1)
				{
					dynScale = m_al_dist_scale_default;
				}
				// adjust the scaling so the mapper can always put 1.0 for every falloff
				//  good looking: approx: sqrt(linear): 0.01, linear: 0.1, square 1.0
				if (falloff == 0.5)
				{
					dynScale /= 100;	// 1.0 => 0.01
				}
				else if (falloff == 1)
				{
					dynScale /= 10;		// 1.0 => 0.1
				}
				vector light_in_pvs = $player1.getLightInPVS( falloff, dynScale );
				m_al_dynamic_x = light_in_pvs_x * dyn_x; 
				m_al_dynamic_y = light_in_pvs_y * dyn_y; 
				m_al_dynamic_z = light_in_pvs_z * dyn_z;

				// cap the value?
				vector cap = locEnt.getVectorKey("ambient_light_dynamic_cap");
				if (cap_x > 0 && m_al_dynamic_x > cap_x ) { m_al_dynamic_x = cap_x; }
				if (cap_y > 0 && m_al_dynamic_y > cap_y ) { m_al_dynamic_y = cap_y; }
				if (cap_z > 0 && m_al_dynamic_z > cap_z ) { m_al_dynamic_z = cap_z; }

				//sys.print( " New dynamic ambient light cap is: '" + cap + "'. \n" );
				// sys.print( " New dynamic ambient light is: '" + m_al_dynamic + "'. \n" );
				//sys.print( " Old dynamic ambient light: '" + m_al_dynamic_old + "'. \n" );
			}
			else // we entered a zone where the dynamic part is zero? avoid getting *stuck* at the old value
			{
				// save the old dynamic value for a smoother fade (bug #2327)
				m_al_dynamic_old = m_al_dynamic;
				// fade it to half the value, over time this will result in 0:
				m_al_dynamic_x = m_al_dynamic_old_x / 2;
				m_al_dynamic_y = m_al_dynamic_old_y / 2;
				m_al_dynamic_z = m_al_dynamic_old_z / 2;
				// sys.print( " dyn = 0 0 0, New dynamic ambient light is: '" + m_al_dynamic + "'. \n" );
			}
		} // end no_ambient_light_fade

		if( locEnt.getName() != m_prevLocation )
		{
			 sys.print( "\nChanged location from '" + m_prevLocation );
			 sys.print( "' to '" + locEnt.getName() + "'.\n" );

			/* Tels: Process call_ spawnargs */

			/* TODO: rate limit the calls when the player runs forth and back? */

			/* The very first time we run, the "call_once_on_entry" will be called
			   for the location the player starts in. We prevent this if "m_prevLocation"
			   is "" (e.g. at map start),so the call happens when the player enters
			   the first zone for the first time. */
			/* on entry */
			string call = locEnt.getKey("call_once_on_entry");
			if (call)
				{
				if (m_prevLocation != "")
					{
					// delete the spawnarg, so we don't process it again
					locEnt.setKey("call_once_on_entry", "");
					// call the function in a sep. thread and pass it the new location
					callGlobalFunction( call, locEnt );
					}
				}
			call = locEnt.getKey("call_on_entry");
			if (call)
				{
				// call the function in a sep. thread and pass it the new location
				callGlobalFunction( call, locEnt );
				}

			/* on exit */
			call = m_prevLocationEnt.getKey("call_once_on_exit");
			if (call)
				{
				// delete the spawnarg, so we don't process it again
				m_prevLocationEnt.setKey("call_once_on_exit", "");
				// call the function in a sep. thread and pass it the old location
				callGlobalFunction( call, m_prevLocationEnt );
				}
			call = m_prevLocationEnt.getKey("call_on_exit");
			if (call)
				{
				// call the function in a sep. thread and pass it the old location
				callGlobalFunction( call, m_prevLocationEnt );
				}

			/* Tels: Update the new ambient base color and fade times/delays, as well as m_al_no_fade */
			updateAmbientLight( locEnt );

			/* Ambient speaker code starts here: */
			prevPrevLocation = prevLocation;
			prevLocation = m_prevLocation; 
			m_prevLocation = locEnt.getName();
			m_prevLocationEnt = locEnt;
			// sys.print( "You've been in " + prevLocation + " and " + prevPrevLocation + ". \n" );

			string newAmbient = locEnt.getKey("ambient");
			if( newAmbient != m_ambientName )
			{ 
				if (channel_state == 1)   // This alternates the channel_state every round, so outgoing sounds are always on channel 1, and incoming always on channel 2
  					{
   						channel1 = SND_CHANNEL_BODY;
   						channel2 = SND_CHANNEL_BODY2;
   						channel_state = 2;
 					}
 				else
  					{
   						channel1 = SND_CHANNEL_BODY2;
   						channel2 = SND_CHANNEL_BODY;
   						channel_state = 1;
  					}
	  
			    // values the mapper enters into location spawnargs 
			    // to control the incoming-outgoing ambient transition
				float fadeOutDelay = locEnt.getFloatKey( "fodelay" );
				float fadeOutDuration = locEnt.getFloatKey( "foduration" );
  				float fadeOutVolume = locEnt.getFloatKey( "fovolume" );
  				float fadeInDelay = locEnt.getFloatKey( "fidelay" );
  				float fadeInDuration = locEnt.getFloatKey( "fiduration" );

  				float fadeInVolume = getCurrentVolume( locEnt );

				fodurationFOIP = locEnt.getFloatKey( "foduration_2foip" );
				fidurationFOIP = locEnt.getFloatKey( "fiduration_2foip" );

	 			sys.wait( fadeOutDelay );  // the mapper can delay the start of the old ambient fade out
    			fadeSound( channel1, fadeOutVolume, fadeOutDuration );  // begins fade-out of the out-going ambient on channel 1
				// sys.println ("Normal fade-out starts now.");

				// This sets the time a "fade-out in progress" starts, to be registered by a 2nd-foip check the *next* time you enter a new
				// zone, not this time (e.g., if you backtrack).  This is why it saves the foip by channel_state, to check the foip start from 2 ambients ago (not the last one).
				foipstart = sys.getTime(); // sets time cannel1 starts fade out; channel1 should turn off when fade out is finished, to fix bug where channel1 is full volume on reload 
				if( channel_state == 1 )  
				{
					foipstart_cs1 = sys.getTime();
					foduration_cs1 = fadeOutDuration;
				}
				else
				{
					foipstart_cs2 = sys.getTime();
					foduration_cs2 = fadeOutDuration;
				}	
			        
				// This is a "fade-out-in-progress" check to see if we need to deal with a previous-previous ambient still fading out before
				// we either cut it off by entering a 3rd location, or re-fade-in it by backtracking.
				cutofftime = sys.getTime();  
				if( channel_state == 1 )
				{
					if( cutofftime < ( foipstart_cs2 + foduration_cs2 ) )  // If the cut-off time (now) is before the fade-out ends, i.e., the prev prev fade out is still going on
					{
						if(m_prevLocation == prevPrevLocation)  // if this location is the same as 2 locations ago (i.e., backtracking)
						{
							foTimeLeft = foduration_cs2 - (cutofftime - foipstart_cs2);
							fadeInDuration = fadeInDuration - foTimeLeft;
							fadeSound( channel2, fadeInVolume, fadeInDuration ); // re-fade-in the (previous-previous) ambient on channel 2 to become the present ambient again.
							backtrack = 1;
							// sys.print( "Re-fading-in backtracked ambient.\n" );
						}
						else   // prev-prev is still foip, not backtrack, so this must be a 3rd location the player managed to run into quickly							
						{
							fadeInDuration = fidurationFOIP;
							foTimeLeft = foduration_cs2 - (cutofftime - foipstart_cs2);
							if( foTimeLeft > fodurationFOIP ) 
							{
								fadeSound( channel2, -60, fodurationFOIP );
								sys.wait( fodurationFOIP );
								// sys.print( "Quickly fading-out prev-prev ambient.\n" );
							}
							else
							{
								sys.wait( foTimeLeft );
								// sys.print( "Waiting out prev-prev ambient's fade-out.\n" );
							}
						}
					}
				}
				else // same thing for channel state 2
				{
					if( cutofftime < ( foipstart_cs1 + foduration_cs1 ) )  // if prev-prev fade out is still going on
					{
						if( m_prevLocation == prevPrevLocation )  // if this location is the same as 2 locations ago (i.e., backtracking)
						{
							foTimeLeft = foduration_cs1 - (cutofftime - foipstart_cs1);
							fadeInDuration = fadeInDuration - foTimeLeft;
							fadeSound( channel2, fadeInVolume, fadeInDuration ); // re-fade-in the (previous-previous) ambient on channel 2 to become the present ambient again.
							backtrack = 1;	
							// sys.print( "Re-fading-in backtracked ambient.\n" );	
						}
						else   // i.e., this is a 3rd location the player managed to run into quickly							
						{
							fadeInDuration = fidurationFOIP;
							foTimeLeft = foduration_cs1 - (cutofftime - foipstart_cs1);
							if( foTimeLeft > fodurationFOIP ) 
							{
								fadeSound( channel2, -60, fodurationFOIP );
								sys.wait( fodurationFOIP );
								// sys.print( "Quickly fading-out prev-prev ambient.\n" );
							}
							else
							{
								sys.wait( foTimeLeft );
								// sys.print( "Waiting out prev-prev ambient's fade-out.\n" );
							}
						}
					}
				}
				if( backtrack == 0 ) // As long as the player hasn't backtracked back into his prev-prev location before the prev-prev ambient has completely faded out (better name is "backtrack_foip")...
				{
					// sys.print( "Turning on new ambient.\n" );
					startSound( newAmbient, channel2, false );  // starts the new ambient on channel 2
					speaker_on = 1; 
     				fadeSound( channel2, -60, .001 ); // immediately fades-out new ambient on channel 2 to prime for fade-in
     				sys.wait( .01 + fadeInDelay ); // gives time for the fade-out to work, plus if the mapper wants to delay the timing of the fade in
     				fadeSound( channel2, fadeInVolume, fadeInDuration ); // begins fade-in of new ambient on channel 2					
				}
				sys.print( "The ambient '" + newAmbient + "' (" + getKey(newAmbient) + ") for location '" + m_prevLocation + "' is now playing.\n");
				m_ambientName = newAmbient;
				backtrack = 0;
			}
		}
		else
		{
			// Tels: the location did not change, so see if the menu_volume changed
			// read out the current volume value from menu slider (0..1.0) and compare it to the last seen value
			// normalize -40 ..0 => 0 .. 1
			float new_music_volume = (sys.strToFloat( sys.getcvar("tdm_music_volume") ) + 40) / 40;
			// conversion decimal to decibels: decibels = (10 * log2(decimal#)), NB sys.log is in base-e, so divide answer by ln(2) = sys.log(2) = db_factor
			// conversion decibel to decimal: decimal = 2^(decibels/10) 
			// conversion method: convert "volume" spawnarg decibel to decimal, multiply by slider cvar decimal, convert result to decibel for fadeSound
			if (music_volume != new_music_volume)
			{
  				float fadeInVolumeNow = locEnt.getFloatKey( "volume" );
				// DEBUG
				// sys.println ("tdm_music_volume changed from " + music_volume + " to " + new_music_volume + " for current ambient volume " + fadeInVolumeNow);
				// save new value
				volumeFactor = (fadeInVolumeNow/10);
				fadeInVolumeNow = sys.pow( 2, volumeFactor );  // convert spawnarg to decimal
				music_volume = new_music_volume;
				// multiply music_volume slider (0..1) by fadeInVolumeNow spawnarg (0..1)
				fadeInVolumeNow *= music_volume;
				volumeFactor = fadeInVolumeNow * 100; // For the console message
				if ( fadeInVolumeNow < .015625 ) // This is the lowest boundary of volume, to avoid log() going to -infinity
					{
						fadeInVolumeNow = .015625;
						volumeFactor = 0; // For the console message
					}
				fadeInVolumeNow = sys.log(fadeInVolumeNow) * db_factor; // convert to decibels
				// yes it changed, so fade to the new volume rather quickly
				fadeSound( channel2, fadeInVolumeNow, 0.01 );
				sys.println ("The ambient volume is now " + fadeInVolumeNow + " decibels (range: -60..0), i.e., " + volumeFactor + "% of full volume.");
			}
			
			// We need to stop channel-1 when it's silent (i.e., faded-out, to stop a bug where it plays full volume on reload)
			// But we need to wait until its fade out is finished (and we're not entering a new location transition)
			cutofftime = sys.getTime();  
			if( cutofftime > (foipstart + fadeOutDuration) && speaker_on )  // If channel1 has finished fading out, and hasn't already been turned off (so speaker_on is still 1/true)
				{
					stopSound( channel1, false );
					// sys.println ("Channel 1 (for out-going ambients) has been turned off.");
					speaker_on = 0; 
				}	
		}

		// now fade the ambient light until the next update
		if (!m_al_no_fade)
	   	{
			fadeAmbientLight();
		}

		wait( m_updatePeriod );
	}
}

#endif //__LOCATION_SETTINGS__

