/*
sequence.c - scripted sequences for CS:CZDS
Copyright (C) 2017 a1batross

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

#include "common.h"
#include "eiface.h"
#include "Sequence.h"

sequenceCommandLine_s g_fileScopeDefaults;
sequenceCommandLine_s g_blockScopeDefaults;
sequenceEntry_s      *g_sequenceList      = NULL;
sentenceGroupEntry_s *g_sentenceGroupList = NULL;
qboolean              g_sequenceParseFileIsGlobal;
unsigned int          g_nonGlobalSentences = 0;
char  g_sequenceParseFileName[MAX_STRING];
int   g_lineNum    = 1;
char *g_scan     = NULL;
char *g_lineScan = NULL;

const sequenceCommandMapping_s g_sequenceCommandMappingTable[] =
{
	{SEQUENCE_COMMAND_PAUSE, "pause", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_TEXT, "text", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_SOUND, "sound", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_FIRETARGETS, "firetargets", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_KILLTARGETS, "killtargets", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_GOSUB, "gosub", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_SENTENCE, "sentence", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_REPEAT, "repeat", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_SETDEFAULTS, "setdefaults", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_MODIFIER, "modifier", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_POSTMODIFIER, "postmodifier", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_COMMAND_NOOP, "noop", SEQUENCE_TYPE_COMMAND},
	{SEQUENCE_MODIFIER_EFFECT, "effect", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_POSITION, "position", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_COLOR, "color", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_COLOR2, "color2", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_FADEIN, "fadein", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_FADEOUT, "fadeout", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_HOLDTIME, "holdtime", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_FXTIME, "fxtime", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_SPEAKER, "speaker", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_LISTENER, "listener", SEQUENCE_TYPE_MODIFIER},
	{SEQUENCE_MODIFIER_TEXTCHANNEL, "channel", SEQUENCE_TYPE_MODIFIER}
};

/*
=============
Sequence_GetCommandEnumForName

=============
*/
sequenceCommandEnum_e Sequence_GetCommandEnumForName( const char *commandName, sequenceCommandType_e type )
{
	int i;

	for( i = 0; i < ARRAYSIZE( g_sequenceCommandMappingTable ); i++ )
	{
		const sequenceCommandMapping_s *mapping = g_sequenceCommandMappingTable + i;

		if( mapping->commandType == type && !Q_stricmp( mapping->commandName, commandName ) )
			return mapping->commandEnum;
	}
	return SEQUENCE_COMMAND_ERROR;
}

/*
=============
Sequence_ResetDefaults

=============
*/
void Sequence_ResetDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source )
{
	if( !source )
	{
		static client_textmessage_t defaultClientMessage =
		{
			0, // effect
			255, 255, 255, 255, // rgba1
			255, 255, 255, 255, // rgba2
			0.5, 0.5, // xy
			0.2, 0.2, // fade-in/out
			1.6, // holdtime
			1.0, // fxtime
			NULL, NULL // pName, pMessage
		};

		destination->clientMessage   = defaultClientMessage;
		destination->textChannel     = 0;
		destination->delay           = 0;
		destination->repeatCount     = 0;
		destination->nextCommandLine = NULL;
		destination->soundFileName   = NULL;
		destination->speakerName     = NULL;
		destination->listenerName    = NULL;
		return;
	}

	destination->clientMessage          = source->clientMessage;
	destination->clientMessage.pName    = NULL;
	destination->clientMessage.pMessage = NULL;
	destination->textChannel            = source->textChannel;
	destination->delay                  = source->delay;
	destination->repeatCount            = source->repeatCount;
	destination->nextCommandLine        = NULL;
	destination->soundFileName          = NULL;

	Z_Free( destination->speakerName );
	destination->speakerName = copystring( source->speakerName );

	Z_Free( destination->listenerName );
	destination->listenerName = copystring( source->listenerName );
}

/*
=============
Sequence_WriteDefaults

=============
*/
void Sequence_WriteDefaults( sequenceCommandLine_s *source, sequenceCommandLine_s *destination )
{
	if( !destination )
		MsgDev( D_ERROR, "Attempt to bake defaults into a non-existant command." );

	if( !source )
		MsgDev( D_ERROR, "Attempt to bake defaults from a non-existant command." );

	if( source->modifierBitField & SEQUENCE_MODIFIER_EFFECT_BIT )
	{
		destination->clientMessage.effect = source->clientMessage.effect;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_POSITION_BIT )
	{
		destination->clientMessage.x = source->clientMessage.x;
		destination->clientMessage.y = source->clientMessage.y;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_COLOR_BIT )
	{
		destination->clientMessage.r1 = source->clientMessage.r1;
		destination->clientMessage.g1 = source->clientMessage.g1;
		destination->clientMessage.b1 = source->clientMessage.b1;
		destination->clientMessage.a1 = source->clientMessage.a1;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_COLOR2_BIT )
	{
		destination->clientMessage.r2 = source->clientMessage.r2;
		destination->clientMessage.g2 = source->clientMessage.g2;
		destination->clientMessage.b2 = source->clientMessage.b2;
		destination->clientMessage.a2 = source->clientMessage.a2;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_FADEIN_BIT )
	{
		destination->clientMessage.fadein = source->clientMessage.fadein;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_FADEOUT_BIT )
	{
		destination->clientMessage.fadeout = source->clientMessage.fadeout;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_HOLDTIME_BIT )
	{
		destination->clientMessage.holdtime = source->clientMessage.holdtime;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_FXTIME_BIT )
	{
		destination->clientMessage.fxtime = source->clientMessage.fxtime;
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_SPEAKER_BIT )
	{
		Z_Free( destination->speakerName );
		destination->speakerName = copystring( source->speakerName );
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_LISTENER_BIT )
	{
		Z_Free( destination->listenerName );
		destination->listenerName = copystring( source->listenerName );
	}

	if( source->modifierBitField & SEQUENCE_MODIFIER_TEXTCHANNEL_BIT )
	{
		destination->textChannel = source->textChannel;
	}
}

