/* 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 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 ) Con_Reportf( S_ERROR "Attempt to bake defaults into a non-existant command." ); if( !source ) Con_Reportf( S_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 ) Con_Reportf( S_ERROR "Attempt to bake defaults into a non-existant command." ); if( !source ) Con_Reportf( S_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 == '$' ) Con_Reportf( S_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 Con_Reportf( S_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 ) ) Con_Reportf( S_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 ) ) Con_Reportf( S_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 ) Con_Reportf( S_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 ) Con_Reportf( S_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 != '\"' ) Con_Reportf( S_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 ); } Con_Reportf( S_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: Con_Reportf( S_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 ) Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: unknown modifier \"%s\"\n", g_lineNum, g_sequenceParseFileName, modifierName ); if( !Sequence_IsCommandAModifier( modifierEnum ) ) Con_Reportf( S_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 != '=' ) Con_Reportf( S_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( ',' ) ) Con_Reportf( S_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 ) Con_Reportf( S_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 != '=' ) Con_Reportf( S_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: Con_Reportf( S_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 != '{' ) Con_Reportf( S_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 ) ) Con_Reportf( S_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 ) Con_Reportf( S_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 != '{' ) Con_Reportf( S_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" ) ) Con_Reportf( S_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 ) ) Con_Reportf( S_ERROR "Error in %s.seq: entry \"%s\" gosubs itself!\n", entry->fileName, entry->entryName ); gosubEntry = Sequence_GetEntryForName( cmd->clientMessage.pName ); if( !gosubEntry ) Con_Reportf( S_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 ============== */ static void Sequence_ParseBuffer( char *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: Con_Reportf( S_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; Con_Reportf( "reading sequence file: %s\n", fileName ); Sequence_ParseBuffer( (char *)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 ); }