mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-26 22:01:38 +00:00
1112 lines
29 KiB
C++
1112 lines
29 KiB
C++
/*
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
//#define FGD_VERBOSE // define this for extra info in the log.
|
|
|
|
#include "plugin.h"
|
|
|
|
/*! \file plugin.cpp
|
|
\brief .fgd entity description format
|
|
|
|
FGD loading code by Dominic Clifton - Hydra (Hydra@Hydras-World.com)
|
|
|
|
Overview
|
|
========
|
|
|
|
This module loads .fgd files, fgd files are split into classes:
|
|
|
|
base classes
|
|
point classes (aka fixed size entities)
|
|
solid classes (aka brush entity)
|
|
|
|
This program first scans each file, building up a list structures
|
|
in memory that contain the information for all the classes found
|
|
in the file.
|
|
|
|
Then the program looks at each of the non-base classes in the list
|
|
and build the eclass_t structure from each one.
|
|
|
|
Classes can request information in other classes.
|
|
|
|
Solid/Base and Point/Base classes can have the same names as other
|
|
classes but there can be only one of each solid/point/base class with
|
|
the same name,
|
|
|
|
e.g.:
|
|
|
|
this is NOT allowed:
|
|
|
|
@solidclass = "a"
|
|
@solidclass = "a"
|
|
|
|
this is NOT allowed:
|
|
|
|
@pointclass = "a"
|
|
@solidclass = "a"
|
|
|
|
this IS allowed:
|
|
|
|
@solidclass = "a"
|
|
@baseclass = "a"
|
|
|
|
Version History
|
|
===============
|
|
|
|
v0.1 - 13/March/2002
|
|
- Initial version.
|
|
|
|
v0.2 - 16/March/2002
|
|
- sets e->skinpath when it finds an iconsprite(<filename>) token.
|
|
|
|
v0.3 - 21/March/2002
|
|
- Core now supports > 8 spawnflags, changes reflected here too.
|
|
- FIXED: mins/maxs were backwards when only w,h,d were in the FGD
|
|
(as opposed to the actual mins/maxs being in the .def file)
|
|
- Made sure all PointClass entities were fixed size entities
|
|
and gave them a default bounding box size if a size() setting
|
|
was not in the FGD file.
|
|
- Removed the string check for classes requesting another class
|
|
with the same name, adjusted Find_Class() so that it can search
|
|
for baseclasses only, this fixes the problem with PointClass "light"
|
|
requesting the BaseClass "light".
|
|
|
|
v0.4 - 25/March/2002
|
|
- bleh, It turns out that non-baseclasses can request non-baseclasses
|
|
so now I've changed Find_Class() so that it can ignore a specific class
|
|
(i.e. the one that's asking for others, so that classes can't request
|
|
themselves but they can request other classes of any kind with the
|
|
same name).
|
|
- made all spawnflag comments appear in one place, rather than being scattered
|
|
all over the comments if a requested class also had some spawnflags
|
|
|
|
v0.5 - 6/April/2002
|
|
- not using skinpath for sprites anymore, apprently we can code a model
|
|
module to display sprites and model files.
|
|
- model() tags are now supported.
|
|
|
|
ToDo
|
|
====
|
|
|
|
* add support for setting the eclass_t's modelpath.
|
|
(not useful for CS, but very useful for HL).
|
|
|
|
* need to implement usage for e->skinpath in the core.
|
|
|
|
* cleanup some areas now that GetTokenExtra() is available
|
|
(some parts were written prior to it's creation).
|
|
|
|
* Import the comments between each BaseClass's main [ ] set.
|
|
(unfortunatly they're // cstyle comments, which GetToken skips over)
|
|
But still ignore comments OUTSIDE the main [ ] set.
|
|
|
|
*/
|
|
|
|
_QERScripLibTable g_ScripLibTable;
|
|
_EClassManagerTable g_EClassManagerTable;
|
|
_QERFuncTable_1 g_FuncTable;
|
|
_QERFileSystemTable g_FileSystemTable;
|
|
|
|
// forward declare
|
|
void Eclass_ScanFile( char *filename );
|
|
|
|
const char* EClass_GetExtension(){
|
|
return "fgd";
|
|
}
|
|
|
|
CSynapseServer* g_pSynapseServer = NULL;
|
|
CSynapseClientFGD g_SynapseClient;
|
|
|
|
#if __GNUC__ >= 4
|
|
#pragma GCC visibility push(default)
|
|
#endif
|
|
extern "C" CSynapseClient * SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces( const char *version, CSynapseServer *pServer ) {
|
|
#if __GNUC__ >= 4
|
|
#pragma GCC visibility pop
|
|
#endif
|
|
if ( strcmp( version, SYNAPSE_VERSION ) ) {
|
|
Syn_Printf( "ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version );
|
|
return NULL;
|
|
}
|
|
g_pSynapseServer = pServer;
|
|
g_pSynapseServer->IncRef();
|
|
Set_Syn_Printf( g_pSynapseServer->Get_Syn_Printf() );
|
|
|
|
g_SynapseClient.AddAPI( ECLASS_MAJOR, "fgd", sizeof( _EClassTable ) );
|
|
g_SynapseClient.AddAPI( SCRIPLIB_MAJOR, NULL, sizeof( g_ScripLibTable ), SYN_REQUIRE, &g_ScripLibTable );
|
|
g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable );
|
|
g_SynapseClient.AddAPI( ECLASSMANAGER_MAJOR, NULL, sizeof( g_EClassManagerTable ), SYN_REQUIRE, &g_EClassManagerTable );
|
|
|
|
// Needs a 'default' option for this minor because we certainly don't load anything from wad files :)
|
|
g_SynapseClient.AddAPI( VFS_MAJOR, "wad", sizeof( g_FileSystemTable ), SYN_REQUIRE, &g_FileSystemTable );
|
|
|
|
return &g_SynapseClient;
|
|
}
|
|
|
|
bool CSynapseClientFGD::RequestAPI( APIDescriptor_t *pAPI ){
|
|
if ( !strcmp( pAPI->major_name, ECLASS_MAJOR ) ) {
|
|
_EClassTable* pTable = static_cast<_EClassTable*>( pAPI->mpTable );
|
|
|
|
pTable->m_pfnGetExtension = &EClass_GetExtension;
|
|
pTable->m_pfnScanFile = &Eclass_ScanFile;
|
|
|
|
return true;
|
|
}
|
|
|
|
Syn_Printf( "ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo() );
|
|
return false;
|
|
}
|
|
|
|
#include "version.h"
|
|
|
|
const char* CSynapseClientFGD::GetInfo(){
|
|
return ".fgd eclass module built " __DATE__ " " RADIANT_VERSION;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
#define CLASS_NOCLASS 0
|
|
#define CLASS_BASECLASS 1
|
|
#define CLASS_POINTCLASS 2
|
|
#define CLASS_SOLIDCLASS 3
|
|
|
|
char *classnames[] = {"NOT DEFINED","BaseClass","PointClass","SolidClass"};
|
|
|
|
#define OPTION_NOOPTION 0
|
|
#define OPTION_STRING 1
|
|
#define OPTION_CHOICES 2
|
|
#define OPTION_INTEGER 3
|
|
#define OPTION_FLAGS 4
|
|
|
|
char *optionnames[] = {"NOT DEFINED","String","Choices","Integer","Flags"};
|
|
|
|
typedef struct choice_s {
|
|
int value;
|
|
char *name;
|
|
} choice_t;
|
|
|
|
typedef struct option_s {
|
|
int optiontype;
|
|
char *optioninfo;
|
|
char *epairname;
|
|
char *optiondefault;
|
|
GSList *choices; // list of choices_t
|
|
} option_t;
|
|
|
|
typedef struct class_s {
|
|
int classtype; // see CLASS_* above.
|
|
char *classname;
|
|
GSList *l_baselist; // when building the eclass_t, other class_s's with these names are required.
|
|
char *description;
|
|
|
|
GSList *l_optionlist; // when building the eclass_t, other class_s's with these names are required.
|
|
|
|
bool gotsize; // if set then boundingbox is valid.
|
|
vec3_t boundingbox[2]; // mins, maxs
|
|
|
|
bool gotcolor; // if set then color is valid.
|
|
vec3_t color; // R,G,B, loaded as 0-255
|
|
|
|
char *model; // relative path + filename to a model (.spr/.mdl) file, or NULL
|
|
} class_t;
|
|
|
|
/*
|
|
===========================================================
|
|
utility functions
|
|
|
|
===========================================================
|
|
*/
|
|
char *strlower( char *start ){
|
|
char *in;
|
|
in = start;
|
|
while ( *in )
|
|
{
|
|
*in = tolower( *in );
|
|
in++;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *addstr( char *dest,char *source ){
|
|
if ( dest ) {
|
|
char *ptr;
|
|
int len = strlen( dest );
|
|
ptr = (char *) malloc( len + strlen( source ) + 1 );
|
|
strcpy( ptr,dest );
|
|
strcpy( ptr + len,source );
|
|
free( dest );
|
|
dest = ptr;
|
|
}
|
|
else
|
|
{
|
|
dest = strdup( source );
|
|
}
|
|
return( dest );
|
|
}
|
|
|
|
int getindex( unsigned int a ){
|
|
unsigned int count = 0;
|
|
unsigned int b = 0;
|
|
for (; b != a; count++ )
|
|
{
|
|
b = ( 1 << count );
|
|
if ( count > a ) {
|
|
return -1;
|
|
}
|
|
}
|
|
return ( count );
|
|
}
|
|
|
|
void ClearGSList( GSList* lst ){
|
|
GSList *p = lst;
|
|
while ( p )
|
|
{
|
|
free( p->data );
|
|
p = g_slist_remove( p, p->data );
|
|
}
|
|
}
|
|
|
|
/*!
|
|
free a choice_t structure and it's contents
|
|
*/
|
|
void Free_Choice( choice_t *p ){
|
|
if ( p->name ) {
|
|
free( p->name );
|
|
}
|
|
free( p );
|
|
|
|
}
|
|
|
|
/*
|
|
===========================================================
|
|
Main functions
|
|
|
|
===========================================================
|
|
*/
|
|
|
|
/*!
|
|
free an option_t structure and it's contents
|
|
*/
|
|
void Free_Option( option_t *p ){
|
|
if ( p->epairname ) {
|
|
free( p->epairname );
|
|
}
|
|
if ( p->optiondefault ) {
|
|
free( p->optiondefault );
|
|
}
|
|
if ( p->optioninfo ) {
|
|
free( p->optioninfo );
|
|
}
|
|
GSList *l = p->choices;
|
|
while ( l )
|
|
{
|
|
Free_Choice( (choice_t *)l->data );
|
|
l = g_slist_remove( l, l->data );
|
|
}
|
|
free( p );
|
|
}
|
|
|
|
/*!
|
|
free a class_t structure and it's contents
|
|
*/
|
|
void Free_Class( class_t *p ){
|
|
GSList *l = p->l_optionlist;
|
|
while ( l )
|
|
{
|
|
Free_Option( (option_t *)l->data );
|
|
l = g_slist_remove( l, l->data );
|
|
}
|
|
|
|
if ( p->classname ) {
|
|
free( p->classname );
|
|
}
|
|
free( p );
|
|
}
|
|
|
|
/*!
|
|
find a class in the list
|
|
*/
|
|
class_t *Find_Class( GSList *l,char *classname, class_t *ignore ){
|
|
for ( GSList *clst = l; clst != NULL; clst = clst->next )
|
|
{
|
|
class_t *c = (class_t *)clst->data;
|
|
|
|
if ( c == ignore ) {
|
|
continue;
|
|
}
|
|
|
|
// NOTE: to speed up we could make all the classnames lower-case when they're initialised.
|
|
if ( !stricmp( c->classname,classname ) ) {
|
|
return c;
|
|
}
|
|
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*!
|
|
Import as much as possible from a class_t into an eclass_t
|
|
Note: this is somewhat recursive, as a class can require a class that requires a class and so on..
|
|
*/
|
|
void EClass_ImportFromClass( eclass_t *e, GSList *l_classes, class_t *bc ){
|
|
char color[128];
|
|
|
|
// We allocate 16k here, but only the memory actually used is kept allocated.
|
|
// this is just used for building the final comments string.
|
|
// Note: if the FGD file contains comments that are >16k (per entity) then
|
|
// radiant will crash upon loading such a file as the eclass_t will become
|
|
// corrupted.
|
|
// FIXME: we could add some length checking when building "newcomments", but
|
|
// that'd slow it down a bit.
|
|
char newcomments[16384] = "";
|
|
|
|
//Note: we override the values already in e.
|
|
//and we do it in such a way that the items that appear last in the l_baselist
|
|
//represent the final values.
|
|
|
|
if ( bc->description ) {
|
|
sprintf( newcomments,"%s\n",bc->description );
|
|
e->comments = addstr( e->comments,newcomments );
|
|
newcomments[0] = 0; // so we don't add them twice.
|
|
}
|
|
|
|
|
|
// import from other classes if required.
|
|
|
|
if ( bc->l_baselist ) {
|
|
// this class requires other base classes.
|
|
|
|
for ( GSList *bclst = bc->l_baselist; bclst != NULL; bclst = bclst->next )
|
|
{
|
|
char *requestedclass = (char *)bclst->data;
|
|
|
|
// class_t *rbc = Find_Class(l_classes, requestedclass, true);
|
|
class_t *rbc = Find_Class( l_classes, requestedclass, bc );
|
|
|
|
// make sure we don't request ourself!
|
|
if ( rbc == bc ) {
|
|
Sys_Printf( "WARNING: baseclass '%s' tried to request itself!\n", bclst->data );
|
|
}
|
|
else
|
|
{
|
|
if ( !rbc ) {
|
|
Sys_Printf( "WARNING: could not find the requested baseclass '%s' when building '%s'\n", requestedclass,bc->classname );
|
|
}
|
|
else
|
|
{
|
|
// call ourself!
|
|
EClass_ImportFromClass( e, l_classes, rbc );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// SIZE
|
|
if ( bc->gotsize ) {
|
|
e->fixedsize = true;
|
|
memcpy( e->mins,bc->boundingbox[0],sizeof( vec3_t ) );
|
|
memcpy( e->maxs,bc->boundingbox[1],sizeof( vec3_t ) );
|
|
}
|
|
/*
|
|
// Hydra: apparently, this would be bad.
|
|
|
|
if (bc->sprite)
|
|
{
|
|
// Hydra - NOTE: e->skinpath is not currently used by the editor but the code
|
|
// to set it is used by both this eclass_t loader and the .DEF eclass_t loader.
|
|
// TODO: implement using e->skinpath.
|
|
if (e->skinpath)
|
|
free (e->skinpath);
|
|
|
|
e->skinpath = strdup(bc->sprite);
|
|
}
|
|
*/
|
|
|
|
// MODEL
|
|
if ( bc->model ) {
|
|
if ( e->modelpath ) {
|
|
free( e->modelpath );
|
|
}
|
|
|
|
e->modelpath = strdup( bc->model );
|
|
}
|
|
|
|
// COLOR
|
|
if ( bc->gotcolor ) {
|
|
memcpy( e->color,bc->color,sizeof( vec3_t ) );
|
|
sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] );
|
|
e->texdef.SetName( color );
|
|
}
|
|
|
|
// SPAWNFLAGS and COMMENTS
|
|
if ( bc->l_optionlist ) {
|
|
for ( GSList *optlst = bc->l_optionlist; optlst != NULL; optlst = optlst->next )
|
|
{
|
|
option_t *opt = (option_t*) optlst->data;
|
|
|
|
if ( opt->optiontype != OPTION_FLAGS ) {
|
|
// add some info to the comments.
|
|
if ( opt->optioninfo ) {
|
|
sprintf( newcomments + strlen( newcomments ),"%s '%s' %s%s\n",
|
|
opt->epairname,
|
|
opt->optioninfo ? opt->optioninfo : "",
|
|
opt->optiondefault ? ", Default: " : "",
|
|
opt->optiondefault ? opt->optiondefault : "" );
|
|
}
|
|
else
|
|
{
|
|
sprintf( newcomments + strlen( newcomments ),"%s %s%s\n",
|
|
opt->epairname,
|
|
opt->optiondefault ? ", Default: " : "",
|
|
opt->optiondefault ? opt->optiondefault : "" );
|
|
}
|
|
}
|
|
|
|
GSList *choicelst;
|
|
switch ( opt->optiontype )
|
|
{
|
|
case OPTION_FLAGS:
|
|
// grab the flags.
|
|
for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next )
|
|
{
|
|
choice_t *choice = (choice_t*) choicelst->data;
|
|
|
|
int index = getindex( choice->value );
|
|
index--;
|
|
if ( index < MAX_FLAGS ) {
|
|
strcpy( e->flagnames[index],choice->name );
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf( "WARNING: baseclass '%s' has a spawnflag out of range, ignored!\n", bc->classname );
|
|
}
|
|
}
|
|
break;
|
|
case OPTION_CHOICES:
|
|
strcat( newcomments," Choices:\n" );
|
|
for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next )
|
|
{
|
|
choice_t *choice = (choice_t*) choicelst->data;
|
|
sprintf( newcomments + strlen( newcomments )," %5d - %s\n",choice->value,choice->name );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// name
|
|
if ( e->name ) {
|
|
free( e->name );
|
|
}
|
|
e->name = strdup( bc->classname );
|
|
|
|
// fixed size initialisation
|
|
if ( bc->classtype == CLASS_POINTCLASS ) {
|
|
e->fixedsize = true;
|
|
// some point classes dont seem to have size()'s in the fgd, so set up a default here..
|
|
if ( ( e->mins[0] == 0 ) && ( e->mins[1] == 0 ) && ( e->mins[2] == 0 ) &&
|
|
( e->maxs[0] == 0 ) && ( e->maxs[1] == 0 ) && ( e->maxs[2] == 0 ) ) {
|
|
e->mins[0] = -8;
|
|
e->mins[1] = -8;
|
|
e->mins[2] = -8;
|
|
e->maxs[0] = 8;
|
|
e->maxs[1] = 8;
|
|
e->maxs[2] = 8;
|
|
}
|
|
|
|
if ( e->texdef.GetName() == NULL ) {
|
|
// no color specified for this entity in the fgd file
|
|
// set one now
|
|
// Note: if this eclass_t is not fully built, then this may be
|
|
// overridden with the correct color.
|
|
|
|
e->color[0] = 1;
|
|
e->color[1] = 0.5; // how about a nice bright pink, mmm, nice! :)
|
|
e->color[2] = 1;
|
|
|
|
sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] );
|
|
e->texdef.SetName( color );
|
|
}
|
|
}
|
|
|
|
// COMMENTS
|
|
if ( newcomments[0] ) {
|
|
e->comments = addstr( e->comments,newcomments );
|
|
}
|
|
}
|
|
|
|
/*!
|
|
create the eclass_t structures and add to the global list.
|
|
*/
|
|
void Create_EClasses( GSList *l_classes ){
|
|
int count = 0;
|
|
// loop through the loaded structures finding all the non BaseClasses
|
|
for ( GSList *clst = l_classes; clst != NULL; clst = clst->next )
|
|
{
|
|
class_t *c = (class_t *)clst->data;
|
|
|
|
if ( c->classtype == CLASS_BASECLASS ) { // not looking for these.
|
|
continue;
|
|
}
|
|
|
|
eclass_t *e = (eclass_t *) malloc( sizeof( eclass_s ) );
|
|
memset( e,0,sizeof( eclass_s ) );
|
|
|
|
EClass_ImportFromClass( e, l_classes, c );
|
|
|
|
// radiant will crash if this is null, and it still could be at this point.
|
|
if ( !e->comments ) {
|
|
e->comments = strdup( "No description available, check documentation\n" );
|
|
}
|
|
|
|
// dump the spawnflags to the end of the comments.
|
|
int i;
|
|
bool havespawnflags;
|
|
|
|
havespawnflags = false;
|
|
for ( i = 0 ; i < MAX_FLAGS ; i++ )
|
|
{
|
|
if ( *e->flagnames[i] ) {
|
|
havespawnflags = true;
|
|
}
|
|
}
|
|
|
|
if ( havespawnflags ) {
|
|
char spawnline[80];
|
|
e->comments = addstr( e->comments,"Spawnflags\n" );
|
|
for ( i = 0 ; i < MAX_FLAGS ; i++ )
|
|
{
|
|
if ( *e->flagnames[i] ) {
|
|
sprintf( spawnline," %d - %s\n", 1 << i, e->flagnames[i] );
|
|
e->comments = addstr( e->comments,spawnline );
|
|
}
|
|
}
|
|
}
|
|
|
|
Eclass_InsertAlphabetized( e );
|
|
count++;
|
|
// Hydra: ttimo, I don't think this code is required...
|
|
// single ?
|
|
*Get_Eclass_E() = e;
|
|
Set_Eclass_Found( true );
|
|
if ( Get_Parsing_Single() ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Sys_Printf( "FGD Loaded %d entities.\n", count );
|
|
}
|
|
|
|
void Eclass_ScanFile( char *filename ){
|
|
int size;
|
|
char *data;
|
|
char temp[1024];
|
|
GSList *l_classes = NULL;
|
|
char token_debug[1024]; //++Hydra FIXME: cleanup this.
|
|
bool done = false;
|
|
int len,classtype;
|
|
|
|
char *token = Token();
|
|
|
|
QE_ConvertDOSToUnixName( temp, filename );
|
|
|
|
size = vfsLoadFullPathFile( filename, (void**)&data );
|
|
if ( size <= 0 ) {
|
|
Sys_FPrintf( SYS_ERR, "Eclass_ScanFile: %s not found\n", filename );
|
|
return;
|
|
}
|
|
Sys_Printf( "ScanFile: %s\n", temp );
|
|
|
|
// start parsing the file
|
|
StartTokenParsing( data );
|
|
|
|
// build a list of base classes first
|
|
|
|
while ( !done )
|
|
{
|
|
// find an @ sign.
|
|
do
|
|
{
|
|
if ( !GetToken( true ) ) {
|
|
done = true;
|
|
break;
|
|
}
|
|
} while ( token[0] != '@' );
|
|
|
|
strcpy( temp,token + 1 ); // skip the @
|
|
|
|
classtype = CLASS_NOCLASS;
|
|
if ( !stricmp( temp,"BaseClass" ) ) {
|
|
classtype = CLASS_BASECLASS;
|
|
}
|
|
if ( !stricmp( temp,"PointClass" ) ) {
|
|
classtype = CLASS_POINTCLASS;
|
|
}
|
|
if ( !stricmp( temp,"SolidClass" ) ) {
|
|
classtype = CLASS_SOLIDCLASS;
|
|
}
|
|
|
|
if ( classtype ) {
|
|
class_t *newclass = (class_t *) malloc( sizeof( class_s ) );
|
|
memset( newclass, 0, sizeof( class_s ) );
|
|
newclass->classtype = classtype;
|
|
|
|
while ( 1 )
|
|
{
|
|
GetTokenExtra( false,"(",false ); // option or =
|
|
strcpy( token_debug,token );
|
|
|
|
if ( !strcmp( token,"=" ) ) {
|
|
UnGetToken();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
strlower( token );
|
|
if ( !strcmp( token,"base" ) ) {
|
|
GetTokenExtra( false,"(",true ); // (
|
|
|
|
if ( !strcmp( token,"(" ) ) {
|
|
while ( GetTokenExtra( false,",)",false ) ) // option) or option,
|
|
{
|
|
newclass->l_baselist = g_slist_append( newclass->l_baselist, strdup( token ) );
|
|
|
|
GetTokenExtra( false,",)",true ); // , or )
|
|
if ( !strcmp( token,")" ) ) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else if ( !strcmp( token,"size" ) ) {
|
|
// parse (w h d) or (x y z, x y z)
|
|
|
|
GetTokenExtra( false,"(",true ); // (
|
|
if ( !strcmp( token,"(" ) ) {
|
|
int sizedone = false;
|
|
float w,h,d;
|
|
GetToken( false );
|
|
w = atof( token );
|
|
GetToken( false );
|
|
h = atof( token );
|
|
GetToken( false ); // number) or number ,
|
|
strcpy( temp,token );
|
|
len = strlen( temp );
|
|
if ( temp[len - 1] == ')' ) {
|
|
sizedone = true;
|
|
}
|
|
temp[len - 1] = 0;
|
|
d = atof( temp );
|
|
if ( sizedone ) {
|
|
// only one set of cordinates supplied, change the W,H,D to mins/maxs
|
|
newclass->boundingbox[0][0] = 0 - ( w / 2 );
|
|
newclass->boundingbox[1][0] = w / 2;
|
|
newclass->boundingbox[0][1] = 0 - ( h / 2 );
|
|
newclass->boundingbox[1][1] = h / 2;
|
|
newclass->boundingbox[0][2] = 0 - ( d / 2 );
|
|
newclass->boundingbox[1][2] = d / 2;
|
|
newclass->gotsize = true;
|
|
}
|
|
else
|
|
{
|
|
newclass->boundingbox[0][0] = w;
|
|
newclass->boundingbox[0][1] = h;
|
|
newclass->boundingbox[0][2] = d;
|
|
GetToken( false );
|
|
newclass->boundingbox[1][0] = atof( token );
|
|
GetToken( false );
|
|
newclass->boundingbox[1][1] = atof( token );
|
|
/*
|
|
GetToken(false); // "number)" or "number )"
|
|
strcpy(temp,token);
|
|
len = strlen(temp);
|
|
if (temp[len-1] == ')')
|
|
temp[len-1] = 0;
|
|
else
|
|
GetToken(false); // )
|
|
newclass->boundingbox[1][2] = atof(temp);
|
|
*/
|
|
GetTokenExtra( false,")",false ); // number
|
|
newclass->boundingbox[1][2] = atof( token );
|
|
newclass->gotsize = true;
|
|
GetTokenExtra( false,")",true ); // )
|
|
}
|
|
}
|
|
}
|
|
else if ( !strcmp( token,"color" ) ) {
|
|
GetTokenExtra( false,"(",true ); // (
|
|
if ( !strcmp( token,"(" ) ) {
|
|
// get the color values (0-255) and normalize them if required.
|
|
GetToken( false );
|
|
newclass->color[0] = atof( token );
|
|
if ( newclass->color[0] > 1 ) {
|
|
newclass->color[0] /= 255;
|
|
}
|
|
GetToken( false );
|
|
newclass->color[1] = atof( token );
|
|
if ( newclass->color[1] > 1 ) {
|
|
newclass->color[1] /= 255;
|
|
}
|
|
GetToken( false );
|
|
strcpy( temp,token );
|
|
len = strlen( temp );
|
|
if ( temp[len - 1] == ')' ) {
|
|
temp[len - 1] = 0;
|
|
}
|
|
newclass->color[2] = atof( temp );
|
|
if ( newclass->color[2] > 1 ) {
|
|
newclass->color[2] /= 255;
|
|
}
|
|
newclass->gotcolor = true;
|
|
}
|
|
}
|
|
else if ( !strcmp( token,"iconsprite" ) ) {
|
|
GetTokenExtra( false,"(",true ); // (
|
|
if ( !strcmp( token,"(" ) ) {
|
|
GetTokenExtra( false,")",false ); // filename)
|
|
// the model plugins will handle sprites too.
|
|
// newclass->sprite = strdup(token);
|
|
newclass->model = strdup( token );
|
|
GetTokenExtra( false,")",true ); // )
|
|
}
|
|
}
|
|
else if ( !strcmp( token,"model" ) ) {
|
|
GetTokenExtra( false,"(",true ); // (
|
|
if ( !strcmp( token,"(" ) ) {
|
|
GetTokenExtra( false,")",false ); // filename)
|
|
newclass->model = strdup( token );
|
|
GetTokenExtra( false,")",true ); // )
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Unsupported
|
|
GetToken( false ); // skip it.
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
GetToken( false ); // =
|
|
strcpy( token_debug,token );
|
|
if ( !strcmp( token,"=" ) ) {
|
|
GetToken( false );
|
|
newclass->classname = strdup( token );
|
|
}
|
|
|
|
// Get the description
|
|
if ( newclass->classtype != CLASS_BASECLASS ) {
|
|
GetToken( false );
|
|
if ( !strcmp( token,":" ) ) {
|
|
GetToken( false );
|
|
newclass->description = strdup( token );
|
|
}
|
|
else{ UnGetToken(); // no description
|
|
}
|
|
}
|
|
|
|
// now build the option list.
|
|
GetToken( true ); // [ or []
|
|
|
|
if ( strcmp( token,"[]" ) ) { // got some options ?
|
|
if ( !strcmp( token,"[" ) ) {
|
|
// yup
|
|
bool optioncomplete = false;
|
|
option_t *newoption;
|
|
|
|
while ( 1 )
|
|
{
|
|
GetToken( true );
|
|
if ( !strcmp( token,"]" ) ) {
|
|
break; // no more options
|
|
|
|
}
|
|
// parse the data and build the option_t
|
|
|
|
strcpy( temp,token );
|
|
len = strlen( temp );
|
|
char *ptr = strchr( temp,'(' );
|
|
|
|
if ( !ptr ) {
|
|
break;
|
|
}
|
|
|
|
newoption = (option_t *) malloc( sizeof( option_s ) );
|
|
memset( newoption, 0, sizeof( option_s ) );
|
|
|
|
*ptr++ = 0;
|
|
newoption->epairname = strdup( temp );
|
|
|
|
len = strlen( ptr );
|
|
if ( ptr[len - 1] != ')' ) {
|
|
break;
|
|
}
|
|
|
|
ptr[len - 1] = 0;
|
|
strlower( ptr );
|
|
if ( !strcmp( ptr,"integer" ) ) {
|
|
newoption->optiontype = OPTION_INTEGER;
|
|
}
|
|
else if ( !strcmp( ptr,"choices" ) ) {
|
|
newoption->optiontype = OPTION_CHOICES;
|
|
}
|
|
else if ( !strcmp( ptr,"flags" ) ) {
|
|
newoption->optiontype = OPTION_FLAGS;
|
|
}
|
|
else // string
|
|
{
|
|
newoption->optiontype = OPTION_STRING;
|
|
}
|
|
|
|
switch ( newoption->optiontype )
|
|
{
|
|
case OPTION_STRING:
|
|
case OPTION_INTEGER:
|
|
if ( !TokenAvailable() ) {
|
|
optioncomplete = true;
|
|
break;
|
|
}
|
|
GetToken( false ); // :
|
|
strcpy( token_debug,token );
|
|
if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) {
|
|
newoption->optioninfo = strdup( token + 1 );
|
|
}
|
|
else
|
|
{
|
|
GetToken( false );
|
|
newoption->optioninfo = strdup( token );
|
|
}
|
|
if ( TokenAvailable() ) { // default value ?
|
|
GetToken( false );
|
|
if ( !strcmp( token,":" ) ) {
|
|
if ( GetToken( false ) ) {
|
|
newoption->optiondefault = strdup( token );
|
|
optioncomplete = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
optioncomplete = true;
|
|
}
|
|
break;
|
|
|
|
case OPTION_CHOICES:
|
|
GetTokenExtra( false,":",true ); // : or :"something like this" (bah!)
|
|
strcpy( token_debug,token );
|
|
if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) {
|
|
if ( token[1] == '\"' ) {
|
|
strcpy( temp,token + 2 );
|
|
while ( 1 )
|
|
{
|
|
if ( !GetToken( false ) ) {
|
|
break;
|
|
}
|
|
strcat( temp," " );
|
|
strcat( temp,token );
|
|
len = strlen( temp );
|
|
if ( temp[len - 1] == '\"' ) {
|
|
temp[len - 1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
newoption->optioninfo = strdup( temp );
|
|
}
|
|
else
|
|
{
|
|
GetToken( false );
|
|
newoption->optioninfo = strdup( token );
|
|
}
|
|
GetToken( false ); // : or =
|
|
strcpy( token_debug,token );
|
|
if ( !strcmp( token,":" ) ) {
|
|
GetToken( false );
|
|
newoption->optiondefault = strdup( token );
|
|
}
|
|
else
|
|
{
|
|
UnGetToken();
|
|
}
|
|
// And Follow on...
|
|
case OPTION_FLAGS:
|
|
GetToken( false ); // : or =
|
|
strcpy( token_debug,token );
|
|
if ( strcmp( token,"=" ) ) { // missing ?
|
|
break;
|
|
}
|
|
|
|
GetToken( true ); // [
|
|
strcpy( token_debug,token );
|
|
if ( strcmp( token,"[" ) ) { // missing ?
|
|
break;
|
|
}
|
|
|
|
choice_t *newchoice;
|
|
while ( 1 )
|
|
{
|
|
GetTokenExtra( true,":",true ); // "]" or "number", or "number:"
|
|
strcpy( token_debug,token );
|
|
if ( !strcmp( token,"]" ) ) { // no more ?
|
|
optioncomplete = true;
|
|
break;
|
|
}
|
|
strcpy( temp,token );
|
|
len = strlen( temp );
|
|
if ( temp[len - 1] == ':' ) {
|
|
temp[len - 1] = 0;
|
|
}
|
|
else
|
|
{
|
|
GetToken( false ); // :
|
|
if ( strcmp( token,":" ) ) { // missing ?
|
|
break;
|
|
}
|
|
}
|
|
if ( !TokenAvailable() ) {
|
|
break;
|
|
}
|
|
GetToken( false ); // the name
|
|
|
|
newchoice = (choice_t *) malloc( sizeof( choice_s ) );
|
|
memset( newchoice, 0, sizeof( choice_s ) );
|
|
|
|
newchoice->value = atoi( temp );
|
|
newchoice->name = strdup( token );
|
|
|
|
newoption->choices = g_slist_append( newoption->choices, newchoice );
|
|
|
|
// ignore any remaining tokens on the line
|
|
while ( TokenAvailable() ) GetToken( false );
|
|
|
|
// and it we found a "]" on the end of the line, put it back in the queue.
|
|
if ( !strcmp( token,"]" ) ) {
|
|
UnGetToken();
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
// add option to the newclass
|
|
|
|
if ( optioncomplete ) {
|
|
if ( newoption ) {
|
|
// add it to the list.
|
|
newclass->l_optionlist = g_slist_append( newclass->l_optionlist, newoption );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf( "%WARNING: Parse error occured in '%s - %s'\n",classnames[newclass->classtype],newclass->classname );
|
|
Free_Option( newoption );
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnGetToken(); // shouldn't get here.
|
|
}
|
|
}
|
|
|
|
// add it to our list.
|
|
l_classes = g_slist_append( l_classes, newclass );
|
|
|
|
}
|
|
}
|
|
|
|
// finished with the file now.
|
|
g_free( data );
|
|
|
|
Sys_Printf( "FGD scan complete, building entities...\n" );
|
|
|
|
// Once we get here we should have a few (!) lists in memory that we
|
|
// can extract all the information required to build a the eclass_t structures.
|
|
|
|
Create_EClasses( l_classes );
|
|
|
|
// Free everything
|
|
|
|
GSList *p = l_classes;
|
|
while ( p )
|
|
{
|
|
class_t *tmpclass = (class_t *)p->data;
|
|
|
|
#ifdef FGD_VERBOSE
|
|
// DEBUG: dump the info...
|
|
Sys_Printf( "%s: %s (", classnames[tmpclass->classtype],tmpclass->classname );
|
|
for ( GSList *tmp = tmpclass->l_baselist; tmp != NULL; tmp = tmp->next )
|
|
{
|
|
if ( tmp != tmpclass->l_baselist ) {
|
|
Sys_Printf( ", " );
|
|
}
|
|
Sys_Printf( "%s", (char *)tmp->data );
|
|
}
|
|
if ( tmpclass->gotsize ) {
|
|
sprintf( temp,"(%.0f %.0f %.0f) - (%.0f %.0f %.0f)",tmpclass->boundingbox[0][0],
|
|
tmpclass->boundingbox[0][1],
|
|
tmpclass->boundingbox[0][2],
|
|
tmpclass->boundingbox[1][0],
|
|
tmpclass->boundingbox[1][1],
|
|
tmpclass->boundingbox[1][2] );
|
|
}
|
|
else{ strcpy( temp,"No Size" ); }
|
|
Sys_Printf( ") '%s' Size: %s",tmpclass->description ? tmpclass->description : "No description",temp );
|
|
if ( tmpclass->gotcolor ) {
|
|
sprintf( temp,"(%d %d %d)",tmpclass->color[0],
|
|
tmpclass->color[1],
|
|
tmpclass->color[2] );
|
|
}
|
|
else{ strcpy( temp,"No Color" ); }
|
|
Sys_Printf( " Color: %s Options:\n",temp );
|
|
if ( !tmpclass->l_optionlist ) {
|
|
Sys_Printf( " No Options\n" );
|
|
}
|
|
else
|
|
{
|
|
option_t *tmpoption;
|
|
int count;
|
|
GSList *olst;
|
|
for ( olst = tmpclass->l_optionlist, count = 1; olst != NULL; olst = olst->next, count++ )
|
|
{
|
|
tmpoption = (option_t *)olst->data;
|
|
Sys_Printf( " %d, Type: %s, EPair: %s\n", count,optionnames[tmpoption->optiontype], tmpoption->epairname );
|
|
|
|
choice_t *tmpchoice;
|
|
GSList *clst;
|
|
int ccount;
|
|
for ( clst = tmpoption->choices, ccount = 1; clst != NULL; clst = clst->next, ccount++ )
|
|
{
|
|
tmpchoice = (choice_t *)clst->data;
|
|
Sys_Printf( " %d, Value: %d, Name: %s\n", ccount, tmpchoice->value, tmpchoice->name );
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
// free the baselist.
|
|
ClearGSList( tmpclass->l_baselist );
|
|
Free_Class( tmpclass );
|
|
p = g_slist_remove( p, p->data );
|
|
}
|
|
|
|
}
|