/*
=============
Sequence_BakeDefaults

=============
*/
void Sequence_BakeDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source )
{
	char *saveName, *saveMessage;

	if( !destination )
		MsgDev( D_ERROR, "Attempt to bake defaults into a non-existant command." );

	if( !source )
		MsgDev( D_ERROR, "Attempt to bake defaults from a non-existant command." );

	saveName= destination->clientMessage.pName;
	saveMessage = destination->clientMessage.pMessage;

	destination->clientMessage = source->clientMessage;

	destination->clientMessage.pName    = saveName;
	destination->clientMessage.pMessage = saveMessage;

	destination->textChannel            = source->textChannel;

	Z_Free( destination->speakerName );
	destination->speakerName = copystring( source->speakerName );

	Z_Free( destination->listenerName );
	destination->listenerName = copystring( source->listenerName );
}

/*
=============
Sequence_SkipWhitespace

=============
*/
qboolean Sequence_SkipWhitespace( void )
{
	qboolean newLine = false;

	for( ; isspace( *g_scan ); g_scan++ )
	{
		if( *g_scan == '\n' )
		{
			g_lineScan = g_scan + 1;
			g_lineNum++;

			newLine = true;
		}
	}

	return newLine;
}

/*
=============
Sequence_IsNameValueChar

=============
*/
qboolean Sequence_IsNameValueChar( char ch )
{
	if( isalnum( ch ) )
		return true;

	switch( ch )
	{
	case '.':
	case '-':
	case '_':
	case '/':
	case '\\':
		return true;
	}

	return false;
}

/*
=============
Sequence_IsSymbol

=============
*/
qboolean Sequence_IsSymbol( char ch )
{
	switch( ch )
	{
	case '"':
	case '#':
	case '$':
	case '%':
	case ',':
	case '=':
	case '@':
	case '{':
	case '}':
		return true;
	}

	return false;
}

/*
=============
Sequence_GetNameValueString

=============
*/
size_t Sequence_GetNameValueString( char *token, size_t len )
{
	char *p;

	Sequence_SkipWhitespace( );

	if( !Sequence_IsNameValueChar( *g_scan ) )
	{
		if( *g_scan == '#' || *g_scan == '$' )
			MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: cannot have more than one '%c' per line; '%c' must be at the beginning of the line ONLY\n", g_lineNum, g_sequenceParseFileName, *g_scan, *g_scan );
		else
			MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: expected name/value, found illegal character '%c'\n", g_lineNum, g_sequenceParseFileName, *g_scan );
	}

	for( p = token; Sequence_IsNameValueChar( *g_scan ) && len;  p++, g_scan++, len-- )
	{
		*p = *g_scan;
	}

	*p = 0;

	return p - token;
}

/*
=============
Sequence_GetSymbol

=============
*/
char Sequence_GetSymbol( void )
{
	char ch;

	Sequence_SkipWhitespace( );

	ch = *g_scan;

	if( ch )
		g_scan++;

	return ch;
}

/*
=============
Sequence_ValidateNameValueString

=============
*/
void Sequence_ValidateNameValueString( char *token )
{
	char *scan;

	for( scan = token; *scan; scan++ )
	{
		if( !Sequence_IsNameValueChar( *scan ) )
			MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: name/value string \"%s\" had illegal character '%c'\n", g_lineNum, g_sequenceParseFileName, token, *scan );
	}
}

/*
=============
Sequence_GetToken

=============
*/
size_t Sequence_GetToken( char *token, size_t size )
{
	Sequence_SkipWhitespace( );

	if( Sequence_IsNameValueChar( *g_scan ) )
	{
		return Sequence_GetNameValueString( token, size );
	}

	if( !Sequence_IsSymbol( *g_scan ) )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: expected token, found '%c' instead\n", g_lineNum, g_sequenceParseFileName, *g_scan );

	token[0] = *g_scan++;
	token[1] = 0;
	g_scan++;

	return 1; // only one symbol has copied to token
}

/*
=============
Sequence_GetLine

=============
*/
size_t Sequence_GetLine( char *line, int lineMaxLen )
{
	int lineLen;
	char *write = line;

	Sequence_SkipWhitespace( );

	char* read = (char*)Q_strchr( g_scan, '\n' );

	if( !read )
		MsgDev( D_ERROR, "Syntax Error on line %d of %s.seq: expected sentence definition or '}', found End-Of-File!\n", g_lineNum, g_sequenceParseFileName );

	lineLen = read - g_scan;

	if( lineLen >= lineMaxLen )
		MsgDev( D_ERROR, "Syntax Error on line %d of %s.seq: line was too long (was %d chars; max is %d chars)\n", g_lineNum, g_sequenceParseFileName, lineLen, lineMaxLen - 1 );

	Q_strncpy( write, g_scan, lineLen );
	write[lineLen] = 0;
	g_scan = read;

	return lineLen;
}

/*
=============
Sequence_StripComments

=============
*/
void Sequence_StripComments( char *buffer, int *pBufSize )
{
	char *eof   = buffer + *pBufSize;
	char *read  = buffer;
	char *write = buffer;

	for( ; read < eof; )
	{
		if( !*read )
			break;

		if( *read == '/' )
		{
			// skip one line comments //
			if( read[1] == '/' )
			{
				read += 2;

				while( *read )
				{
					if( *read == '\n' )
						break;

					if( *read == '\r' )
						break;

					read++;
				}

				continue;
			}

			// skip multiline /* */
			if( read[1] == '*' )
			{
				read += 2;

				while( *read && read[1] )
				{
					if( *read == '*' && read[1] == '/' )
					{
						read += 2;
						break;
					}

					if( *read == '\n' || *read == '\r' )
						*write++ = *read;

					read++;
				}

				continue;
			}
		}

		*write++ = *read++;
	}

	*write = 0;
}

