mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 15:11:54 +00:00
1137 lines
33 KiB
C++
1137 lines
33 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;
|
||
|
|
||
|
extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
}
|