You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1782 lines
39 KiB
1782 lines
39 KiB
/* |
|
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 <ctype.h> |
|
#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 |
|
-1, -1, -1, -1, // rgba1 |
|
-1, -1, -1, -1, // 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 *read; |
|
char *write = line; |
|
|
|
Sequence_SkipWhitespace( ); |
|
|
|
read = 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 = Z_Malloc( sizeof( sequenceCommandLine_s ) ); |
|
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 = Z_Malloc( sizeof( sequenceCommandLine_s ) ); |
|
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 = Z_Malloc( sizeof( sequenceCommandLine_s ) ); |
|
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 = Z_Malloc( sizeof( sequenceCommandLine_s ) ); |
|
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 = Z_Malloc( sizeof( sequenceCommandLine_s ) ); |
|
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 = 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 = 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 = 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 = 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 = buffer; |
|
g_lineScan = g_scan; |
|
|
|
Sequence_StripComments( 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 ); |
|
}
|
|
|