/*
=============
Sequence_ReadInt

=============
*/
int Sequence_ReadInt( void )
{
	char str[MAX_STRING];

	Sequence_SkipWhitespace( );
	Sequence_GetNameValueString( str, MAX_STRING );

	return Q_atoi( str );
}

/*
=============
Sequence_ReadFloat

=============
*/
float Sequence_ReadFloat( void )
{
	char str[MAX_STRING];

	Sequence_SkipWhitespace( );
	Sequence_GetNameValueString( str, MAX_STRING );

	return Q_atof( str );
}

/*
=============
Sequence_ReadFloat

=============
*/
void Sequence_ReadString( char **dest, char *string, size_t len )
{
	Sequence_SkipWhitespace( );
	Sequence_GetNameValueString( string, len );

	if( dest ) *dest = copystring( string );
}

/*
=============
Sequence_ReadQuotedString

=============
*/
void Sequence_ReadQuotedString( char **dest, char *str, size_t len )
{
	char *write, ch;

	Sequence_SkipWhitespace( );

	ch = Sequence_GetSymbol( );
	if( ch != '\"' )
		MsgDev( D_ERROR, "Parsing error on or before line %d of %s.seq: expected quote (\"), found '%c' instead\n", g_lineNum, g_sequenceParseFileName, ch );

	for( write = str; *g_scan && len; write++, g_scan++, len-- )
	{
		if( *g_scan == '\"' )
			break;

		if( *g_scan == '\n' )
			g_lineNum++;

		*write = *g_scan;
	}

	*write = 0;
	g_scan++;

	if( dest ) *dest = copystring( str );
}

/*
=============
Sequence_ConfirmCarriageReturnOrSymbol

=============
*/
qboolean Sequence_ConfirmCarriageReturnOrSymbol( char symbol )
{
	if( Sequence_SkipWhitespace( ) )
		return true;
	return *g_scan == symbol;
}


/*
=============
Sequence_IsCommandAModifier

=============
*/
qboolean Sequence_IsCommandAModifier( sequenceCommandEnum_e commandEnum )
{
	int i;

	for( i = 0; i < ARRAYSIZE( g_sequenceCommandMappingTable ); i++ )
	{
		if( g_sequenceCommandMappingTable[i].commandEnum == commandEnum )
			return ( g_sequenceCommandMappingTable[i].commandType == SEQUENCE_TYPE_MODIFIER );
	}

	MsgDev( D_ERROR, "Internal error caused by line %d of %s.seq: unknown command enum = %d\n", g_lineNum, g_sequenceParseFileName, commandEnum );
	return false;
}

