mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 07:02:26 +00:00
653 lines
16 KiB
C++
653 lines
16 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
|
|
*/
|
|
|
|
// Hydra - FIXME : TTimo, We need to know what game + engine we're using for
|
|
// the halflife (not Q2) specific stuff
|
|
// we need an API for modules to get this info!
|
|
|
|
//
|
|
// parses quake3 map format into internal objects
|
|
//
|
|
|
|
#include "plugin.h"
|
|
|
|
// cmdlib
|
|
extern void ExtractFileName( const char *path, char *dest );
|
|
|
|
extern int g_MapVersion;
|
|
int abortcode; // see imap.h for values.
|
|
|
|
// Start of half-life specific stuff
|
|
|
|
GSList *g_WadList; // halflife specific.
|
|
GSList *g_TextureNameCache; // halflife specific.
|
|
|
|
// NOTE TTimo: yuck..
|
|
void FreeGSList( GSList *l ){
|
|
while ( l )
|
|
{
|
|
free( l->data );
|
|
l = g_slist_remove( l, l->data );
|
|
}
|
|
}
|
|
|
|
// NOTE TTimo: ideally, this would be using Str functions instead
|
|
void trim( char *str ){
|
|
int len;
|
|
len = strlen( str );
|
|
while ( str[--len] == ' ' )
|
|
str[len] = 0;
|
|
}
|
|
|
|
void BuildWadList( char *wadstr ){
|
|
char wads[2048]; // change to CString usage ?
|
|
wads[0] = 0;
|
|
char *p1,*p2;
|
|
char cleanwadname[QER_MAX_NAMELEN];
|
|
|
|
g_WadList = NULL;
|
|
|
|
strcpy( wads,wadstr );
|
|
QE_ConvertDOSToUnixName( wads,wads );
|
|
|
|
// ok, we got the list of ; delimited wads, now split it into a GSList that contains
|
|
// just the wad names themselves.
|
|
|
|
p1 = wads;
|
|
|
|
do
|
|
{
|
|
p2 = strchr( p1,';' );
|
|
if ( p2 ) {
|
|
*p2 = 0; // swap the ; with a null terminator
|
|
|
|
}
|
|
if ( strchr( p1,'/' ) || strchr( p1,'\\' ) ) {
|
|
ExtractFileName( p1,cleanwadname );
|
|
|
|
trim( cleanwadname );
|
|
|
|
if ( *cleanwadname ) {
|
|
g_WadList = g_slist_append( g_WadList, strdup( cleanwadname ) );
|
|
Sys_Printf( "wad: %s\n",cleanwadname );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trim( p1 );
|
|
if ( *p1 ) {
|
|
g_WadList = g_slist_append( g_WadList, strdup( p1 ) );
|
|
Sys_Printf( "wad: %s\n",p1 );
|
|
}
|
|
}
|
|
if ( p2 ) {
|
|
p1 = p2 + 1; // point back to the remainder of the string
|
|
}
|
|
else{
|
|
p1 = NULL; // make it so we exit the loop.
|
|
|
|
}
|
|
} while ( p1 );
|
|
|
|
// strip the ".wad" extensions.
|
|
for ( GSList *l = g_WadList; l != NULL ; l = l->next )
|
|
{
|
|
p1 = (char *)l->data;
|
|
|
|
if ( p1[strlen( p1 ) - 4] == '.' ) {
|
|
p1[strlen( p1 ) - 4] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: usefulness of this cache sounds very discutable
|
|
char *CheckCacheForTextureName( const char *cleantexturename ){
|
|
char *str;
|
|
int len;
|
|
GSList *l;
|
|
|
|
// search our little cache first to speed things up.
|
|
// cache strings are stored as "<cleanname>;<actualnameshader>"
|
|
len = strlen( cleantexturename );
|
|
for ( l = g_TextureNameCache; l != NULL ; l = l->next )
|
|
{
|
|
str = (char *)l->data;
|
|
if ( ( strnicmp( cleantexturename,str,len ) == 0 ) && ( str[len] == ';' ) ) { // must do in this order or we'll get an access violation, even though it's slower.
|
|
return ( str + len + 1 ); // skip the delimiter ;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *AddToCache( const char *cleantexturename, const char *actualname ){
|
|
char *cachestr;
|
|
cachestr = (char *)malloc( strlen( cleantexturename ) + 1 + strlen( actualname ) + 1 ); // free()'d when g_TextureNameCache is freed
|
|
sprintf( cachestr,"%s;%s",cleantexturename,actualname );
|
|
g_TextureNameCache = g_slist_append( g_TextureNameCache, cachestr );
|
|
return cachestr;
|
|
}
|
|
|
|
char *SearchWadsForTextureName( const char *cleantexturename ){
|
|
char *str;
|
|
char *wadname;
|
|
char *actualtexturename = NULL;
|
|
GSList *l;
|
|
int count;
|
|
|
|
actualtexturename = CheckCacheForTextureName( cleantexturename );
|
|
if ( actualtexturename ) {
|
|
return actualtexturename;
|
|
}
|
|
|
|
// still here ? guess it's not in the cache then!
|
|
|
|
// search the wads listed in the worldspawn "wad" key
|
|
for ( l = g_WadList; l != NULL && actualtexturename == NULL ; l = l->next )
|
|
{
|
|
wadname = (char *)l->data;
|
|
|
|
str = new char[strlen( wadname ) + strlen( cleantexturename ) + 9 + 1 + 4 + 1];
|
|
|
|
// hlw here is ok as we never have anything other than hlw files in a wad.
|
|
sprintf( str,"textures/%s/%s.hlw",wadname,cleantexturename );
|
|
count = vfsGetFileCount( str, VFS_SEARCH_PAK ); // only search pack files
|
|
// LordHavoc: hacked in .mip loading here
|
|
if ( !count ) {
|
|
sprintf( str,"textures/%s/%s.mip",wadname,cleantexturename );
|
|
count = vfsGetFileCount( str, VFS_SEARCH_PAK ); // only search pack files
|
|
}
|
|
|
|
if ( count > 0 ) {
|
|
// strip the extension, build the cache string and add the the cache
|
|
str[strlen( str ) - 4] = 0;
|
|
|
|
actualtexturename = AddToCache( cleantexturename,str );
|
|
|
|
//point the return value to the actual name, not what we add to the cache
|
|
actualtexturename += 1 + strlen( cleantexturename );
|
|
}
|
|
delete [] str;
|
|
}
|
|
return actualtexturename;
|
|
}
|
|
// End of half-life specific stuff
|
|
|
|
void Patch_Parse( patchMesh_t *pPatch ){
|
|
int i, j;
|
|
char *str;
|
|
|
|
char *token = Token();
|
|
|
|
GetToken( true ); //{
|
|
|
|
// parse shader name
|
|
GetToken( true );
|
|
str = new char[strlen( token ) + 10];
|
|
strcpy( str, "textures/" );
|
|
strcpy( str + 9, token );
|
|
pPatch->pShader = QERApp_Shader_ForName( str );
|
|
pPatch->d_texture = pPatch->pShader->getTexture();
|
|
delete [] str;
|
|
|
|
GetToken( true ); //(
|
|
|
|
// parse matrix dimensions
|
|
GetToken( false );
|
|
pPatch->width = atoi( token );
|
|
GetToken( false );
|
|
pPatch->height = atoi( token );
|
|
|
|
// ignore contents/flags/value
|
|
GetToken( false );
|
|
GetToken( false );
|
|
GetToken( false );
|
|
|
|
GetToken( false ); //)
|
|
|
|
// parse matrix
|
|
GetToken( true ); //(
|
|
for ( i = 0; i < pPatch->width; i++ )
|
|
{
|
|
GetToken( true ); //(
|
|
for ( j = 0; j < pPatch->height; j++ )
|
|
{
|
|
GetToken( false ); //(
|
|
|
|
GetToken( false );
|
|
pPatch->ctrl[i][j].xyz[0] = atof( token );
|
|
GetToken( false );
|
|
pPatch->ctrl[i][j].xyz[1] = atof( token );
|
|
GetToken( false );
|
|
pPatch->ctrl[i][j].xyz[2] = atof( token );
|
|
GetToken( false );
|
|
pPatch->ctrl[i][j].st[0] = atof( token );
|
|
GetToken( false );
|
|
pPatch->ctrl[i][j].st[1] = atof( token );
|
|
|
|
GetToken( false ); //)
|
|
}
|
|
GetToken( false ); //)
|
|
}
|
|
GetToken( true ); //)
|
|
|
|
GetToken( true ); //}
|
|
}
|
|
|
|
void Face_Parse( face_t *face, bool bAlternateTexdef = false ){
|
|
int i, j;
|
|
char *str;
|
|
bool bworldcraft = false;
|
|
|
|
char *token = Token();
|
|
|
|
// parse planepts
|
|
str = NULL;
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
GetToken( true ); //(
|
|
for ( j = 0; j < 3; j++ )
|
|
{
|
|
GetToken( false );
|
|
face->planepts[i][j] = atof( token );
|
|
}
|
|
GetToken( false ); //)
|
|
}
|
|
|
|
if ( bAlternateTexdef ) {
|
|
// parse alternate texdef
|
|
GetToken( false ); // (
|
|
GetToken( false ); // (
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
GetToken( false );
|
|
face->brushprimit_texdef.coords[0][i] = atof( token );
|
|
}
|
|
GetToken( false ); // )
|
|
GetToken( false ); // (
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
GetToken( false );
|
|
face->brushprimit_texdef.coords[1][i] = atof( token );
|
|
}
|
|
GetToken( false ); // )
|
|
GetToken( false ); // )
|
|
}
|
|
|
|
|
|
// parse shader name
|
|
GetToken( false ); // shader
|
|
|
|
// if we're loading a halflife map then we don't have a relative texture name
|
|
// we just get <texturename>. So we need to convert this to a relative name
|
|
// like this: "textures/<wadname>/shader", so we use vfsFileFile to get the filename.
|
|
|
|
// *** IMPORTANT ***
|
|
// For Halflife we need to see if the texture is in wads listed in the
|
|
// map's worldspawn "wad" e-pair. If we don't then the image used will be the
|
|
// first image with this texture name that is found in any of the wads on the
|
|
// user's system. this is not a huge problem, because the map compiler obeys
|
|
// the "wad" epair when compiling the map, but the user might end up looking at
|
|
// the wrong texture in the editor. (more of a problem if the texture we use
|
|
// here has a different size from the one in the wad the map compiler uses...)
|
|
|
|
// Hydra: - TTimo: I looked all over for other places to put this, but really it
|
|
// is an issue with map loading (because of a limitation of halflife/q2 map format)
|
|
// so it's gone in here, it also stops incorrect shader/texdef names getting used
|
|
// in the radiant core and it's modules which we'd only have to change later on.
|
|
// (either in map_importentities() or the shader module). so it's actually cleaner
|
|
// in the long run, even if a little odd. And it keeps more game specific stuff
|
|
// OUT of the core, which is a good thing.
|
|
|
|
if ( g_MapVersion == MAPVERSION_HL ) {
|
|
qboolean done = false;
|
|
|
|
// FIXME: This bit is halflife specific.
|
|
// look in the list of wads supplied in the worldspawn "wad" key/pair for the
|
|
// texture first, if it's not in any then we carry on searching the vfs for it
|
|
// as usual.
|
|
|
|
// each time we find a texture, we add it to the a cache
|
|
// so we don't have to hunt the vfs for it each time.
|
|
// See SearchWadsForTextureName() and AddToCache() above for cache stuff
|
|
|
|
char *wadname;
|
|
wadname = SearchWadsForTextureName( token );
|
|
|
|
if ( wadname ) {
|
|
face->texdef.SetName( wadname );
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
// using the cache below means that this message is only ever printed out once!
|
|
Sys_Printf( "WARNING: could not find \"%s\" in any listed wad files, searching all wad files instead!\n",token );
|
|
}
|
|
// end of half-life specific bit.
|
|
|
|
// check the cache!
|
|
if ( !done ) {
|
|
str = CheckCacheForTextureName( token );
|
|
if ( str ) {
|
|
face->texdef.SetName( str );
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
if ( !done ) {
|
|
char *fullpath;
|
|
|
|
str = new char[strlen( token ) + 4 + 1];
|
|
|
|
// FIXME: halflife specific file extension, we'll have to support Q2/Q1 formats
|
|
// and maybe tga texture format for HL here too..
|
|
sprintf( str,"%s.hlw",token );
|
|
fullpath = vfsGetFullPath( str,0,VFS_SEARCH_PAK | VFS_SEARCH_DIR );
|
|
|
|
// MIP support for quake
|
|
if ( !fullpath ) {
|
|
sprintf( str,"%s.mip",token );
|
|
fullpath = vfsGetFullPath( str, 0, 0 );
|
|
}
|
|
|
|
// TGA support in halflife ?
|
|
/*
|
|
if (!fullpath)
|
|
{
|
|
sprintf(str,"%s.tga",token);
|
|
fullpath = vfsGetFullPath(str);
|
|
}
|
|
*/
|
|
delete [] str;
|
|
|
|
if ( fullpath ) {
|
|
// strip the extension.
|
|
int len = strlen( fullpath );
|
|
if ( fullpath[len - 4] == '.' ) {
|
|
fullpath[len - 4] = '\0';
|
|
}
|
|
|
|
// and set the correct name!
|
|
face->texdef.SetName( fullpath );
|
|
AddToCache( token,fullpath );
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf( "WARNING: could not find \"%s\" in the vfs search path\n",token );
|
|
str = new char[strlen( token ) + 10];
|
|
strcpy( str, "textures/" );
|
|
strcpy( str + 9, token );
|
|
face->texdef.SetName( str );
|
|
AddToCache( token,str );
|
|
delete [] str;
|
|
}
|
|
}
|
|
}
|
|
else // !MAPVERSION_HL
|
|
{
|
|
str = new char[strlen( token ) + 10];
|
|
strcpy( str, "textures/" );
|
|
strcpy( str + 9, token );
|
|
face->texdef.SetName( str );
|
|
delete [] str;
|
|
}
|
|
|
|
if ( !bAlternateTexdef ) {
|
|
if ( g_MapVersion == MAPVERSION_HL ) { // Q1 as well ?
|
|
GetToken( false );
|
|
if ( token[0] == '[' && token[1] == '\0' ) {
|
|
bworldcraft = true;
|
|
|
|
GetToken( false ); // UAxis[0]
|
|
GetToken( false ); // UAxis[1]
|
|
GetToken( false ); // UAxis[2]
|
|
|
|
GetToken( false ); // shift
|
|
face->texdef.shift[0] = atof( token );
|
|
|
|
GetToken( false ); // ]
|
|
|
|
GetToken( false ); // [
|
|
GetToken( false ); // VAxis[0]
|
|
GetToken( false ); // VAxis[1]
|
|
GetToken( false ); // VAxis[2]
|
|
|
|
GetToken( false ); // shift
|
|
face->texdef.shift[1] = atof( token );
|
|
|
|
GetToken( false ); // ]
|
|
|
|
// rotation is derived from the U and V axes.
|
|
// ZHLT ignores this setting even if present in a .map file.
|
|
GetToken( false );
|
|
face->texdef.rotate = atof( token );
|
|
|
|
// Scales
|
|
GetToken( false );
|
|
face->texdef.scale[0] = atof( token );
|
|
GetToken( false );
|
|
face->texdef.scale[1] = atof( token );
|
|
}
|
|
else
|
|
{
|
|
UnGetToken();
|
|
}
|
|
}
|
|
|
|
if ( !bworldcraft ) { // !MAPVERSION_HL
|
|
// parse texdef
|
|
GetToken( false );
|
|
face->texdef.shift[0] = atof( token );
|
|
GetToken( false );
|
|
face->texdef.shift[1] = atof( token );
|
|
GetToken( false );
|
|
face->texdef.rotate = atof( token );
|
|
GetToken( false );
|
|
face->texdef.scale[0] = atof( token );
|
|
GetToken( false );
|
|
face->texdef.scale[1] = atof( token );
|
|
}
|
|
}
|
|
// parse the optional contents/flags/value
|
|
if ( !bworldcraft && TokenAvailable() ) {
|
|
GetToken( true );
|
|
if ( isdigit( token[0] ) ) {
|
|
face->texdef.contents = atoi( token );
|
|
GetToken( false );
|
|
face->texdef.flags = atoi( token );
|
|
GetToken( false );
|
|
face->texdef.value = atoi( token );
|
|
}
|
|
else
|
|
{
|
|
UnGetToken();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Primitive_Parse( brush_t *pBrush ){
|
|
char *token = Token();
|
|
|
|
GetToken( true );
|
|
if ( !strcmp( token, "patchDef2" ) ) {
|
|
pBrush->patchBrush = true;
|
|
pBrush->pPatch = Patch_Alloc();
|
|
pBrush->pPatch->pSymbiot = pBrush;
|
|
Patch_Parse( pBrush->pPatch );
|
|
GetToken( true ); //}
|
|
|
|
// A patchdef should never be loaded from a quake2 map file
|
|
// so we just return false and the brush+patch gets freed
|
|
// and the user gets told.
|
|
if ( g_MapVersion != MAPVERSION_Q3 ) {
|
|
// FIXME: Hydra - I wanted to write out a line number here, but I can't because there's no API to access the core's "scriptline" variable.
|
|
Syn_Printf( "ERROR: patchDef2's are not supported in Quake%d format .map files!\n",g_MapVersion );
|
|
abortcode = MAP_WRONGVERSION;
|
|
return false;
|
|
}
|
|
}
|
|
else if ( !strcmp( token, "brushDef" ) ) {
|
|
pBrush->bBrushDef = true;
|
|
GetToken( true ); // {
|
|
while ( 1 )
|
|
{
|
|
face_t *f = pBrush->brush_faces;
|
|
pBrush->brush_faces = Face_Alloc();
|
|
Face_Parse( pBrush->brush_faces, true );
|
|
pBrush->brush_faces->next = f;
|
|
// check for end of brush
|
|
GetToken( true );
|
|
if ( strcmp( token,"}" ) == 0 ) {
|
|
break;
|
|
}
|
|
UnGetToken();
|
|
}
|
|
GetToken( true ); // }
|
|
}
|
|
else
|
|
{
|
|
UnGetToken();
|
|
while ( 1 )
|
|
{
|
|
face_t *f = pBrush->brush_faces;
|
|
pBrush->brush_faces = Face_Alloc();
|
|
Face_Parse( pBrush->brush_faces );
|
|
pBrush->brush_faces->next = f;
|
|
|
|
// check for end of brush
|
|
GetToken( true );
|
|
if ( strcmp( token,"}" ) == 0 ) {
|
|
break;
|
|
}
|
|
UnGetToken();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Entity_Parse( entity_t *pEntity ){
|
|
brush_t *pBrush;
|
|
// CPtrArray *brushes = NULL;
|
|
char temptoken[1024];
|
|
|
|
char *token = Token();
|
|
|
|
while ( 1 )
|
|
{
|
|
GetToken( true ); // { or } or epair
|
|
if ( !strcmp( token, "}" ) ) {
|
|
break;
|
|
}
|
|
else if ( !strcmp( token, "{" ) ) {
|
|
|
|
pBrush = Brush_Alloc();
|
|
if ( Primitive_Parse( pBrush ) ) {
|
|
( (CPtrArray*)pEntity->pData )->Add( pBrush );
|
|
}
|
|
else {
|
|
Brush_Free( pBrush, true );
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
strcpy( temptoken, token );
|
|
GetToken( false );
|
|
|
|
SetKeyValue( pEntity, temptoken, token );
|
|
|
|
if ( g_MapVersion == MAPVERSION_HL ) {
|
|
// if we've not god a "wads" key/pair already, then break it into a list.
|
|
if ( !g_WadList && ( stricmp( temptoken,"wad" ) == 0 ) ) {
|
|
BuildWadList( token );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map_Read( IDataStream *in, CPtrArray *map ){
|
|
entity_t *pEntity;
|
|
char *buf;
|
|
|
|
unsigned long len = in->GetLength();
|
|
buf = new char[len + 1];
|
|
in->Read( buf, len );
|
|
buf[len] = '\0';
|
|
StartTokenParsing( buf );
|
|
abortcode = MAP_NOERROR;
|
|
|
|
while ( abortcode == MAP_NOERROR )
|
|
{
|
|
if ( !GetToken( true ) ) { // { or NULL
|
|
break;
|
|
}
|
|
pEntity = Entity_Alloc();
|
|
pEntity->pData = new CPtrArray;
|
|
Entity_Parse( pEntity );
|
|
map->Add( pEntity );
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
if ( abortcode != MAP_NOERROR ) {
|
|
int num_ents, num_brushes,i,j;
|
|
entity_t *e;
|
|
CPtrArray *brushes;
|
|
|
|
num_ents = map->GetSize();
|
|
for ( i = 0; i < num_ents; i++ )
|
|
{
|
|
e = (entity_t*)map->GetAt( i );
|
|
brushes = (CPtrArray*)e->pData;
|
|
num_brushes = brushes->GetSize();
|
|
for ( j = 0; j < num_brushes; j++ )
|
|
{
|
|
Brush_Free( (brush_t *)brushes->GetAt( j ), true );
|
|
}
|
|
brushes->RemoveAll();
|
|
delete brushes;
|
|
Entity_Free( e );
|
|
}
|
|
map->RemoveAll();
|
|
}
|
|
}
|
|
|
|
void Map_ReadQ3( IDataStream *in, CPtrArray *map ){
|
|
g_MapVersion = MAPVERSION_Q3;
|
|
Map_Read( in,map );
|
|
}
|
|
|
|
void Map_ReadHL( IDataStream *in, CPtrArray *map ){
|
|
g_WadList = NULL;
|
|
g_TextureNameCache = NULL;
|
|
|
|
g_MapVersion = MAPVERSION_HL;
|
|
Map_Read( in,map );
|
|
|
|
FreeGSList( g_TextureNameCache );
|
|
FreeGSList( g_WadList );
|
|
}
|
|
|
|
void Map_ReadQ2( IDataStream *in, CPtrArray *map ){
|
|
g_MapVersion = MAPVERSION_Q2;
|
|
Map_Read( in,map );
|
|
}
|