xash3d-fwgs/engine/client/titles.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
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:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

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;
}