/*
=============
Sequence_ReadCommandData

=============
*/
void Sequence_ReadCommandData( sequenceCommandEnum_e commandEnum, sequenceCommandLine_s *defaults )
{
	char temp[1024];

	if( commandEnum >= SEQUENCE_MODIFIER_EFFECT || commandEnum <= SEQUENCE_MODIFIER_TEXTCHANNEL )
		defaults->modifierBitField |= BIT( SEQUENCE_MODIFIER_EFFECT - SEQUENCE_COMMAND_NOOP );

	switch( commandEnum )
	{
	case SEQUENCE_COMMAND_PAUSE:
		defaults->delay = Sequence_ReadFloat( );
		break;

	case SEQUENCE_COMMAND_FIRETARGETS:
		Sequence_ReadQuotedString( &defaults->fireTargetNames, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_KILLTARGETS:
		Sequence_ReadQuotedString( &defaults->killTargetNames, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_TEXT:
		Sequence_ReadQuotedString( &defaults->clientMessage.pMessage, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_SOUND:
		Sequence_ReadString( &defaults->soundFileName, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_GOSUB:
		Sequence_ReadString( &defaults->clientMessage.pName, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_SENTENCE:
		Sequence_ReadString( &defaults->sentenceName, temp, sizeof( temp ) );
		break;

	case SEQUENCE_COMMAND_REPEAT:
		defaults->repeatCount = Sequence_ReadInt( );
		break;

	case SEQUENCE_MODIFIER_EFFECT:
		defaults->clientMessage.effect = Sequence_ReadInt( );
		break;

	case SEQUENCE_MODIFIER_POSITION:
		defaults->clientMessage.x = Sequence_ReadFloat( );
		defaults->clientMessage.y = Sequence_ReadFloat( );
		break;

	case SEQUENCE_MODIFIER_COLOR:
		defaults->clientMessage.r1 = Sequence_ReadInt( );
		defaults->clientMessage.g1 = Sequence_ReadInt( );
		defaults->clientMessage.b1 = Sequence_ReadInt( );
		defaults->clientMessage.a1 = 255;
		break;

	case SEQUENCE_MODIFIER_COLOR2:
		defaults->clientMessage.r2 = Sequence_ReadInt( );
		defaults->clientMessage.g2 = Sequence_ReadInt( );
		defaults->clientMessage.b2 = Sequence_ReadInt( );
		defaults->clientMessage.a2 = 255;
		break;

	case SEQUENCE_MODIFIER_FADEIN:
		defaults->clientMessage.fadein = Sequence_ReadFloat( );
		break;

	case SEQUENCE_MODIFIER_FADEOUT:
		defaults->clientMessage.fadeout = Sequence_ReadFloat( );
		break;

	case SEQUENCE_MODIFIER_HOLDTIME:
		defaults->clientMessage.holdtime = Sequence_ReadFloat( );
		break;

	case SEQUENCE_MODIFIER_FXTIME:
		defaults->clientMessage.fxtime = Sequence_ReadFloat( );
		break;

	case SEQUENCE_MODIFIER_SPEAKER:
		Sequence_ReadString( &defaults->speakerName, temp, sizeof( temp ) );
		break;

	case SEQUENCE_MODIFIER_LISTENER:
		Sequence_ReadString( &defaults->listenerName, temp, sizeof( temp ) );
		break;

	case SEQUENCE_MODIFIER_TEXTCHANNEL:
		defaults->textChannel = Sequence_ReadInt( );
		break;

	default:
		MsgDev( D_ERROR, "Internal error caused by line %d of %s.seq: unknown command enum = %d\n", g_lineNum, g_sequenceParseFileName, commandEnum );
	}
}

/*
=============
Sequence_ParseModifier

=============
*/
char Sequence_ParseModifier( sequenceCommandLine_s *defaults )
{
	char modifierName[MAX_STRING];
	char delimiter;
	sequenceCommandEnum_e modifierEnum;

	Sequence_GetNameValueString( modifierName, MAX_STRING );
	modifierEnum = Sequence_GetCommandEnumForName( modifierName, SEQUENCE_TYPE_MODIFIER );

	if( modifierEnum == SEQUENCE_COMMAND_ERROR )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: unknown modifier \"%s\"\n", g_lineNum, g_sequenceParseFileName, modifierName );

	if( !Sequence_IsCommandAModifier( modifierEnum ) )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: \"%s\" is a #command, not a $modifier\n", g_lineNum, g_sequenceParseFileName, modifierName );

	delimiter = Sequence_GetSymbol( );

	if( delimiter != '=' )
		MsgDev( D_ERROR, "Parsing error on or after line %d of %s.seq: after modifier \"%s\", expected '=', found '%c'\n", g_lineNum, g_sequenceParseFileName, modifierName, delimiter );

	Sequence_ReadCommandData( modifierEnum, defaults );

	if( !Sequence_ConfirmCarriageReturnOrSymbol( ',' ) )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: after value(s) for modifier \"%s\", expected ',' or End-Of-Line; found '%c'\n", g_lineNum, g_sequenceParseFileName, modifierName, *g_scan );

	return Sequence_GetSymbol( );
}

/*
=============
Sequence_AddCommandLineToEntry

=============
*/
void Sequence_AddCommandLineToEntry( sequenceCommandLine_s *commandLine, sequenceEntry_s *entry )
{
	sequenceCommandLine_s *scan;

	if( entry->firstCommand )
	{
		for( scan = entry->firstCommand; scan->nextCommandLine; scan = scan->nextCommandLine );
		scan->nextCommandLine = commandLine;
	}
	else entry->firstCommand = commandLine;

	commandLine->nextCommandLine = NULL;
}

/*
=============
Sequence_ParseModifierLine

=============
*/
char Sequence_ParseModifierLine( sequenceEntry_s *entry, sequenceCommandType_e modifierType )
{
	sequenceCommandLine_s *newCommandLine;
	char delimiter = ',';

	while( delimiter == ',' )
	{
		switch( modifierType )
		{
		case SEQUENCE_TYPE_COMMAND:
			newCommandLine = (sequenceCommandLine_s * )Z_Malloc( sizeof( sequenceCommandLine_s ) );
			Q_memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) );
			newCommandLine->commandType = SEQUENCE_COMMAND_MODIFIER;
			Sequence_AddCommandLineToEntry( newCommandLine, entry );
			delimiter = Sequence_ParseModifier( newCommandLine );
			break;

		case SEQUENCE_TYPE_MODIFIER:
			delimiter = Sequence_ParseModifier( &g_fileScopeDefaults );
			break;
		}
	}

	return delimiter;
}

/*
=============
Sequence_ParseCommand

=============
*/
char Sequence_ParseCommand( sequenceCommandLine_s *newCommandLine )
{
	char commandName[MAX_STRING], ch;
	sequenceCommandEnum_e commandEnum;
	sequenceCommandLine_s *modifierCommandLine;

	Sequence_GetNameValueString( commandName, MAX_STRING );
	commandEnum = Sequence_GetCommandEnumForName( commandName, SEQUENCE_TYPE_COMMAND );

	if( commandEnum == SEQUENCE_COMMAND_ERROR )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: unknown command \"%s\"\n", g_lineNum, g_sequenceParseFileName, commandName );

	if( Sequence_IsCommandAModifier( commandEnum ) )
	{
		modifierCommandLine = (sequenceCommandLine_s * )Z_Malloc( sizeof( sequenceCommandLine_s ) );
		Q_memset( modifierCommandLine, 0, sizeof( sequenceCommandLine_s ) );
		modifierCommandLine->commandType = SEQUENCE_COMMAND_POSTMODIFIER;

		for( ; newCommandLine->nextCommandLine; newCommandLine = newCommandLine->nextCommandLine );

		newCommandLine->nextCommandLine = modifierCommandLine;
		newCommandLine = modifierCommandLine;
	}

	ch = Sequence_GetSymbol( );
	if( ch != '=' )
		MsgDev( D_ERROR, "Parsing error on or before line %d of %s.seq: after command \"%s\", expected '=', found '%c'\n",
				   g_lineNum, g_sequenceParseFileName, commandName, ch );

	Sequence_ReadCommandData( commandEnum, newCommandLine );
	return Sequence_GetSymbol( );
}

/*
=============
Sequence_ParseCommandLine

=============
*/
char Sequence_ParseCommandLine( sequenceEntry_s *entry )
{
	char symbol;
	sequenceCommandLine_s *newCommandLine;

	newCommandLine = (sequenceCommandLine_s * )Z_Malloc( sizeof( sequenceCommandLine_s ) );
	Q_memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) );

	Sequence_ResetDefaults( newCommandLine, &g_blockScopeDefaults );
	Sequence_AddCommandLineToEntry( newCommandLine, entry );

	symbol = Sequence_ParseCommand( newCommandLine );

	while( symbol == ',' )
	{
		symbol = Sequence_ParseCommand( newCommandLine );
	}

	return symbol;
}

