mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-25 11:19:59 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
348 lines
7.7 KiB
C
348 lines
7.7 KiB
C
/*
|
|
titles.c - implementation of titles.txt parser
|
|
Copyright (C) 2010 Uncle Mike
|
|
|
|
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 "client.h"
|
|
|
|
#define MAX_MESSAGES 2048
|
|
|
|
#define MSGFILE_NAME 0
|
|
#define MSGFILE_TEXT 1
|
|
|
|
client_textmessage_t gMessageParms;
|
|
|
|
// the string "pText" is assumed to have all whitespace from both ends cut out
|
|
static int IsComment( const char *pText )
|
|
{
|
|
if( pText )
|
|
{
|
|
int length = Q_strlen( pText );
|
|
|
|
if( length >= 2 && pText[0] == '/' && pText[1] == '/' )
|
|
return 1;
|
|
|
|
// no text?
|
|
if( length > 0 )
|
|
return 0;
|
|
}
|
|
|
|
// no text is a comment too
|
|
return 1;
|
|
}
|
|
|
|
// the string "pText" is assumed to have all whitespace from both ends cut out
|
|
static int IsStartOfText( const char *pText )
|
|
{
|
|
if( pText )
|
|
{
|
|
if( pText[0] == '{' )
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// the string "pText" is assumed to have all whitespace from both ends cut out
|
|
static int IsEndOfText( const char *pText )
|
|
{
|
|
if( pText )
|
|
{
|
|
if( pText[0] == '}' )
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int IsWhiteSpace( char space )
|
|
{
|
|
if( space == ' ' || space == '\t' || space == '\r' || space == '\n' )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static const char *SkipSpace( const char *pText )
|
|
{
|
|
if( pText )
|
|
{
|
|
int pos = 0;
|
|
while( pText[pos] && IsWhiteSpace( pText[pos] ))
|
|
pos++;
|
|
return pText + pos;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *SkipText( const char *pText )
|
|
{
|
|
if( pText )
|
|
{
|
|
int pos = 0;
|
|
while( pText[pos] && !IsWhiteSpace( pText[pos] ))
|
|
pos++;
|
|
return pText + pos;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int ParseFloats( const char *pText, float *pFloat, int count )
|
|
{
|
|
const char *pTemp = pText;
|
|
int index = 0;
|
|
|
|
while( pTemp && count > 0 )
|
|
{
|
|
// skip current token / float
|
|
pTemp = SkipText( pTemp );
|
|
// skip any whitespace in between
|
|
pTemp = SkipSpace( pTemp );
|
|
|
|
if( pTemp )
|
|
{
|
|
// parse a float
|
|
pFloat[index] = Q_atof( pTemp );
|
|
count--;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
if( count == 0 )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int IsToken( const char *pText, const char *pTokenName )
|
|
{
|
|
if( !pText || !pTokenName )
|
|
return 0;
|
|
|
|
if( !Q_strnicmp( pText+1, pTokenName, Q_strlen( pTokenName )))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ParseDirective( const char *pText )
|
|
{
|
|
if( pText && pText[0] == '$' )
|
|
{
|
|
float tempFloat[8];
|
|
|
|
if( IsToken( pText, "position" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 2 ))
|
|
{
|
|
gMessageParms.x = tempFloat[0];
|
|
gMessageParms.y = tempFloat[1];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "effect" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 1 ))
|
|
{
|
|
gMessageParms.effect = (int)tempFloat[0];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "fxtime" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 1 ))
|
|
{
|
|
gMessageParms.fxtime = tempFloat[0];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "color2" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 3 ))
|
|
{
|
|
gMessageParms.r2 = (int)tempFloat[0];
|
|
gMessageParms.g2 = (int)tempFloat[1];
|
|
gMessageParms.b2 = (int)tempFloat[2];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "color" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 3 ))
|
|
{
|
|
gMessageParms.r1 = (int)tempFloat[0];
|
|
gMessageParms.g1 = (int)tempFloat[1];
|
|
gMessageParms.b1 = (int)tempFloat[2];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "fadein" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 1 ))
|
|
{
|
|
gMessageParms.fadein = tempFloat[0];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "fadeout" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 3 ))
|
|
{
|
|
gMessageParms.fadeout = tempFloat[0];
|
|
}
|
|
}
|
|
else if( IsToken( pText, "holdtime" ))
|
|
{
|
|
if( ParseFloats( pText, tempFloat, 3 ))
|
|
{
|
|
gMessageParms.holdtime = tempFloat[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( S_ERROR "unknown token: %s\n", pText );
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CL_TextMessageParse( byte *pMemFile, int fileSize )
|
|
{
|
|
char buf[512], trim[512], currentName[512];
|
|
char *pCurrentText = NULL, *pNameHeap;
|
|
char nameHeap[32768]; // g-cont. i will scale up heap to handle all TFC messages
|
|
int mode = MSGFILE_NAME; // searching for a message name
|
|
int lineNumber, filePos, lastLinePos;
|
|
client_textmessage_t textMessages[MAX_MESSAGES];
|
|
int i, nameHeapSize, textHeapSize, messageSize, nameOffset;
|
|
int messageCount, lastNamePos;
|
|
|
|
lastNamePos = 0;
|
|
lineNumber = 0;
|
|
filePos = 0;
|
|
lastLinePos = 0;
|
|
messageCount = 0;
|
|
|
|
while( COM_MemFgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL )
|
|
{
|
|
COM_TrimSpace( buf, trim );
|
|
|
|
switch( mode )
|
|
{
|
|
case MSGFILE_NAME:
|
|
// skip comment lines
|
|
if( IsComment( trim ))
|
|
break;
|
|
|
|
// Is this a directive "$command"?, if so parse it and break
|
|
if( ParseDirective( trim ))
|
|
break;
|
|
|
|
if( IsStartOfText( trim ))
|
|
{
|
|
mode = MSGFILE_TEXT;
|
|
pCurrentText = (char*)(pMemFile + filePos);
|
|
break;
|
|
}
|
|
|
|
if( IsEndOfText( trim ))
|
|
{
|
|
Con_Reportf( "TextMessage: unexpected '}' found, line %d\n", lineNumber );
|
|
return;
|
|
}
|
|
Q_strcpy( currentName, trim );
|
|
break;
|
|
case MSGFILE_TEXT:
|
|
if( IsEndOfText( trim ))
|
|
{
|
|
int length = Q_strlen( currentName );
|
|
|
|
// save name on name heap
|
|
if( lastNamePos + length > 32768 )
|
|
{
|
|
Con_Reportf( "TextMessage: error while parsing!\n" );
|
|
return;
|
|
}
|
|
|
|
Q_strcpy( nameHeap + lastNamePos, currentName );
|
|
|
|
// terminate text in-place in the memory file
|
|
// (it's temporary memory that will be deleted)
|
|
pMemFile[lastLinePos-1] = 0;
|
|
|
|
// Save name/text on heap
|
|
textMessages[messageCount] = gMessageParms;
|
|
textMessages[messageCount].pName = nameHeap + lastNamePos;
|
|
lastNamePos += length + 1;
|
|
textMessages[messageCount].pMessage = pCurrentText;
|
|
messageCount++;
|
|
|
|
// reset parser to search for names
|
|
mode = MSGFILE_NAME;
|
|
break;
|
|
}
|
|
if( IsStartOfText( trim ))
|
|
{
|
|
Con_Reportf( "TextMessage: unexpected '{' found, line %d\n", lineNumber );
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
lineNumber++;
|
|
lastLinePos = filePos;
|
|
|
|
if( messageCount >= MAX_MESSAGES )
|
|
{
|
|
Con_Printf( S_WARN "Too many messages in titles.txt, max is %d\n", MAX_MESSAGES );
|
|
break;
|
|
}
|
|
}
|
|
|
|
Con_Reportf( "TextMessage: parsed %d text messages\n", messageCount );
|
|
nameHeapSize = lastNamePos;
|
|
textHeapSize = 0;
|
|
|
|
for( i = 0; i < messageCount; i++ )
|
|
textHeapSize += Q_strlen( textMessages[i].pMessage ) + 1;
|
|
messageSize = ( messageCount * sizeof( client_textmessage_t ));
|
|
|
|
if(( textHeapSize + nameHeapSize + messageSize ) <= 0 )
|
|
{
|
|
clgame.titles = NULL;
|
|
clgame.numTitles = 0;
|
|
return;
|
|
}
|
|
|
|
// must malloc because we need to be able to clear it after initialization
|
|
clgame.titles = (client_textmessage_t *)Mem_Calloc( cls.mempool, textHeapSize + nameHeapSize + messageSize );
|
|
|
|
// copy table over
|
|
memcpy( clgame.titles, textMessages, messageSize );
|
|
|
|
// copy Name heap
|
|
pNameHeap = ((char *)clgame.titles) + messageSize;
|
|
memcpy( pNameHeap, nameHeap, nameHeapSize );
|
|
//nameOffset = pNameHeap - clgame.titles[0].pName; //undefined on amd64
|
|
|
|
|
|
// copy text & fixup pointers
|
|
pCurrentText = pNameHeap + nameHeapSize;
|
|
|
|
for( i = 0; i < messageCount; i++ )
|
|
{
|
|
clgame.titles[i].pName = pNameHeap; // adjust name pointer (parallel buffer)
|
|
Q_strcpy( pCurrentText, clgame.titles[i].pMessage ); // copy text over
|
|
clgame.titles[i].pMessage = pCurrentText;
|
|
pNameHeap += Q_strlen( pNameHeap ) + 1;
|
|
pCurrentText += Q_strlen( pCurrentText ) + 1;
|
|
}
|
|
|
|
if(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize ))
|
|
Con_DPrintf( S_ERROR "TextMessage: overflow text message buffer!\n" );
|
|
|
|
clgame.numTitles = messageCount;
|
|
}
|