/*
=============
Sequence_ParseMacro

=============
*/
char Sequence_ParseMacro( sequenceEntry_s *entry )
{
	char symbol;
	sequenceCommandLine_s *newCommandLine;

	newCommandLine = (sequenceCommandLine_s * )Z_Malloc( sizeof( sequenceCommandLine_s ) );
	Q_memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) );

	Sequence_ResetDefaults( newCommandLine, &g_blockScopeDefaults );
	Sequence_AddCommandLineToEntry( newCommandLine, entry );
	Sequence_ReadCommandData( SEQUENCE_COMMAND_GOSUB, newCommandLine );

	symbol = Sequence_GetSymbol( );

	while( symbol == ',' )
	{
		symbol = Sequence_ParseCommand( newCommandLine );
	}

	return symbol;
}

/*
=============
Sequence_ParseLine

=============
*/
char Sequence_ParseLine( char start, sequenceEntry_s *entry )
{
	char end = '\0';

	switch( start )
	{
	case '#':
		end = Sequence_ParseCommandLine( entry );
		break;

	case '$':
		end = Sequence_ParseModifierLine( entry, SEQUENCE_TYPE_MODIFIER );
		break;

	case '@':
		end = Sequence_ParseMacro( entry );
		break;

	default:
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: line must begin with either '#' (command) or '$' (modifier); found '%c'\n", g_lineNum, g_sequenceParseFileName, start );
	}

	return end;
}

/*
=============
Sequence_CalcEntryDuration

=============
*/
float Sequence_CalcEntryDuration( sequenceEntry_s *entry )
{
	float duration;
	sequenceCommandLine_s *cmd;

	duration = 0;

	for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine )
		duration += cmd->delay;

	return duration;
}

/*
=============
Sequence_DoesEntryContainInfiniteLoop

=============
*/
qboolean Sequence_DoesEntryContainInfiniteLoop( sequenceEntry_s *entry )
{
	sequenceCommandLine_s *cmd;

	for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine )
	{
		if( cmd->repeatCount < 0 )
			return true;
	}

	return false;
}

/*
=============
Sequence_IsEntrySafe

=============
*/
qboolean Sequence_IsEntrySafe( sequenceEntry_s *entry )
{
	float duration;
	sequenceCommandLine_s *cmd;

	duration = 0;

	for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine )
	{
		duration += cmd->delay;

		if( cmd->repeatCount < 0 )
		{
			if( duration <= 0 )
				return false;
		}
	}

	return true;
}

/*
=============
Sequence_CreateDefaultsCommand

=============
*/
void Sequence_CreateDefaultsCommand( sequenceEntry_s *entry )
{
	sequenceCommandLine_s *cmd;

	cmd = (sequenceCommandLine_s * )Z_Malloc( sizeof( sequenceCommandLine_s ) );
	Q_memset( cmd, 0, sizeof( sequenceCommandLine_s ) );

	Sequence_ResetDefaults( cmd, &g_fileScopeDefaults );
	cmd->commandType      = SEQUENCE_COMMAND_SETDEFAULTS;
	cmd->modifierBitField = SEQUENCE_MODIFIER_EFFECT_BIT   |
							SEQUENCE_MODIFIER_POSITION_BIT |
							SEQUENCE_MODIFIER_COLOR_BIT    |
							SEQUENCE_MODIFIER_COLOR2_BIT   |
							SEQUENCE_MODIFIER_FADEIN_BIT   |
							SEQUENCE_MODIFIER_FADEOUT_BIT  |
							SEQUENCE_MODIFIER_HOLDTIME_BIT |
							SEQUENCE_MODIFIER_FXTIME_BIT;

	Sequence_AddCommandLineToEntry( cmd, entry );
}


/*
=============
Sequence_ParseEntry

=============
*/
char Sequence_ParseEntry( void )
{
	char symbol;
	char token[MAX_STRING];
	sequenceEntry_s *entry;

	Sequence_GetNameValueString( token, MAX_STRING );
	symbol = Sequence_GetSymbol( );

	if( symbol != '{' )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: expected '{' to start a\n new entry block; found '%c' instead!", g_lineNum, g_sequenceParseFileName, symbol );

	entry = (sequenceEntry_s * )Z_Malloc( sizeof( sequenceEntry_s ) );
	Sequence_ResetDefaults( &g_blockScopeDefaults, &g_fileScopeDefaults );
	entry->entryName    = copystring( token );
	entry->fileName     = copystring( g_sequenceParseFileName );
	entry->isGlobal     = g_sequenceParseFileIsGlobal;
	entry->firstCommand = NULL;
	Sequence_CreateDefaultsCommand( entry );

	symbol = Sequence_GetSymbol( );

	while( symbol != '}' )
	{
		symbol = Sequence_ParseLine( symbol, entry );
	}

	if( !Sequence_IsEntrySafe( entry ) )
		MsgDev( D_ERROR, "Logic error in file %s.seq before line %d: execution of entry \"%%%s\" would cause an infinite loop!", g_sequenceParseFileName, g_lineNum, entry->entryName );

	entry->nextEntry = g_sequenceList;
	g_sequenceList   = entry;

	return Sequence_GetSymbol( );
}

/*
=============
Sequence_FindSentenceGroup

=============
*/
sentenceGroupEntry_s *Sequence_FindSentenceGroup( const char *groupName )
{
	sentenceGroupEntry_s *groupEntry;

	for( groupEntry = g_sentenceGroupList; groupEntry; groupEntry = groupEntry->nextEntry )
	{
		if( !Q_stricmp( groupEntry->groupName, groupName ) )
			return groupEntry;
	}

	return NULL;
}

/*
=============
Sequence_GetSentenceByIndex

=============
*/
sentenceEntry_s *Sequence_GetSentenceByIndex( unsigned int index )
{
	sentenceEntry_s *sentenceEntry;
	sentenceGroupEntry_s *groupEntry;
	unsigned int sentenceCount=0;

	for( groupEntry = g_sentenceGroupList; groupEntry; groupEntry = groupEntry->nextEntry )
	{
		sentenceCount += groupEntry->numSentences;

		if( index < sentenceCount )
		{
			for( sentenceEntry = groupEntry->firstSentence; sentenceEntry;  sentenceEntry = sentenceEntry->nextEntry )
			{
				if( sentenceEntry->index == index )
					return sentenceEntry;
			}
		}
	}

	return NULL;
}


/*
=============
Sequence_PickSentence

=============
*/
sentenceEntry_s *Sequence_PickSentence( const char *groupName, int pickMethod, int *picked )
{
	sentenceEntry_s *sentenceEntry;
	sentenceGroupEntry_s *groupEntry;
	unsigned int pickedIdx;
	unsigned int entryIdx;

	groupEntry = Sequence_FindSentenceGroup( groupName );

	if( groupEntry )
	{
		pickedIdx     = Com_RandomLong( 0, groupEntry->numSentences - 1 );
		sentenceEntry = groupEntry->firstSentence;

		for( entryIdx = pickedIdx; entryIdx; entryIdx-- )
			sentenceEntry = sentenceEntry->nextEntry;
	}
	else
	{
		pickedIdx   = 0;
		sentenceEntry = NULL;
	}

	if( picked )
		*picked = pickedIdx;

	return sentenceEntry;
}

/*
=============
Sequence_AddSentenceGroup

=============
*/
sentenceGroupEntry_s *Sequence_AddSentenceGroup( char *groupName )
{
	sentenceGroupEntry_s *entry, *last;

	entry                = (sentenceGroupEntry_s * )Z_Malloc( sizeof( sentenceGroupEntry_s ) );
	entry->numSentences  = 0;
	entry->firstSentence = NULL;
	entry->nextEntry     = NULL;
	entry->groupName     = copystring( groupName );

	if( g_sentenceGroupList )
	{
		for( last = g_sentenceGroupList; last->nextEntry; last = last->nextEntry );
		last->nextEntry = entry;
	}
	else
	{
		g_sentenceGroupList = entry;
	}

	return entry;
}

/*
=============
Sequence_AddSentenceToGroup

=============
*/
void Sequence_AddSentenceToGroup( char *groupName, char *data )
{
	sentenceEntry_s *entry, *last;
	sentenceGroupEntry_s *group;

	group = Sequence_FindSentenceGroup( groupName );

	if( !group )
	{
		group = Sequence_AddSentenceGroup( groupName );

		if( !group )
			MsgDev( D_ERROR, "Unable to allocate sentence group %s at line %d in file %s.seq", groupName, g_lineNum, g_sequenceParseFileName );
	}

	entry            = (sentenceEntry_s * )Z_Malloc( sizeof( sentenceEntry_s ) );
	entry->nextEntry = NULL;
	entry->data      = copystring( data );
	entry->index     = g_nonGlobalSentences;
	entry->isGlobal  = g_sequenceParseFileIsGlobal;

	group->numSentences++;
	g_nonGlobalSentences++;

	if( group->firstSentence )
	{
		for( last = group->firstSentence; last->nextEntry; last = last->nextEntry );

		last->nextEntry = entry;
	}
	else
	{
		group->firstSentence = entry;
	}
}

/*
=============
Sequence_ParseSentenceLine

=============
*/
qboolean Sequence_ParseSentenceLine( void )
{
	char data[1024];
	char fullgroup[64];
	char groupName[64];
	char *c;
	int lastCharacterPos;
	size_t len;

	len = Sequence_GetToken( fullgroup, sizeof( fullgroup ) );

	if( *fullgroup == '}' )
		return true;

	c = fullgroup + len;

	while( !isalpha( *c ) && *c != '_' )
		c--;

	c += 1;

	if( *c )
		*c = 0;

	strcpy( groupName, fullgroup );

	len = Sequence_GetLine( data, sizeof( data ) );
	lastCharacterPos = len - 1;

	if( data[lastCharacterPos] == '\n' || data[lastCharacterPos] == '\r' )
		data[lastCharacterPos] = 0;

	Sequence_AddSentenceToGroup( groupName, data );
	return false;
}

/*
==============
Sequence_ParseSentenceBlock

==============
*/
char Sequence_ParseSentenceBlock( void )
{
	qboolean end = false;
	char ch = Sequence_GetSymbol( );
	if( ch != '{' )
		MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: expected '{' to start a\n new sentence block; found '%c' instead!", g_lineNum, g_sequenceParseFileName, ch );

	while( !end )
	{
		end = Sequence_ParseSentenceLine( );
	}

	return Sequence_GetSymbol( );
}

/*
==============
Sequence_ParseGlobalDataBlock

==============
*/
char Sequence_ParseGlobalDataBlock( void )
{
	char token[MAX_STRING];

	Sequence_GetNameValueString( token, MAX_STRING );

	if( Q_stricmp( token, "Sentences" ) )
		MsgDev( D_ERROR, "Syntax error in file %s.seq on line %d: found global data block symbol '!' with unknown data type \"%s\"", g_sequenceParseFileName, g_lineNum, token );

	return Sequence_ParseSentenceBlock( );
}

/*
==============
Sequence_GetEntryForName

==============
*/
sequenceEntry_s *Sequence_GetEntryForName( const char *entryName )
{
	sequenceEntry_s *scan;

	for( scan = g_sequenceList; scan; scan = scan->nextEntry )
	{
		if( !Q_stricmp( entryName, scan->entryName ) )
			return scan;
	}

	return NULL;
}

/*
==============
Sequence_CopyCommand

==============
*/
sequenceCommandLine_s *Sequence_CopyCommand( sequenceCommandLine_s *commandOrig )
{
	sequenceCommandLine_s *commandCopy;

	commandCopy = (sequenceCommandLine_s *)Z_Malloc( sizeof( sequenceCommandLine_s ) );

	commandCopy->commandType      = commandOrig->commandType;
	commandCopy->clientMessage    = commandOrig->clientMessage;
	commandCopy->clientMessage.pMessage = copystring( commandOrig->clientMessage.pMessage );
	commandCopy->clientMessage.pName    = copystring( commandOrig->clientMessage.pName );
	commandCopy->speakerName      = copystring( commandOrig->speakerName );
	commandCopy->listenerName     = copystring( commandOrig->listenerName );
	commandCopy->soundFileName    = copystring( commandOrig->soundFileName );
	commandCopy->sentenceName     = copystring( commandOrig->sentenceName );
	commandCopy->fireTargetNames  = copystring( commandOrig->fireTargetNames );
	commandCopy->killTargetNames  = copystring( commandOrig->killTargetNames );
	commandCopy->delay            = commandOrig->delay;
	commandCopy->repeatCount      = commandOrig->repeatCount;
	commandCopy->textChannel      = commandOrig->textChannel;
	commandCopy->modifierBitField = commandOrig->modifierBitField;
	commandCopy->nextCommandLine  = NULL;

	return commandCopy;
}

/*
==============
Sequence_CopyCommandList

==============
*/
sequenceCommandLine_s *Sequence_CopyCommandList( sequenceCommandLine_s *list )
{
	sequenceCommandLine_s *scan, *copy, *New, *prev;

	copy = NULL;
	prev = NULL;

	for( scan = list; scan; scan = scan->nextCommandLine )
	{
		if( scan->commandType != SEQUENCE_COMMAND_SETDEFAULTS )
		{
			New = Sequence_CopyCommand( scan );

			if( prev )
			{
				prev->nextCommandLine = New;
				prev                  = New;
			}
			else
			{
				prev = New;
				copy = New;
			}
		}
	}

	return copy;
}

/*
==============
Sequence_ExpandGosubsForEntry

==============
*/
qboolean Sequence_ExpandGosubsForEntry( sequenceEntry_s *entry )
{
	sequenceCommandLine_s *cmd, *copyList, *scan;
	sequenceEntry_s *gosubEntry;
	qboolean foundGosubs = false;

	for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine )
	{
		if( !cmd->clientMessage.pName )
			continue;

		if( !Q_stricmp( cmd->clientMessage.pName, entry->entryName ) )
			MsgDev( D_ERROR, "Error in %s.seq: entry \"%s\" gosubs itself!\n", entry->fileName, entry->entryName );

		gosubEntry = Sequence_GetEntryForName( cmd->clientMessage.pName );

		if( !gosubEntry )
			MsgDev( D_ERROR, "Error in %s.seq: Gosub in entry \"%s\" specified unknown entry \"%s\"\n", entry->fileName, entry->entryName, cmd->clientMessage.pName );

		foundGosubs = true;
		copyList    = Sequence_CopyCommandList( gosubEntry->firstCommand );

		if( copyList )
		{
			for( scan = copyList->nextCommandLine; scan; scan = scan->nextCommandLine );

			scan->nextCommandLine = cmd->nextCommandLine;

			Z_Free( cmd->clientMessage.pName );
			cmd->clientMessage.pName = NULL;
			cmd                      = scan;
		}
		else
		{
			Z_Free( cmd->clientMessage.pName );
			cmd->clientMessage.pName = NULL;
		}
	}

	return !foundGosubs;
}

/*
==============
Sequence_ExpandAllGosubs

==============
*/
void Sequence_ExpandAllGosubs( void )
{
	sequenceEntry_s *scan;
	qboolean isComplete = true;

	while( !isComplete )
	{
		for( scan = g_sequenceList; scan; scan = scan->nextEntry )
		{
			isComplete = Sequence_ExpandGosubsForEntry( scan );
		}
	}
}

/*
==============
Sequence_FlattenEntry

==============
*/
void Sequence_FlattenEntry( sequenceEntry_s *entry )
{
	sequenceCommandLine_s *cmd, *last = NULL;

	for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine )
	{
		switch( cmd->commandType )
		{
		case SEQUENCE_COMMAND_SETDEFAULTS:
			Sequence_WriteDefaults( cmd, &g_blockScopeDefaults );
			cmd->commandType = SEQUENCE_COMMAND_NOOP;
			break;

		case SEQUENCE_COMMAND_MODIFIER:
			Sequence_WriteDefaults( cmd, &g_blockScopeDefaults );
			break;

		case SEQUENCE_COMMAND_POSTMODIFIER:
			Sequence_WriteDefaults( cmd, last );
			break;

		default:
			Sequence_BakeDefaults( cmd, &g_blockScopeDefaults );
			last = cmd;
		}
	}
}

/*
==============
Sequence_FlattenAllEntries

==============
*/
void Sequence_FlattenAllEntries( void )
{
	sequenceEntry_s *entry;

	for( entry = g_sequenceList; entry; entry = entry->nextEntry )
		Sequence_FlattenEntry( entry );
}

/*
==============
Sequence_ParseBuffer

==============
*/
void Sequence_ParseBuffer( byte *buffer, int bufferSize )
{
	char symbol;

	g_lineNum  = 1;
	g_scan     = (char*)buffer;
	g_lineScan = g_scan;

	Sequence_StripComments((char*)buffer, &bufferSize );
	Sequence_ResetDefaults( &g_fileScopeDefaults, NULL );

	symbol = Sequence_GetSymbol( );

	while( symbol )
	{
		switch( symbol )
		{
		case '$':
			do
				symbol = Sequence_ParseModifier( &g_fileScopeDefaults );
			while( symbol == ',' );
			break;

		case '%':
			symbol = Sequence_ParseEntry( );
			break;

		case '!':
			symbol = Sequence_ParseGlobalDataBlock( );
			break;

		default:
			MsgDev( D_ERROR, "Parsing error on line %d of %s.seq: At file scope, lines must begin with '$' (modifier) or '%%' (entry block) or '!' (sentence / global data block); found '%c'\n", g_lineNum, g_sequenceParseFileName, symbol );
		}
	}

	Sequence_ExpandAllGosubs( );
	Sequence_FlattenAllEntries( );
}

/*
==============
Sequence_ParseFile

==============
*/
void Sequence_ParseFile( const char *fileName, qboolean isGlobal )
{
	byte *buffer;
	fs_offset_t bufSize = 0;

	Q_strcpy( g_sequenceParseFileName, fileName );
	g_sequenceParseFileIsGlobal = isGlobal;

	buffer = FS_LoadFile( va("sequences/%s.seq", fileName ), &bufSize, true );

	if( !buffer )
		return;

	MsgDev( D_INFO, "reading sequence file: %s\n", fileName );

	Sequence_ParseBuffer( buffer, bufSize );

	Mem_Free( buffer );
}

/*
==============
Sequence_Init

==============
*/
void Sequence_Init( void )
{
	Sequence_ParseFile( "global", true );
}

/*
==============
SequenceGet

==============
*/
sequenceEntry_s *Sequence_Get( const char *fileName, const char *entryName )
{
	sequenceEntry_s *scan;

	for( scan = g_sequenceList; scan; scan = scan->nextEntry )
	{
		if( ( !fileName || !Q_stricmp( fileName, scan->fileName ) ) && // a1ba: add filename check, even if originally it is ignored
			!Q_stricmp( entryName, scan->entryName ) )
			return scan;
	}

	return NULL;
}

/*
==============
Sequence_FreeCommand

==============
*/
void Sequence_FreeCommand( sequenceCommandLine_s *kill )
{
	Z_Free( kill->fireTargetNames );
	Z_Free( kill->speakerName );
	Z_Free( kill->listenerName );
	Z_Free( kill->soundFileName );
	Z_Free( kill->sentenceName );
	Z_Free( kill->clientMessage.pName );
	Z_Free( kill->clientMessage.pMessage );
}

/*
==============
Sequence_FreeEntry

==============
*/
void Sequence_FreeEntry( sequenceEntry_s *kill )
{
	sequenceCommandLine_s *dead;

	Z_Free( kill->entryName );
	Z_Free( kill->fileName );

	for( dead = kill->firstCommand; dead; dead = dead->nextCommandLine )
	{
		kill->firstCommand = dead->nextCommandLine;
		Sequence_FreeCommand( dead );
	}

	Z_Free( kill );
}

/*
==============
Sequence_FreeSentence

==============
*/
void Sequence_FreeSentence( sentenceEntry_s *sentenceEntry )
{
	Z_Free( sentenceEntry->data );
	Z_Free( sentenceEntry );
}

/*
==============
Sequence_FreeSentenceGroup

==============
*/
void Sequence_FreeSentenceGroup( sentenceGroupEntry_s *groupEntry )
{
	Z_Free( groupEntry->groupName );
	Z_Free( groupEntry );
}

/*
==============
Sequence_FreeSentenceGroupEntries

==============
*/
void Sequence_FreeSentenceGroupEntries( sentenceGroupEntry_s *groupEntry, qboolean purgeGlobals )
{
	sentenceEntry_s *sentenceEntry;
	sentenceEntry_s *deadSentence;
	sentenceEntry_s *prevSentence;

	sentenceEntry = groupEntry->firstSentence;
	prevSentence  = NULL;

	while( sentenceEntry )
	{
		if( !sentenceEntry->isGlobal || purgeGlobals )
		{
			if( prevSentence )
				prevSentence->nextEntry = sentenceEntry->nextEntry;
			else
				groupEntry->firstSentence = sentenceEntry->nextEntry;

			groupEntry->numSentences--;
			g_nonGlobalSentences--;

			deadSentence  = sentenceEntry;
			sentenceEntry = sentenceEntry->nextEntry;

			Sequence_FreeSentence( deadSentence );
		}
		else
		{
			prevSentence  = sentenceEntry;
			sentenceEntry = sentenceEntry->nextEntry;
		}
	}
}

/*
==============
Sequence_PurgeEntries

==============
*/
void Sequence_PurgeEntries( qboolean purgeGlobals )
{
	sequenceEntry_s *scan;
	sequenceEntry_s *dead;
	sequenceEntry_s *prev;
	sentenceGroupEntry_s *groupEntry;
	sentenceGroupEntry_s *deadGroup;
	sentenceGroupEntry_s *prevGroup;

	dead = NULL;
	prev = NULL;

	for( scan = g_sequenceList; scan; )
	{
		if( !scan->isGlobal || purgeGlobals )
		{
			if( prev )
				prev->nextEntry = scan->nextEntry;
			else
				g_sequenceList = scan->nextEntry;

			dead = scan;
			scan = scan->nextEntry;
			Sequence_FreeEntry( dead );
		}
		else
		{
			prev = scan;
			scan = scan->nextEntry;
		}
	}

	groupEntry = g_sentenceGroupList;
	prevGroup  = NULL;

	while( groupEntry )
	{
		Sequence_FreeSentenceGroupEntries( groupEntry, purgeGlobals );

		if( groupEntry->numSentences )
		{
			prevGroup  = groupEntry;
			groupEntry = groupEntry->nextEntry;
		}
		else
		{
			if( prevGroup )
				prevGroup->nextEntry = groupEntry->nextEntry;
			else
				g_sentenceGroupList = groupEntry->nextEntry;

			deadGroup  = groupEntry;
			groupEntry = groupEntry->nextEntry;
			Sequence_FreeSentenceGroup( deadGroup );
		}
	}
}

/*
==============
Sequence_OnLevelLoad

==============
*/
void Sequence_OnLevelLoad( const char *mapName )
{
	Sequence_PurgeEntries( false );
	Sequence_ParseFile( mapName, false );
}
