Xash3D FWGS engine.
 
 
 
 

3403 lines
85 KiB

/*
mod_bmodel.c - loading & handling world and brushmodels
Copyright (C) 2016 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 "mod_local.h"
#include "sprite.h"
#include "xash3d_mathlib.h"
#include "alias.h"
#include "studio.h"
#include "wadfile.h"
#include "world.h"
#include "enginefeatures.h"
#include "client.h"
#include "server.h" // LUMP_ error codes
#include "ref_common.h"
#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 )
typedef struct wadlist_s
{
char wadnames[MAX_MAP_WADS][32];
int wadusage[MAX_MAP_WADS];
int count;
} wadlist_t;
typedef struct leaflist_s
{
int count;
int maxcount;
qboolean overflowed;
int *list;
vec3_t mins, maxs;
int topnode; // for overflows where each leaf can't be stored individually
} leaflist_t;
typedef struct
{
// generic lumps
dmodel_t *submodels;
size_t numsubmodels;
dvertex_t *vertexes;
size_t numvertexes;
dplane_t *planes;
size_t numplanes;
union
{
dnode_t *nodes;
dnode32_t *nodes32;
};
size_t numnodes;
union
{
dleaf_t *leafs;
dleaf32_t *leafs32;
};
size_t numleafs;
union
{
dclipnode_t *clipnodes;
dclipnode32_t *clipnodes32;
};
size_t numclipnodes;
dtexinfo_t *texinfo;
size_t numtexinfo;
union
{
dmarkface_t *markfaces;
dmarkface32_t *markfaces32;
};
size_t nummarkfaces;
dsurfedge_t *surfedges;
size_t numsurfedges;
union
{
dedge_t *edges;
dedge32_t *edges32;
};
size_t numedges;
union
{
dface_t *surfaces;
dface32_t *surfaces32;
};
size_t numsurfaces;
dfaceinfo_t *faceinfo;
size_t numfaceinfo;
// array lumps
byte *visdata;
size_t visdatasize;
byte *lightdata;
size_t lightdatasize;
byte *deluxdata;
size_t deluxdatasize;
byte *shadowdata;
size_t shadowdatasize;
byte *entdata;
size_t entdatasize;
// lumps that required personal handler
dmiptexlump_t *textures;
size_t texdatasize;
// intermediate arrays (pointers will lost after loading, but keep the data)
color24 *deluxedata_out; // deluxemap data pointer
byte *shadowdata_out; // occlusion data pointer
dclipnode32_t *clipnodes_out; // temporary 32-bit array to hold clipnodes
// misc stuff
wadlist_t wadlist;
int lightmap_samples; // samples per lightmap (1 or 3)
int version; // model version
qboolean isworld;
qboolean isbsp30ext;
} dbspmodel_t;
typedef struct
{
const char *lumpname;
size_t entrysize;
size_t maxcount;
size_t count;
} mlumpstat_t;
typedef struct
{
char name[64]; // just for debug
// count errors and warnings
int numerrors;
int numwarnings;
} loadstat_t;
#define CHECK_OVERFLOW BIT( 0 ) // if some of lumps will be overflowed this non fatal for us. But some lumps are critical. mark them
#define USE_EXTRAHEADER BIT( 1 )
#define LUMP_SAVESTATS BIT( 0 )
#define LUMP_TESTONLY BIT( 1 )
#define LUMP_SILENT BIT( 2 )
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
typedef struct
{
int lumpnumber;
const size_t mincount;
const size_t maxcount;
const int entrysize;
const int entrysize32; // alternative (-1 by default)
const char *loadname;
int flags;
const void **dataptr;
size_t *count;
} mlumpinfo_t;
world_static_t world;
static dbspmodel_t srcmodel;
static loadstat_t loadstat;
static model_t *worldmodel;
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS];
static mlumpinfo_t srclumps[HEADER_LUMPS] =
{
{ LUMP_ENTITIES, 32, MAX_MAP_ENTSTRING, sizeof( byte ), -1, "entities", 0, (const void **)&srcmodel.entdata, &srcmodel.entdatasize },
{ LUMP_PLANES, 1, MAX_MAP_PLANES, sizeof( dplane_t ), -1, "planes", 0, (const void **)&srcmodel.planes, &srcmodel.numplanes },
{ LUMP_TEXTURES, 1, MAX_MAP_MIPTEX, sizeof( byte ), -1, "textures", 0, (const void **)&srcmodel.textures, &srcmodel.texdatasize },
{ LUMP_VERTEXES, 0, MAX_MAP_VERTS, sizeof( dvertex_t ), -1, "vertexes", 0, (const void **)&srcmodel.vertexes, &srcmodel.numvertexes },
{ LUMP_VISIBILITY, 0, MAX_MAP_VISIBILITY, sizeof( byte ), -1, "visibility", 0, (const void **)&srcmodel.visdata, &srcmodel.visdatasize },
{ LUMP_NODES, 1, MAX_MAP_NODES, sizeof( dnode_t ), sizeof( dnode32_t ), "nodes", CHECK_OVERFLOW, (const void **)&srcmodel.nodes, &srcmodel.numnodes },
{ LUMP_TEXINFO, 0, MAX_MAP_TEXINFO, sizeof( dtexinfo_t ), -1, "texinfo", CHECK_OVERFLOW, (const void **)&srcmodel.texinfo, &srcmodel.numtexinfo },
{ LUMP_FACES, 0, MAX_MAP_FACES, sizeof( dface_t ), sizeof( dface32_t ), "faces", CHECK_OVERFLOW, (const void **)&srcmodel.surfaces, &srcmodel.numsurfaces },
{ LUMP_LIGHTING, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "lightmaps", 0, (const void **)&srcmodel.lightdata, &srcmodel.lightdatasize },
{ LUMP_CLIPNODES, 0, MAX_MAP_CLIPNODES, sizeof( dclipnode_t ), sizeof( dclipnode32_t ), "clipnodes", 0, (const void **)&srcmodel.clipnodes, &srcmodel.numclipnodes },
{ LUMP_LEAFS, 1, MAX_MAP_LEAFS, sizeof( dleaf_t ), sizeof( dleaf32_t ), "leafs", CHECK_OVERFLOW, (const void **)&srcmodel.leafs, &srcmodel.numleafs },
{ LUMP_MARKSURFACES, 0, MAX_MAP_MARKSURFACES, sizeof( dmarkface_t ), sizeof( dmarkface32_t ), "markfaces", 0, (const void **)&srcmodel.markfaces, &srcmodel.nummarkfaces },
{ LUMP_EDGES, 0, MAX_MAP_EDGES, sizeof( dedge_t ), sizeof( dedge32_t ), "edges", 0, (const void **)&srcmodel.edges, &srcmodel.numedges },
{ LUMP_SURFEDGES, 0, MAX_MAP_SURFEDGES, sizeof( dsurfedge_t ), -1, "surfedges", 0, (const void **)&srcmodel.surfedges, &srcmodel.numsurfedges },
{ LUMP_MODELS, 1, MAX_MAP_MODELS, sizeof( dmodel_t ), -1, "models", CHECK_OVERFLOW, (const void **)&srcmodel.submodels, &srcmodel.numsubmodels },
};
static mlumpinfo_t extlumps[EXTRA_LUMPS] =
{
{ LUMP_LIGHTVECS, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "deluxmaps", USE_EXTRAHEADER, (const void **)&srcmodel.deluxdata, &srcmodel.deluxdatasize },
{ LUMP_FACEINFO, 0, MAX_MAP_FACEINFO, sizeof( dfaceinfo_t ), -1, "faceinfos", CHECK_OVERFLOW|USE_EXTRAHEADER, (const void **)&srcmodel.faceinfo, &srcmodel.numfaceinfo },
{ LUMP_SHADOWMAP, 0, MAX_MAP_LIGHTING / 3, sizeof( byte ), -1, "shadowmap", USE_EXTRAHEADER, (const void **)&srcmodel.shadowdata, &srcmodel.shadowdatasize },
};
/*
===============================================================================
Static helper functions
===============================================================================
*/
static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i )
{
if( i < 0 || i >= bmod->textures->nummiptex )
return NULL;
if( bmod->textures->dataofs[i] == -1 )
return NULL;
return (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] );
}
// Returns index of WAD that texture was found in, or -1 if not found.
static int Mod_FindTextureInWadList( wadlist_t *list, const char *name, char *dst, size_t size )
{
int i;
if( !list || !COM_CheckString( name ))
return -1;
// check wads in reverse order
for( i = list->count - 1; i >= 0; i-- )
{
char path[MAX_VA_STRING];
Q_snprintf( path, sizeof( path ), "%s.wad/%s.mip", list->wadnames[i], name );
if( FS_FileExists( path, false ))
{
if( dst && size > 0 )
Q_strncpy( dst, path, size );
return i;
}
}
return -1;
}
static fs_offset_t Mod_CalculateMipTexSize( mip_t *mt, qboolean palette )
{
if( !mt )
return 0;
return sizeof( *mt ) + (( mt->width * mt->height * 85 ) >> 6 ) +
( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 );
}
static qboolean Mod_CalcMipTexUsesCustomPalette( dbspmodel_t *bmod, int textureIndex )
{
int nextTextureIndex = 0;
mip_t *mipTex;
fs_offset_t size, remainingBytes;
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
if( !mipTex || mipTex->offsets[0] <= 0 )
return false;
// Calculate the size assuming we are not using a custom palette.
size = Mod_CalculateMipTexSize( mipTex, false );
// Compute next data offset to determine allocated miptex space
for( nextTextureIndex = textureIndex + 1; nextTextureIndex < loadmodel->numtextures; nextTextureIndex++ )
{
int nextOffset = bmod->textures->dataofs[nextTextureIndex];
if( nextOffset != -1 )
{
remainingBytes = nextOffset - ( bmod->textures->dataofs[textureIndex] + size );
return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;
}
}
// There was no other miptex after this one.
// See if there is enough space between the end and our offset.
remainingBytes = bmod->texdatasize - ( bmod->textures->dataofs[textureIndex] + size );
return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;
}
static qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex )
{
if( !tex )
return false;
// Not an animated texture name
if( tex->name[0] != '-' && tex->name[0] != '+' )
return false;
// Name implies texture is animated - check second character is valid.
if( !( tex->name[1] >= '0' && tex->name[1] <= '9' ) &&
!( tex->name[1] >= 'a' && tex->name[1] <= 'j' ))
{
Con_Printf( S_ERROR "Mod_NameImpliesTextureIsAnimated: animating texture \"%s\" has invalid name\n", tex->name );
return false;
}
return true;
}
static void Mod_CreateDefaultTexture( texture_t **texture )
{
texture_t *tex;
// Pointer must be valid, and value pointed to must be null.
if( !texture || *texture != NULL )
return;
*texture = tex = Mem_Calloc( loadmodel->mempool, sizeof( *tex ));
Q_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name ));
#if !XASH_DEDICATED
if( !Host_IsDedicated( ))
{
tex->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
tex->width = 16;
tex->height = 16;
}
#endif // XASH_DEDICATED
}
/*
===============================================================================
MAP PROCESSING
===============================================================================
*/
/*
=================
Mod_LoadLump
generic loader
=================
*/
static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, int flags )
{
int version = ((dheader_t *)in)->version;
size_t numelems, real_entrysize;
char msg1[32], msg2[32];
dlump_t *l = NULL;
if( FBitSet( info->flags, USE_EXTRAHEADER ))
{
dextrahdr_t *header = (dextrahdr_t *)((byte *)in + sizeof( dheader_t ));
if( header->id != IDEXTRAHEADER || header->version != EXTRA_VERSION )
return;
l = &header->lumps[info->lumpnumber];
}
else
{
dheader_t *header = (dheader_t *)in;
l = &header->lumps[info->lumpnumber];
}
// lump is unused by engine for some reasons ?
if( !l || info->entrysize <= 0 || info->maxcount <= 0 )
return;
real_entrysize = info->entrysize; // default
// analyze real entrysize
if( version == QBSP2_VERSION && info->entrysize32 > 0 )
{
// always use alternate entrysize for BSP2
real_entrysize = info->entrysize32;
}
else if( version == HLBSP_VERSION && FBitSet( flags, LUMP_BSP30EXT ) && info->lumpnumber == LUMP_CLIPNODES )
{
// if this map is bsp30ext, try to guess extended clipnodes
if((( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize32 ) >= MAX_MAP_CLIPNODES_HLBSP ))
{
real_entrysize = info->entrysize32;
}
}
// bmodels not required the visibility
if( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY )
SetBits( flags, LUMP_SILENT ); // shut up warning
// fill the stats for world
if( FBitSet( flags, LUMP_SAVESTATS ))
{
stat->lumpname = info->loadname;
stat->entrysize = real_entrysize;
stat->maxcount = info->maxcount;
if( real_entrysize != 0 )
stat->count = l->filelen / real_entrysize;
}
Q_strncpy( msg1, info->loadname, sizeof( msg1 ));
Q_strncpy( msg2, info->loadname, sizeof( msg2 ));
msg2[0] = Q_toupper( msg2[0] ); // first letter in cap
// lump is not present
if( l->filelen <= 0 )
{
// don't warn about extra lumps - it's optional
if( !FBitSet( info->flags, USE_EXTRAHEADER ))
{
// some data array that may be optional
if( real_entrysize == sizeof( byte ))
{
if( !FBitSet( flags, LUMP_SILENT ))
{
Con_DPrintf( S_WARN "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numwarnings++;
}
}
else if( info->mincount > 0 )
{
// it has the mincount and the lump is completely missed!
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
}
}
return;
}
if( l->filelen % real_entrysize )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %zu bytes\n", msg2, l->filelen, real_entrysize );
loadstat.numerrors++;
return;
}
numelems = l->filelen / real_entrysize;
if( numelems < info->mincount )
{
// it has the mincount and it's smaller than this limit
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
return;
}
if( numelems > info->maxcount )
{
// it has the maxcount and it's overflowed
if( FBitSet( info->flags, CHECK_OVERFLOW ))
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
return;
}
else if( !FBitSet( flags, LUMP_SILENT ))
{
// just throw warning
Con_DPrintf( S_WARN "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
loadstat.numwarnings++;
}
}
if( FBitSet( flags, LUMP_TESTONLY ))
return; // don't fill the intermediate struct
// all checks are passed, store pointers
if( info->dataptr ) *info->dataptr = (void *)(in + l->fileofs);
if( info->count ) *info->count = numelems;
}
/*
================
Mod_ArrayUsage
================
*/
static int Mod_ArrayUsage( const char *szItem, int items, int maxitems, int itemsize )
{
float percentage = maxitems ? (items * 100.0f / maxitems) : 0.0f;
Con_Printf( "%-12s %7i/%-7i %8i/%-8i (%4.1f%%) ", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage );
if( percentage > 99.99f )
Con_Printf( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Con_Printf( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Con_Printf( "^2VERY FULL!^7\n" );
else Con_Printf( "\n" );
return items * itemsize;
}
/*
================
Mod_GlobUsage
================
*/
static int Mod_GlobUsage( const char *szItem, int itemstorage, int maxstorage )
{
float percentage = maxstorage ? (itemstorage * 100.0f / maxstorage) : 0.0f;
Con_Printf( "%-15s %-12s %8i/%-8i (%4.1f%%) ", szItem, "[variable]", itemstorage, maxstorage, percentage );
if( percentage > 99.99f )
Con_Printf( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Con_Printf( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Con_Printf( "^2VERY FULL!^7\n" );
else Con_Printf( "\n" );
return itemstorage;
}
/*
=============
Mod_PrintWorldStats_f
Dumps info about world
=============
*/
void Mod_PrintWorldStats_f( void )
{
int i, totalmemory = 0;
model_t *w = worldmodel;
if( !w || !w->numsubmodels )
{
Con_Printf( "No map loaded\n" );
return;
}
Con_Printf( "\n" );
Con_Printf( "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" );
Con_Printf( "------------ --------------- --------------- --------\n" );
for( i = 0; i < ARRAYSIZE( worldstats ); i++ )
{
mlumpstat_t *stat = &worldstats[i];
if( !stat->lumpname || !stat->maxcount || !stat->count )
continue; // unused or lump is empty
if( stat->entrysize == sizeof( byte ))
totalmemory += Mod_GlobUsage( stat->lumpname, stat->count, stat->maxcount );
else totalmemory += Mod_ArrayUsage( stat->lumpname, stat->count, stat->maxcount, stat->entrysize );
}
Con_Printf( "=== Total BSP file data space used: %s ===\n", Q_memprint( totalmemory ));
Con_Printf( "World size ( %g %g %g ) units\n", world.size[0], world.size[1], world.size[2] );
Con_Printf( "Supports transparency world water: %s\n", FBitSet( world.flags, FWORLD_WATERALPHA ) ? "Yes" : "No" );
Con_Printf( "Lighting: %s\n", FBitSet( w->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" );
Con_Printf( "World total leafs: %d\n", worldmodel->numleafs + 1 );
Con_Printf( "original name: ^1%s\n", worldmodel->name );
Con_Printf( "internal name: ^2%s\n", world.message[0] ? world.message : "none" );
Con_Printf( "map compiler: ^3%s\n", world.compiler[0] ? world.compiler : "unknown" );
Con_Printf( "map editor: ^2%s\n", world.generator[0] ? world.generator : "unknown" );
}
/*
===============================================================================
COMMON ROUTINES
===============================================================================
*/
/*
===================
Mod_DecompressPVS
===================
*/
byte *Mod_DecompressPVS( const byte *in, int visbytes )
{
byte *out;
int c;
out = g_visdata;
if( !in )
{
// no vis info, so make all visible
while( visbytes )
{
*out++ = 0xff;
visbytes--;
}
return g_visdata;
}
do
{
if( *in )
{
*out++ = *in++;
continue;
}
c = in[1];
in += 2;
while( c )
{
*out++ = 0;
c--;
}
} while( out - g_visdata < visbytes );
return g_visdata;
}
/*
==================
Mod_PointInLeaf
==================
*/
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node )
{
Assert( node != NULL );
while( 1 )
{
if( node->contents < 0 )
return (mleaf_t *)node;
node = node->children[PlaneDiff( p, node->plane ) <= 0];
}
// never reached
return NULL;
}
/*
==================
Mod_GetPVSForPoint
Returns PVS data for a given point
NOTE: can return NULL
==================
*/
byte *Mod_GetPVSForPoint( const vec3_t p )
{
mnode_t *node;
mleaf_t *leaf = NULL;
ASSERT( worldmodel != NULL );
node = worldmodel->nodes;
while( 1 )
{
if( node->contents < 0 )
{
leaf = (mleaf_t *)node;
break; // we found a leaf
}
node = node->children[PlaneDiff( p, node->plane ) <= 0];
}
if( leaf && leaf->cluster >= 0 )
return Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );
return NULL;
}
/*
==================
Mod_FatPVS_RecursiveBSPNode
==================
*/
static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node )
{
int i;
while( node->contents >= 0 )
{
float d = PlaneDiff( org, node->plane );
if( d > radius )
node = node->children[0];
else if( d < -radius )
node = node->children[1];
else
{
// go down both sides
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0] );
node = node->children[1];
}
}
// if this leaf is in a cluster, accumulate the vis bits
if(((mleaf_t *)node)->cluster >= 0 )
{
byte *vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes );
for( i = 0; i < visbytes; i++ )
visbuffer[i] |= vis[i];
}
}
/*
==================
Mod_FatPVS_RecursiveBSPNode
Calculates a PVS that is the inclusive or of all leafs
within radius pixels of the given point.
==================
*/
int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis )
{
int bytes = world.visbytes;
mleaf_t *leaf = NULL;
ASSERT( worldmodel != NULL );
leaf = Mod_PointInLeaf( org, worldmodel->nodes );
bytes = Q_min( bytes, visbytes );
// enable full visibility for some reasons
if( fullvis || !worldmodel->visdata || !leaf || leaf->cluster < 0 )
{
memset( visbuffer, 0xFF, bytes );
return bytes;
}
if( !merge ) memset( visbuffer, 0x00, bytes );
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes );
return bytes;
}
/*
======================================================================
LEAF LISTING
======================================================================
*/
static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node )
{
int sides;
while( 1 )
{
if( node->contents == CONTENTS_SOLID )
return;
if( node->contents < 0 )
{
mleaf_t *leaf = (mleaf_t *)node;
// it's a leaf!
if( ll->count >= ll->maxcount )
{
ll->overflowed = true;
return;
}
ll->list[ll->count++] = leaf->cluster;
return;
}
sides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane );
if( sides == 1 )
{
node = node->children[0];
}
else if( sides == 2 )
{
node = node->children[1];
}
else
{
// go down both
if( ll->topnode == -1 )
ll->topnode = node - worldmodel->nodes;
Mod_BoxLeafnums_r( ll, node->children[0] );
node = node->children[1];
}
}
}
/*
==================
Mod_BoxLeafnums
==================
*/
static int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *topnode )
{
leaflist_t ll;
if( !worldmodel ) return 0;
VectorCopy( mins, ll.mins );
VectorCopy( maxs, ll.maxs );
ll.maxcount = listsize;
ll.overflowed = false;
ll.topnode = -1;
ll.list = list;
ll.count = 0;
Mod_BoxLeafnums_r( &ll, worldmodel->nodes );
if( topnode ) *topnode = ll.topnode;
return ll.count;
}
/*
=============
Mod_BoxVisible
Returns true if any leaf in boxspace
is potentially visible
=============
*/
qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits )
{
int leafList[MAX_BOX_LEAFS];
int i, count;
if( !visbits || !mins || !maxs )
return true;
count = Mod_BoxLeafnums( mins, maxs, leafList, MAX_BOX_LEAFS, NULL );
for( i = 0; i < count; i++ )
{
if( CHECKVISBIT( visbits, leafList[i] ))
return true;
}
return false;
}
/*
=============
Mod_HeadnodeVisible
=============
*/
qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf )
{
if( !node || node->contents == CONTENTS_SOLID )
return false;
if( node->contents < 0 )
{
if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster ))
return false;
if( lastleaf )
*lastleaf = ((mleaf_t *)node)->cluster;
return true;
}
if( Mod_HeadnodeVisible( node->children[0], visbits, lastleaf ))
return true;
if( Mod_HeadnodeVisible( node->children[1], visbits, lastleaf ))
return true;
return false;
}
/*
=================
Mod_FindModelOrigin
routine to detect bmodels with origin-brush
=================
*/
static void Mod_FindModelOrigin( const char *entities, const char *modelname, vec3_t origin )
{
char *pfile;
string keyname;
char token[2048];
qboolean model_found;
qboolean origin_found;
if( !entities || !COM_CheckString( modelname ))
return;
if( !origin || !VectorIsNull( origin ))
return;
pfile = (char *)entities;
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
{
if( token[0] != '{' )
Host_Error( "Mod_FindModelOrigin: found %s when expecting {\n", token );
model_found = origin_found = false;
VectorClear( origin );
while( 1 )
{
// parse key
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" );
if( token[0] == '}' ) break; // end of desc
Q_strncpy( keyname, token, sizeof( keyname ));
// parse value
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" );
if( token[0] == '}' )
Host_Error( "Mod_FindModelOrigin: closing brace without data\n" );
if( !Q_stricmp( keyname, "model" ) && !Q_stricmp( modelname, token ))
model_found = true;
if( !Q_stricmp( keyname, "origin" ))
{
Q_atov( origin, token, 3 );
origin_found = true;
}
}
if( model_found ) break;
}
}
/*
==================
Mod_CheckWaterAlphaSupport
converted maps potential may don't
support water transparency
==================
*/
static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod )
{
mleaf_t *leaf;
int i, j;
const byte *pvs;
if( bmod->visdatasize <= 0 )
return true;
// check all liquid leafs to see if they can see into empty leafs, if any
// can we can assume this map supports r_wateralpha
for( i = 0, leaf = loadmodel->leafs; i < loadmodel->numleafs; i++, leaf++ )
{
if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 )
{
pvs = Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );
for( j = 0; j < loadmodel->numleafs; j++ )
{
if( CHECKVISBIT( pvs, loadmodel->leafs[j].cluster ) && loadmodel->leafs[j].contents == CONTENTS_EMPTY )
return true;
}
}
}
return false;
}
/*
==================
Mod_SampleSizeForFace
return the current lightmap resolution per face
==================
*/
int Mod_SampleSizeForFace( msurface_t *surf )
{
if( !surf || !surf->texinfo )
return LM_SAMPLE_SIZE;
// world luxels has more priority
if( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS ))
return 1;
if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))
return LM_SAMPLE_EXTRASIZE;
if( surf->texinfo->faceinfo )
return surf->texinfo->faceinfo->texture_step;
return LM_SAMPLE_SIZE;
}
/*
==================
Mod_GetFaceContents
determine face contents by name
==================
*/
static int Mod_GetFaceContents( const char *name )
{
if( !Q_strnicmp( name, "SKY", 3 ))
return CONTENTS_SKY;
if( name[0] == '!' || name[0] == '*' )
{
if( !Q_strnicmp( name + 1, "lava", 4 ))
return CONTENTS_LAVA;
else if( !Q_strnicmp( name + 1, "slime", 5 ))
return CONTENTS_SLIME;
return CONTENTS_WATER; // otherwise it's water
}
if( !Q_strnicmp( name, "water", 5 ))
return CONTENTS_WATER;
return CONTENTS_SOLID;
}
/*
==================
Mod_GetFaceContents
determine face contents by name
==================
*/
static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge )
{
int lindex;
medge_t *edge;
lindex = mod->surfedges[surfedge];
if( lindex > 0 )
{
edge = &mod->edges[lindex];
return &mod->vertexes[edge->v[0]];
}
else
{
edge = &mod->edges[-lindex];
return &mod->vertexes[edge->v[1]];
}
}
/*
==================
Mod_MakeNormalAxial
remove jitter from near-axial normals
==================
*/
static void Mod_MakeNormalAxial( vec3_t normal )
{
int i, type;
for( type = 0; type < 3; type++ )
{
if( fabs( normal[type] ) > 0.9999f )
break;
}
// make positive and pure axial
for( i = 0; i < 3 && type != 3; i++ )
{
if( i == type )
normal[i] = 1.0f;
else normal[i] = 0.0f;
}
}
/*
==================
Mod_LightMatrixFromTexMatrix
compute lightmap matrix based on texture matrix
==================
*/
static void Mod_LightMatrixFromTexMatrix( const mtexinfo_t *tx, float lmvecs[2][4] )
{
float lmscale = LM_SAMPLE_SIZE;
int i, j;
// this is can't be possible but who knews
if( FBitSet( tx->flags, TEX_EXTRA_LIGHTMAP ))
lmscale = LM_SAMPLE_EXTRASIZE;
if( tx->faceinfo )
lmscale = tx->faceinfo->texture_step;
// copy texmatrix into lightmap matrix fisrt
for( i = 0; i < 2; i++ )
{
for( j = 0; j < 4; j++ )
{
lmvecs[i][j] = tx->vecs[i][j];
}
}
if( !FBitSet( tx->flags, TEX_WORLD_LUXELS ))
return; // just use texmatrix
VectorNormalize( lmvecs[0] );
VectorNormalize( lmvecs[1] );
if( FBitSet( tx->flags, TEX_AXIAL_LUXELS ))
{
Mod_MakeNormalAxial( lmvecs[0] );
Mod_MakeNormalAxial( lmvecs[1] );
}
// put the lighting origin at center the of poly
VectorScale( lmvecs[0], (1.0f / lmscale), lmvecs[0] );
VectorScale( lmvecs[1], -(1.0f / lmscale), lmvecs[1] );
lmvecs[0][3] = lmscale * 0.5f;
lmvecs[1][3] = -lmscale * 0.5f;
}
/*
=================
Mod_CalcSurfaceExtents
Fills in surf->texturemins[] and surf->extents[]
=================
*/
static void Mod_CalcSurfaceExtents( msurface_t *surf )
{
// this place is VERY critical to precision
// keep it as float, don't use double, because it causes issues with lightmap
float mins[2], maxs[2], val;
float lmmins[2], lmmaxs[2];
int bmins[2], bmaxs[2];
int i, j, e, sample_size;
mextrasurf_t *info = surf->info;
int facenum = surf - loadmodel->surfaces;
mtexinfo_t *tex;
mvertex_t *v;
sample_size = Mod_SampleSizeForFace( surf );
tex = surf->texinfo;
Mod_LightMatrixFromTexMatrix( tex, info->lmvecs );
mins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999;
maxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[1] =-999999;
for( i = 0; i < surf->numedges; i++ )
{
e = loadmodel->surfedges[surf->firstedge + i];
if( e >= loadmodel->numedges || e <= -loadmodel->numedges )
Host_Error( "Mod_CalcSurfaceExtents: bad edge\n" );
if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
for( j = 0; j < 2; j++ )
{
val = DotProductPrecise( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3];
mins[j] = Q_min( val, mins[j] );
maxs[j] = Q_max( val, maxs[j] );
}
for( j = 0; j < 2; j++ )
{
val = DotProductPrecise( v->position, info->lmvecs[j] ) + info->lmvecs[j][3];
lmmins[j] = Q_min( val, lmmins[j] );
lmmaxs[j] = Q_max( val, lmmaxs[j] );
}
}
for( i = 0; i < 2; i++ )
{
bmins[i] = floor( mins[i] / sample_size );
bmaxs[i] = ceil( maxs[i] / sample_size );
surf->texturemins[i] = bmins[i] * sample_size;
surf->extents[i] = (bmaxs[i] - bmins[i]) * sample_size;
if( FBitSet( tex->flags, TEX_WORLD_LUXELS ))
{
lmmins[i] = floor( lmmins[i] );
lmmaxs[i] = ceil( lmmaxs[i] );
info->lightmapmins[i] = lmmins[i];
info->lightextents[i] = (lmmaxs[i] - lmmins[i]);
}
else
{
// just copy texturemins
info->lightmapmins[i] = surf->texturemins[i];
info->lightextents[i] = surf->extents[i];
}
#if !XASH_DEDICATED && 0 // REFTODO:
if( !FBitSet( tex->flags, TEX_SPECIAL ) && ( surf->extents[i] > 16384 ) && ( tr.block_size == BLOCK_SIZE_DEFAULT ))
Con_Reportf( S_ERROR "Bad surface extents %i\n", surf->extents[i] );
#endif // XASH_DEDICATED
}
}
/*
=================
Mod_CalcSurfaceBounds
fills in surf->mins and surf->maxs
=================
*/
static void Mod_CalcSurfaceBounds( msurface_t *surf )
{
int i, e;
mvertex_t *v;
ClearBounds( surf->info->mins, surf->info->maxs );
for( i = 0; i < surf->numedges; i++ )
{
e = loadmodel->surfedges[surf->firstedge + i];
if( e >= loadmodel->numedges || e <= -loadmodel->numedges )
Host_Error( "Mod_CalcSurfaceBounds: bad edge\n" );
if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
AddPointToBounds( v->position, surf->info->mins, surf->info->maxs );
}
VectorAverage( surf->info->mins, surf->info->maxs, surf->info->origin );
}
/*
=================
Mod_CreateFaceBevels
=================
*/
static void Mod_CreateFaceBevels( msurface_t *surf )
{
vec3_t delta, edgevec;
byte *facebevel;
vec3_t faceNormal;
mvertex_t *v0, *v1;
int contents;
int i, size;
vec_t radius;
mfacebevel_t *fb;
if( surf->texinfo && surf->texinfo->texture )
contents = Mod_GetFaceContents( surf->texinfo->texture->name );
else contents = CONTENTS_SOLID;
size = sizeof( mfacebevel_t ) + surf->numedges * sizeof( mplane_t );
facebevel = (byte *)Mem_Calloc( loadmodel->mempool, size );
fb = (mfacebevel_t *)facebevel;
facebevel += sizeof( mfacebevel_t );
fb->edges = (mplane_t *)facebevel;
fb->numedges = surf->numedges;
fb->contents = contents;
surf->info->bevel = fb;
if( FBitSet( surf->flags, SURF_PLANEBACK ))
VectorNegate( surf->plane->normal, faceNormal );
else VectorCopy( surf->plane->normal, faceNormal );
// compute face origin and plane edges
for( i = 0; i < surf->numedges; i++ )
{
mplane_t *dest = &fb->edges[i];
v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i );
v1 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + (i + 1) % surf->numedges );
VectorSubtract( v1->position, v0->position, edgevec );
CrossProduct( faceNormal, edgevec, dest->normal );
VectorNormalize( dest->normal );
dest->dist = DotProduct( dest->normal, v0->position );
dest->type = PlaneTypeForNormal( dest->normal );
VectorAdd( fb->origin, v0->position, fb->origin );
}
VectorScale( fb->origin, 1.0f / surf->numedges, fb->origin );
// compute face radius
for( i = 0; i < surf->numedges; i++ )
{
v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i );
VectorSubtract( v0->position, fb->origin, delta );
radius = DotProduct( delta, delta );
fb->radius = Q_max( radius, fb->radius );
}
}
/*
=================
Mod_SetParent
=================
*/
static void Mod_SetParent( mnode_t *node, mnode_t *parent )
{
node->parent = parent;
if( node->contents < 0 ) return; // it's leaf
Mod_SetParent( node->children[0], node );
Mod_SetParent( node->children[1], node );
}
/*
==================
CountClipNodes_r
==================
*/
static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountClipNodes_r( src, hull, src[nodenum].children[0] );
CountClipNodes_r( src, hull, src[nodenum].children[1] );
}
/*
==================
CountClipNodes32_r
==================
*/
static void CountClipNodes32_r( dclipnode32_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountClipNodes32_r( src, hull, src[nodenum].children[0] );
CountClipNodes32_r( src, hull, src[nodenum].children[1] );
}
/*
==================
RemapClipNodes_r
==================
*/
static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum )
{
dclipnode32_t *src;
mclipnode_t *out;
int i, c;
// leaf?
if( nodenum < 0 )
return nodenum;
// emit a clipnode
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
src = srcnodes + nodenum;
c = hull->lastclipnode;
out = &hull->clipnodes[c];
hull->lastclipnode++;
out->planenum = src->planenum;
for( i = 0; i < 2; i++ )
out->children[i] = RemapClipNodes_r( srcnodes, hull, src->children[i] );
return c;
}
/*
=================
Mod_MakeHull0
Duplicate the drawing hull structure as a clipping hull
=================
*/
static void Mod_MakeHull0( void )
{
mnode_t *in, *child;
mclipnode_t *out;
hull_t *hull;
int i, j;
hull = &loadmodel->hulls[0];
hull->clipnodes = out = Mem_Malloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out ));
in = loadmodel->nodes;
hull->firstclipnode = 0;
hull->lastclipnode = loadmodel->numnodes - 1;
hull->planes = loadmodel->planes;
for( i = 0; i < loadmodel->numnodes; i++, out++, in++ )
{
out->planenum = in->plane - loadmodel->planes;
for( j = 0; j < 2; j++ )
{
child = in->children[j];
if( child->contents < 0 )
out->children[j] = child->contents;
else out->children[j] = child - loadmodel->nodes;
}
}
}
/*
=================
Mod_SetupHull
=================
*/
static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool, int headnode, int hullnum )
{
hull_t *hull = &mod->hulls[hullnum];
int count;
// assume no hull
hull->firstclipnode = hull->lastclipnode = 0;
hull->planes = NULL; // hull is missed
if(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 ))
return; // hull missed
if( headnode >= mod->numclipnodes )
return; // ZHLT weird empty hulls
switch( hullnum )
{
case 1:
VectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull
VectorCopy( host.player_maxs[0], hull->clip_maxs );
break;
case 2:
VectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull
VectorCopy( host.player_maxs[3], hull->clip_maxs );
break;
case 3:
VectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull
VectorCopy( host.player_maxs[1], hull->clip_maxs );
break;
default:
Host_Error( "Mod_SetupHull: bad hull number %i\n", hullnum );
break;
}
if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs ))
return; // no hull specified
CountClipNodes32_r( bmod->clipnodes_out, hull, headnode );
count = hull->lastclipnode;
// fit array to real count
hull->clipnodes = (mclipnode_t *)Mem_Malloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode );
hull->planes = mod->planes; // share planes
hull->lastclipnode = 0; // restart counting
// remap clipnodes to 16-bit indexes
RemapClipNodes_r( bmod->clipnodes_out, hull, headnode );
}
/*
=================
Mod_LoadColoredLighting
=================
*/
static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod )
{
char modelname[64];
char path[64];
int iCompare;
fs_offset_t litdatasize;
byte *in;
COM_FileBase( loadmodel->name, modelname );
Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname );
// make sure what deluxemap is actual
if( !COM_CompareFileTime( path, loadmodel->name, &iCompare ))
return false;
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
Con_Printf( S_WARN "%s probably is out of date\n", path );
in = FS_LoadFile( path, &litdatasize, false );
Assert( in != NULL );
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
{
Mem_Free( in );
return false;
}
// skip header bytes
litdatasize -= 8;
if( litdatasize != ( bmod->lightdatasize * 3 ))
{
Con_Printf( S_ERROR "%s has mismatched size (%llu should be %zu)\n", path, litdatasize, bmod->lightdatasize * 3 );
Mem_Free( in );
return false;
}
loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, litdatasize );
memcpy( loadmodel->lightdata, in + 8, litdatasize );
SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING );
bmod->lightdatasize = litdatasize;
Mem_Free( in );
return true;
}
/*
=================
Mod_LoadDeluxemap
=================
*/
static void Mod_LoadDeluxemap( dbspmodel_t *bmod )
{
char modelname[64];
fs_offset_t deluxdatasize;
char path[64];
int iCompare;
byte *in;
if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
return;
COM_FileBase( loadmodel->name, modelname );
Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname );
// make sure what deluxemap is actual
if( !COM_CompareFileTime( path, loadmodel->name, &iCompare ))
return;
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
Con_Printf( S_WARN "%s probably is out of date\n", path );
in = FS_LoadFile( path, &deluxdatasize, false );
Assert( in != NULL );
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
{
Mem_Free( in );
return;
}
// skip header bytes
deluxdatasize -= 8;
if( deluxdatasize != bmod->lightdatasize )
{
Con_Reportf( S_ERROR "%s has mismatched size (%llu should be %zu)\n", path, deluxdatasize, bmod->lightdatasize );
Mem_Free( in );
return;
}
bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, deluxdatasize );
memcpy( bmod->deluxedata_out, in + 8, deluxdatasize );
bmod->deluxdatasize = deluxdatasize;
Mem_Free( in );
}
/*
=================
Mod_SetupSubmodels
duplicate the basic information
for embedded submodels
=================
*/
static void Mod_SetupSubmodels( dbspmodel_t *bmod )
{
qboolean colored = false;
poolhandle_t mempool;
char *ents;
model_t *mod;
dmodel_t *bm;
int i, j;
ents = loadmodel->entities;
mempool = loadmodel->mempool;
if( FBitSet( loadmodel->flags, MODEL_COLORED_LIGHTING ))
colored = true;
mod = loadmodel;
loadmodel->numframes = 2; // regular and alternate animation
// set up the submodels
for( i = 0; i < mod->numsubmodels; i++ )
{
bm = &mod->submodels[i];
// hull 0 is just shared across all bmodels
mod->hulls[0].firstclipnode = bm->headnode[0];
mod->hulls[0].lastclipnode = bm->headnode[0]; // need to be real count
// counting a real number of clipnodes per each submodel
CountClipNodes_r( mod->hulls[0].clipnodes, &mod->hulls[0], bm->headnode[0] );
// but hulls1-3 is build individually for a each given submodel
for( j = 1; j < MAX_MAP_HULLS; j++ )
Mod_SetupHull( bmod, mod, mempool, bm->headnode[j], j );
mod->firstmodelsurface = bm->firstface;
mod->nummodelsurfaces = bm->numfaces;
VectorCopy( bm->mins, mod->mins );
VectorCopy( bm->maxs, mod->maxs );
mod->radius = RadiusFromBounds( mod->mins, mod->maxs );
mod->numleafs = bm->visleafs;
mod->flags = 0;
// this bit will be shared between all the submodels include worldmodel
if( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING );
if( i != 0 )
{
char temp[MAX_VA_STRING];
Q_snprintf( temp, sizeof( temp ), "*%i", i );
Mod_FindModelOrigin( ents, temp, bm->origin );
// mark models that have origin brushes
if( !VectorIsNull( bm->origin ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#ifdef HACKS_RELATED_HLMODS
// c2a1 doesn't have origin brush it's just placed at center of the level
if( !Q_stricmp( loadmodel->name, "maps/c2a1.bsp" ) && ( i == 11 ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#endif
}
// sets the model flags
for( j = 0; i != 0 && j < mod->nummodelsurfaces; j++ )
{
msurface_t *surf = mod->surfaces + mod->firstmodelsurface + j;
if( FBitSet( surf->flags, SURF_CONVEYOR ))
SetBits( mod->flags, MODEL_CONVEYOR );
if( FBitSet( surf->flags, SURF_TRANSPARENT ))
SetBits( mod->flags, MODEL_TRANSPARENT );
if( FBitSet( surf->flags, SURF_DRAWTURB ))
SetBits( mod->flags, MODEL_LIQUID );
}
if( i < mod->numsubmodels - 1 )
{
char name[8];
// duplicate the basic information
Q_snprintf( name, sizeof( name ), "*%i", i + 1 );
loadmodel = Mod_FindName( name, true );
*loadmodel = *mod;
Q_strncpy( loadmodel->name, name, sizeof( loadmodel->name ));
loadmodel->mempool = 0;
mod = loadmodel;
}
}
if( bmod->clipnodes_out != NULL )
Mem_Free( bmod->clipnodes_out );
}
/*
===============================================================================
MAP LOADING
===============================================================================
*/
/*
=================
Mod_LoadSubmodels
=================
*/
static void Mod_LoadSubmodels( dbspmodel_t *bmod )
{
dmodel_t *in, *out;
int oldmaxfaces;
int i, j;
// allocate extradata for each dmodel_t
out = Mem_Malloc( loadmodel->mempool, bmod->numsubmodels * sizeof( *out ));
loadmodel->numsubmodels = bmod->numsubmodels;
loadmodel->submodels = out;
in = bmod->submodels;
if( bmod->isworld )
refState.max_surfaces = 0;
oldmaxfaces = refState.max_surfaces;
for( i = 0; i < bmod->numsubmodels; i++, in++, out++ )
{
for( j = 0; j < 3; j++ )
{
// reset empty bounds to prevent error
if( in->mins[j] == 999999.0f )
in->mins[j] = 0.0f;
if( in->maxs[j] == -999999.0f)
in->maxs[j] = 0.0f;
// spread the mins / maxs by a unit
out->mins[j] = in->mins[j] - 1.0f;
out->maxs[j] = in->maxs[j] + 1.0f;
out->origin[j] = in->origin[j];
}
for( j = 0; j < MAX_MAP_HULLS; j++ )
out->headnode[j] = in->headnode[j];
out->visleafs = in->visleafs;
out->firstface = in->firstface;
out->numfaces = in->numfaces;
if( i == 0 && bmod->isworld )
continue; // skip the world to save mem
oldmaxfaces = Q_max( oldmaxfaces, out->numfaces );
}
// these array used to sort translucent faces in bmodels
if( oldmaxfaces > refState.max_surfaces )
{
refState.draw_surfaces = (sortedface_t *)Z_Realloc( refState.draw_surfaces, oldmaxfaces * sizeof( sortedface_t ));
refState.max_surfaces = oldmaxfaces;
}
}
/*
=================
Mod_LoadEntities
=================
*/
static void Mod_LoadEntities( dbspmodel_t *bmod )
{
byte *entpatch = NULL;
char token[MAX_TOKEN];
char wadstring[MAX_TOKEN];
string keyname;
char *pfile;
if( bmod->isworld )
{
char entfilename[MAX_QPATH];
fs_offset_t entpatchsize;
size_t ft1, ft2;
// world is check for entfile too
Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename ));
COM_ReplaceExtension( entfilename, ".ent" );
// make sure what entity patch is never than bsp
ft1 = FS_FileTime( loadmodel->name, false );
ft2 = FS_FileTime( entfilename, true );
if( ft2 != -1 )
{
if( ft1 > ft2 )
{
Con_Printf( S_WARN "Entity patch is older than bsp. Ignored.\n" );
}
else if(( entpatch = FS_LoadFile( entfilename, &entpatchsize, true )) != NULL )
{
Con_Printf( "^2Read entity patch:^7 %s\n", entfilename );
bmod->entdatasize = entpatchsize;
bmod->entdata = entpatch;
}
}
}
// make sure what we really has terminator
loadmodel->entities = Mem_Calloc( loadmodel->mempool, bmod->entdatasize + 1 );
memcpy( loadmodel->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool
if( entpatch ) Mem_Free( entpatch ); // release entpatch if present
if( !bmod->isworld ) return;
pfile = (char *)loadmodel->entities;
world.generator[0] = '\0';
world.compiler[0] = '\0';
world.message[0] = '\0';
bmod->wadlist.count = 0;
// parse all the wads for loading textures in right ordering
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
{
if( token[0] != '{' )
Host_Error( "Mod_LoadEntities: found %s when expecting {\n", token );
while( 1 )
{
// parse key
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
Host_Error( "Mod_LoadEntities: EOF without closing brace\n" );
if( token[0] == '}' ) break; // end of desc
Q_strncpy( keyname, token, sizeof( keyname ));
// parse value
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
Host_Error( "Mod_LoadEntities: EOF without closing brace\n" );
if( token[0] == '}' )
Host_Error( "Mod_LoadEntities: closing brace without data\n" );
if( !Q_stricmp( keyname, "wad" ))
{
char *pszWadFile;
Q_strncpy( wadstring, token, MAX_TOKEN - 2 );
wadstring[MAX_TOKEN - 2] = 0;
if( !Q_strchr( wadstring, ';' ))
Q_strcat( wadstring, ";" );
// parse wad pathes
for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" ))
{
COM_FixSlashes( pszWadFile );
COM_FileBase( pszWadFile, token );
// make sure what wad is really exist
if( FS_FileExists( va( "%s.wad", token ), false ))
{
int num = bmod->wadlist.count++;
Q_strncpy( bmod->wadlist.wadnames[num], token, sizeof( bmod->wadlist.wadnames[0] ));
bmod->wadlist.wadusage[num] = 0;
}
if( bmod->wadlist.count >= MAX_MAP_WADS )
break; // too many wads...
}
}
else if( !Q_stricmp( keyname, "message" ))
Q_strncpy( world.message, token, sizeof( world.message ));
else if( !Q_stricmp( keyname, "compiler" ) || !Q_stricmp( keyname, "_compiler" ))
Q_strncpy( world.compiler, token, sizeof( world.compiler ));
else if( !Q_stricmp( keyname, "generator" ) || !Q_stricmp( keyname, "_generator" ))
Q_strncpy( world.generator, token, sizeof( world.generator ));
}
return; // all done
}
}
/*
=================
Mod_LoadPlanes
=================
*/
static void Mod_LoadPlanes( dbspmodel_t *bmod )
{
dplane_t *in;
mplane_t *out;
int i, j;
in = bmod->planes;
loadmodel->planes = out = Mem_Malloc( loadmodel->mempool, bmod->numplanes * sizeof( *out ));
loadmodel->numplanes = bmod->numplanes;
for( i = 0; i < bmod->numplanes; i++, in++, out++ )
{
out->signbits = 0;
for( j = 0; j < 3; j++ )
{
out->normal[j] = in->normal[j];
if( out->normal[j] < 0.0f )
SetBits( out->signbits, BIT( j ));
}
if( VectorLength( out->normal ) < 0.5f )
Con_Printf( S_ERROR "bad normal for plane #%i\n", i );
out->dist = in->dist;
out->type = in->type;
}
}
/*
=================
Mod_LoadVertexes
=================
*/
static void Mod_LoadVertexes( dbspmodel_t *bmod )
{
dvertex_t *in;
mvertex_t *out;
int i;
in = bmod->vertexes;
out = loadmodel->vertexes = Mem_Malloc( loadmodel->mempool, bmod->numvertexes * sizeof( mvertex_t ));
loadmodel->numvertexes = bmod->numvertexes;
if( bmod->isworld ) ClearBounds( world.mins, world.maxs );
for( i = 0; i < bmod->numvertexes; i++, in++, out++ )
{
if( bmod->isworld )
AddPointToBounds( in->point, world.mins, world.maxs );
VectorCopy( in->point, out->position );
}
if( !bmod->isworld ) return;
VectorSubtract( world.maxs, world.mins, world.size );
for( i = 0; i < 3; i++ )
{
// spread the mins / maxs by a pixel
world.mins[i] -= 1.0f;
world.maxs[i] += 1.0f;
}
}
/*
=================
Mod_LoadEdges
=================
*/
static void Mod_LoadEdges( dbspmodel_t *bmod )
{
medge_t *out;
int i;
loadmodel->edges = out = Mem_Malloc( loadmodel->mempool, bmod->numedges * sizeof( medge_t ));
loadmodel->numedges = bmod->numedges;
if( bmod->version == QBSP2_VERSION )
{
dedge32_t *in = (dedge32_t *)bmod->edges32;
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
out->v[0] = in->v[0];
out->v[1] = in->v[1];
}
}
else
{
dedge_t *in = (dedge_t *)bmod->edges;
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
out->v[0] = (word)in->v[0];
out->v[1] = (word)in->v[1];
}
}
}
/*
=================
Mod_LoadSurfEdges
=================
*/
static void Mod_LoadSurfEdges( dbspmodel_t *bmod )
{
loadmodel->surfedges = Mem_Malloc( loadmodel->mempool, bmod->numsurfedges * sizeof( dsurfedge_t ));
memcpy( loadmodel->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t ));
loadmodel->numsurfedges = bmod->numsurfedges;
}
/*
=================
Mod_LoadMarkSurfaces
=================
*/
static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod )
{
msurface_t **out;
int i;
loadmodel->marksurfaces = out = Mem_Malloc( loadmodel->mempool, bmod->nummarkfaces * sizeof( *out ));
loadmodel->nummarksurfaces = bmod->nummarkfaces;
if( bmod->version == QBSP2_VERSION )
{
dmarkface32_t *in = bmod->markfaces32;
for( i = 0; i < bmod->nummarkfaces; i++, in++ )
{
if( *in < 0 || *in >= loadmodel->numsurfaces )
Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name );
out[i] = loadmodel->surfaces + *in;
}
}
else
{
dmarkface_t *in = bmod->markfaces;
for( i = 0; i < bmod->nummarkfaces; i++, in++ )
{
if( *in < 0 || *in >= loadmodel->numsurfaces )
Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name );
out[i] = loadmodel->surfaces + *in;
}
}
}
static void Mod_LoadTextureData( dbspmodel_t *bmod, int textureIndex )
{
#if !XASH_DEDICATED
texture_t *texture = NULL;
mip_t *mipTex = NULL;
qboolean usesCustomPalette = false;
uint32_t txFlags = 0;
// Don't load texture data on dedicated server, as there is no renderer.
// FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too
// but there is no facility for this yet
if( Host_IsDedicated( ))
return;
texture = loadmodel->textures[textureIndex];
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' )
SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing
usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( bmod, textureIndex );
// check for multi-layered sky texture (quake1 specific)
if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 )
{
ref.dllFuncs.R_InitSkyClouds( mipTex, texture, usesCustomPalette ); // load quake sky
if( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ) && R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ))
SetBits( world.flags, FWORLD_SKYSPHERE );
// No texture to load in this case, so just exit.
return;
}
// Texture loading order:
// 1. From WAD
// 2. Internal from map
// Try WAD texture (force while r_wadtextures is 1)
if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || mipTex->offsets[0] <= 0 )
{
char texpath[MAX_VA_STRING];
int wadIndex = Mod_FindTextureInWadList( &bmod->wadlist, mipTex->name, texpath, sizeof( texpath ));
if( wadIndex >= 0 )
{
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags );
bmod->wadlist.wadusage[wadIndex]++;
}
}
// WAD failed, so use internal texture (if present)
if( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 )
{
char texName[64];
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
}
// If texture is completely missed:
if( texture->gl_texturenum == 0 )
{
Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex->name );
texture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
}
// Check for luma texture
if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, texture->gl_texturenum ), TF_HAS_LUMA ))
{
char texName[64];
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
if( mipTex->offsets[0] > 0 )
{
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA );
}
else
{
char texpath[MAX_VA_STRING];
int wadIndex;
fs_offset_t srcSize = 0;
byte* src = NULL;
// NOTE: We can't load the _luma texture from the WAD as normal because it
// doesn't exist there. The original texture is already loaded, but cannot be modified.
// Instead, load the original texture again and convert it to luma.
wadIndex = Mod_FindTextureInWadList( &bmod->wadlist, texture->name, texpath, sizeof( texpath ));
if( wadIndex >= 0 )
{
src = FS_LoadFile( texpath, &srcSize, false );
bmod->wadlist.wadusage[wadIndex]++;
}
// OK, loading it from wad or hi-res version
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, src, srcSize, TF_MAKELUMA );
if( src )
Mem_Free( src );
}
}
#endif // !XASH_DEDICATED
}
static void Mod_LoadTexture( dbspmodel_t *bmod, int textureIndex )
{
texture_t *texture;
mip_t *mipTex;
if( textureIndex < 0 || textureIndex >= loadmodel->numtextures )
return;
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
if( !mipTex )
{
// No data for this texture.
// Create default texture (some mods require this).
Mod_CreateDefaultTexture( &loadmodel->textures[textureIndex] );
return;
}
if( mipTex->name[0] == '\0' )
Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex );
texture = (texture_t *)Mem_Calloc( loadmodel->mempool, sizeof( *texture ));
loadmodel->textures[textureIndex] = texture;
// Ensure texture name is lowercase.
Q_strncpy( texture->name, mipTex->name, sizeof( texture->name ));
Q_strnlwr( texture->name, texture->name, sizeof( texture->name ));
texture->width = mipTex->width;
texture->height = mipTex->height;
Mod_LoadTextureData( bmod, textureIndex );
}
static void Mod_LoadAllTextures( dbspmodel_t *bmod )
{
int i;
for( i = 0; i < loadmodel->numtextures; i++ )
Mod_LoadTexture( bmod, i );
}
static void Mod_SequenceAnimatedTexture( int baseTextureIndex )
{
texture_t *anims[10];
texture_t *altanims[10];
texture_t *baseTexture;
int max = 0;
int altmax = 0;
int candidateIndex;
if( baseTextureIndex < 0 || baseTextureIndex >= loadmodel->numtextures )
return;
baseTexture = loadmodel->textures[baseTextureIndex];
if( !Mod_NameImpliesTextureIsAnimated( baseTexture ))
return;
// Already sequenced
if( baseTexture->anim_next )
return;
// find the number of frames in the animation
memset( anims, 0, sizeof( anims ));
memset( altanims, 0, sizeof( altanims ));
if( baseTexture->name[1] >= '0' && baseTexture->name[1] <= '9' )
{
// This texture is a standard animation frame.
int frameIndex = (int)baseTexture->name[1] - (int)'0';
anims[frameIndex] = baseTexture;
max = frameIndex + 1;
}
else
{
// This texture is an alternate animation frame.
int frameIndex = (int)baseTexture->name[1] - (int)'a';
altanims[frameIndex] = baseTexture;
altmax = frameIndex + 1;
}
// Now search the rest of the textures to find all other frames.
for( candidateIndex = baseTextureIndex + 1; candidateIndex < loadmodel->numtextures; candidateIndex++ )
{
texture_t *altTexture = loadmodel->textures[candidateIndex];
if( !Mod_NameImpliesTextureIsAnimated( altTexture ))
continue;
// This texture is animated, but is it part of the same group as
// the original texture we encountered? Check that the rest of
// the name matches the original (both will be valid for at least
// string index 2).
if( Q_strcmp( altTexture->name + 2, baseTexture->name + 2 ) != 0 )
continue;
if( altTexture->name[1] >= '0' && altTexture->name[1] <= '9' )
{
// This texture is a standard frame.
int frameIndex = (int)altTexture->name[1] - (int)'0';
anims[frameIndex] = altTexture;
if( frameIndex >= max )
max = frameIndex + 1;
}
else
{
// This texture is an alternate frame.
int frameIndex = (int)altTexture->name[1] - (int)'a';
altanims[frameIndex] = altTexture;
if( frameIndex >= altmax )
altmax = frameIndex + 1;
}
}
// Link all standard animated frames together.
for( candidateIndex = 0; candidateIndex < max; candidateIndex++ )
{
texture_t *tex = anims[candidateIndex];
if( !tex )
{
Con_Printf( S_ERROR "Mod_SequenceAnimatedTexture: missing frame %i of animated texture \"%s\"\n",
candidateIndex,
baseTexture->name );
baseTexture->anim_total = 0;
break;
}
tex->anim_total = max * ANIM_CYCLE;
tex->anim_min = candidateIndex * ANIM_CYCLE;
tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;
tex->anim_next = anims[( candidateIndex + 1 ) % max];
if( altmax > 0 )
tex->alternate_anims = altanims[0];
}
// Link all alternate animated frames together.
for( candidateIndex = 0; candidateIndex < altmax; candidateIndex++ )
{
texture_t *tex = altanims[candidateIndex];
if( !tex )
{
Con_Printf( S_ERROR "Mod_SequenceAnimatedTexture: missing alternate frame %i of animated texture \"%s\"\n",
candidateIndex,
baseTexture->name );
baseTexture->anim_total = 0;
break;
}
tex->anim_total = altmax * ANIM_CYCLE;
tex->anim_min = candidateIndex * ANIM_CYCLE;
tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;
tex->anim_next = altanims[( candidateIndex + 1 ) % altmax];
if( max > 0 )
tex->alternate_anims = anims[0];
}
}
static void Mod_SequenceAllAnimatedTextures( void )
{
int i;
for( i = 0; i < loadmodel->numtextures; i++ )
Mod_SequenceAnimatedTexture( i );
}
/*
=================
Mod_LoadTextures
=================
*/
static void Mod_LoadTextures( dbspmodel_t *bmod )
{
dmiptexlump_t *lump;
#if !XASH_DEDICATED
// release old sky layers first
if( !Host_IsDedicated() && bmod->isworld )
{
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ));
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ));
}
#endif
lump = bmod->textures;
if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )
{
// no textures
loadmodel->textures = NULL;
return;
}
loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, lump->nummiptex * sizeof( texture_t * ));
loadmodel->numtextures = lump->nummiptex;
Mod_LoadAllTextures( bmod );
Mod_SequenceAllAnimatedTextures();
}
/*
=================
Mod_LoadTexInfo
=================
*/
static void Mod_LoadTexInfo( dbspmodel_t *bmod )
{
mfaceinfo_t *fout, *faceinfo;
int i, j, k, miptex;
dfaceinfo_t *fin;
mtexinfo_t *out;
dtexinfo_t *in;
// trying to load faceinfo
faceinfo = fout = Mem_Calloc( loadmodel->mempool, bmod->numfaceinfo * sizeof( *fout ));
fin = bmod->faceinfo;
for( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ )
{
Q_strncpy( fout->landname, fin->landname, sizeof( fout->landname ));
fout->texture_step = fin->texture_step;
fout->max_extent = fin->max_extent;
fout->groupid = fin->groupid;
}
loadmodel->texinfo = out = Mem_Calloc( loadmodel->mempool, bmod->numtexinfo * sizeof( *out ));
loadmodel->numtexinfo = bmod->numtexinfo;
in = bmod->texinfo;
for( i = 0; i < bmod->numtexinfo; i++, in++, out++ )
{
for( j = 0; j < 2; j++ )
for( k = 0; k < 4; k++ )
out->vecs[j][k] = in->vecs[j][k];
miptex = in->miptex;
if( miptex < 0 || miptex > loadmodel->numtextures )
miptex = 0; // this is possible?
out->texture = loadmodel->textures[miptex];
out->flags = in->flags;
// make sure what faceinfo is really exist
if( faceinfo != NULL && in->faceinfo != -1 && in->faceinfo < bmod->numfaceinfo )
out->faceinfo = &faceinfo[in->faceinfo];
}
}
/*
=================
Mod_LoadSurfaces
=================
*/
static void Mod_LoadSurfaces( dbspmodel_t *bmod )
{
int test_lightsize = -1;
int next_lightofs = -1;
int prev_lightofs = -1;
int i, j, lightofs;
mextrasurf_t *info;
msurface_t *out;
loadmodel->surfaces = out = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( msurface_t ));
info = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( mextrasurf_t ));
loadmodel->numsurfaces = bmod->numsurfaces;
// predict samplecount based on bspversion
if( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION )
bmod->lightmap_samples = 1;
else bmod->lightmap_samples = 3;
for( i = 0; i < bmod->numsurfaces; i++, out++, info++ )
{
texture_t *tex;
// setup crosslinks between two parts of msurface_t
out->info = info;
info->surf = out;
if( bmod->version == QBSP2_VERSION )
{
dface32_t *in = &bmod->surfaces32[i];
if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges )
continue; // corrupted level?
out->firstedge = in->firstedge;
out->numedges = in->numedges;
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
out->plane = loadmodel->planes + in->planenum;
out->texinfo = loadmodel->texinfo + in->texinfo;
for( j = 0; j < MAXLIGHTMAPS; j++ )
out->styles[j] = in->styles[j];
lightofs = in->lightofs;
}
else
{
dface_t *in = &bmod->surfaces[i];
if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges )
{
Con_Reportf( S_ERROR "bad surface %i from %zu\n", i, bmod->numsurfaces );
continue;
}
out->firstedge = in->firstedge;
out->numedges = in->numedges;
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
out->plane = loadmodel->planes + in->planenum;
out->texinfo = loadmodel->texinfo + in->texinfo;
for( j = 0; j < MAXLIGHTMAPS; j++ )
out->styles[j] = in->styles[j];
lightofs = in->lightofs;
}
tex = out->texinfo->texture;
if( !Q_strncmp( tex->name, "sky", 3 ))
SetBits( out->flags, SURF_DRAWSKY );
if(( tex->name[0] == '*' && Q_stricmp( tex->name, REF_DEFAULT_TEXTURE )) || tex->name[0] == '!' )
SetBits( out->flags, SURF_DRAWTURB );
if( !Host_IsQuakeCompatible( ))
{
if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 ))
SetBits( out->flags, SURF_DRAWTURB );
}
if( !Q_strncmp( tex->name, "scroll", 6 ))
SetBits( out->flags, SURF_CONVEYOR );
if( FBitSet( out->texinfo->flags, TEX_SCROLL ))
SetBits( out->flags, SURF_CONVEYOR );
// g-cont. added a combined conveyor-transparent
if( !Q_strncmp( tex->name, "{scroll", 7 ))
SetBits( out->flags, SURF_CONVEYOR|SURF_TRANSPARENT );
if( tex->name[0] == '{' )
SetBits( out->flags, SURF_TRANSPARENT );
if( FBitSet( out->texinfo->flags, TEX_SPECIAL ))
SetBits( out->flags, SURF_DRAWTILED );
Mod_CalcSurfaceBounds( out );
Mod_CalcSurfaceExtents( out );
Mod_CreateFaceBevels( out );
// grab the second sample to detect colored lighting
if( test_lightsize > 0 && lightofs != -1 )
{
if( lightofs > prev_lightofs && lightofs < next_lightofs )
next_lightofs = lightofs;
}
// grab the first sample to determine lightmap size
if( lightofs != -1 && test_lightsize == -1 )
{
int sample_size = Mod_SampleSizeForFace( out );
int smax = (info->lightextents[0] / sample_size) + 1;
int tmax = (info->lightextents[1] / sample_size) + 1;
int lightstyles = 0;
test_lightsize = smax * tmax;
// count styles to right compute test_lightsize
for( j = 0; j < MAXLIGHTMAPS && out->styles[j] != 255; j++ )
lightstyles++;
test_lightsize *= lightstyles;
prev_lightofs = lightofs;
next_lightofs = 99999999;
}
#if !XASH_DEDICATED // TODO: Do we need subdivide on server?
if( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() )
ref.dllFuncs.GL_SubdivideSurface( out ); // cut up polygon for warps
#endif
}
// now we have enough data to trying determine samplecount per lightmap pixel
if( test_lightsize > 0 && prev_lightofs != -1 && next_lightofs != -1 && next_lightofs != 99999999 )
{
float samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;
if( samples != (int)samples )
{
test_lightsize = (test_lightsize + 3) & ~3; // align datasize and try again
samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;
}
if( samples == 1 || samples == 3 )
{
bmod->lightmap_samples = (int)samples;
Con_Reportf( "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" );
bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero
}
else Con_DPrintf( S_WARN "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples );
}
}
/*
=================
Mod_LoadNodes
=================
*/
static void Mod_LoadNodes( dbspmodel_t *bmod )
{
mnode_t *out;
int i, j, p;
loadmodel->nodes = out = (mnode_t *)Mem_Calloc( loadmodel->mempool, bmod->numnodes * sizeof( *out ));
loadmodel->numnodes = bmod->numnodes;
for( i = 0; i < loadmodel->numnodes; i++, out++ )
{
if( bmod->version == QBSP2_VERSION )
{
dnode32_t *in = &bmod->nodes32[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
p = in->planenum;
out->plane = loadmodel->planes + p;
out->firstsurface = in->firstface;
out->numsurfaces = in->numfaces;
for( j = 0; j < 2; j++ )
{
p = in->children[j];
if( p >= 0 ) out->children[j] = loadmodel->nodes + p;
else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p ));
}
}
else
{
dnode_t *in = &bmod->nodes[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
p = in->planenum;
out->plane = loadmodel->planes + p;
out->firstsurface = in->firstface;
out->numsurfaces = in->numfaces;
for( j = 0; j < 2; j++ )
{
p = in->children[j];
if( p >= 0 ) out->children[j] = loadmodel->nodes + p;
else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p ));
}
}
}
// sets nodes and leafs
Mod_SetParent( loadmodel->nodes, NULL );
}
/*
=================
Mod_LoadLeafs
=================
*/
static void Mod_LoadLeafs( dbspmodel_t *bmod )
{
mleaf_t *out;
int i, j, p;
int visclusters = 0;
loadmodel->leafs = out = (mleaf_t *)Mem_Calloc( loadmodel->mempool, bmod->numleafs * sizeof( *out ));
loadmodel->numleafs = bmod->numleafs;
if( bmod->isworld )
{
visclusters = loadmodel->submodels[0].visleafs;
world.visbytes = (visclusters + 7) >> 3;
world.fatbytes = (visclusters + 31) >> 3;
refState.visbytes = world.visbytes;
}
for( i = 0; i < bmod->numleafs; i++, out++ )
{
if( bmod->version == QBSP2_VERSION )
{
dleaf32_t *in = &bmod->leafs32[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
out->contents = in->contents;
p = in->visofs;
for( j = 0; j < 4; j++ )
out->ambient_sound_level[j] = in->ambient_level[j];
out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface;
out->nummarksurfaces = in->nummarksurfaces;
}
else
{
dleaf_t *in = &bmod->leafs[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
out->contents = in->contents;
p = in->visofs;
for( j = 0; j < 4; j++ )
out->ambient_sound_level[j] = in->ambient_level[j];
out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface;
out->nummarksurfaces = in->nummarksurfaces;
}
if( bmod->isworld )
{
out->cluster = ( i - 1 ); // solid leaf 0 has no visdata
if( out->cluster >= visclusters )
out->cluster = -1;
// ignore visofs errors on leaf 0 (solid)
if( p >= 0 && out->cluster >= 0 && loadmodel->visdata )
{
if( p < bmod->visdatasize )
out->compressed_vis = loadmodel->visdata + p;
else Con_Reportf( S_WARN "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i );
}
}
else out->cluster = -1; // no visclusters on bmodels
if( p == -1 ) out->compressed_vis = NULL;
else out->compressed_vis = loadmodel->visdata + p;
// gl underwater warp
if( out->contents != CONTENTS_EMPTY )
{
for( j = 0; j < out->nummarksurfaces; j++ )
{
// mark underwater surfaces
SetBits( out->firstmarksurface[j]->flags, SURF_UNDERWATER );
}
}
}
if( bmod->isworld && loadmodel->leafs[0].contents != CONTENTS_SOLID )
Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", loadmodel->name );
// do some final things for world
if( bmod->isworld && Mod_CheckWaterAlphaSupport( bmod ))
SetBits( world.flags, FWORLD_WATERALPHA );
}
/*
=================
Mod_LoadClipnodes
=================
*/
static void Mod_LoadClipnodes( dbspmodel_t *bmod )
{
dclipnode32_t *out;
int i;
bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( loadmodel->mempool, bmod->numclipnodes * sizeof( *out ));
if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->isbsp30ext && bmod->numclipnodes >= MAX_MAP_CLIPNODES_HLBSP ))
{
dclipnode32_t *in = bmod->clipnodes32;
for( i = 0; i < bmod->numclipnodes; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = in->children[0];
out->children[1] = in->children[1];
}
}
else
{
dclipnode_t *in = bmod->clipnodes;
for( i = 0; i < bmod->numclipnodes; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = (unsigned short)in->children[0];
out->children[1] = (unsigned short)in->children[1];
// Arguire QBSP 'broken' clipnodes
if( out->children[0] >= bmod->numclipnodes )
out->children[0] -= 65536;
if( out->children[1] >= bmod->numclipnodes )
out->children[1] -= 65536;
}
}
// FIXME: fill loadmodel->clipnodes?
loadmodel->numclipnodes = bmod->numclipnodes;
}
/*
=================
Mod_LoadVisibility
=================
*/
static void Mod_LoadVisibility( dbspmodel_t *bmod )
{
loadmodel->visdata = Mem_Malloc( loadmodel->mempool, bmod->visdatasize );
memcpy( loadmodel->visdata, bmod->visdata, bmod->visdatasize );
}
/*
=================
Mod_LoadLightVecs
=================
*/
static void Mod_LoadLightVecs( dbspmodel_t *bmod )
{
if( bmod->deluxdatasize != bmod->lightdatasize )
{
if( bmod->deluxdatasize > 0 )
Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%zu should be %zu)\n", bmod->deluxdatasize, bmod->lightdatasize );
else Mod_LoadDeluxemap( bmod ); // old method
return;
}
bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, bmod->deluxdatasize );
memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize );
}
/*
=================
Mod_LoadShadowmap
=================
*/
static void Mod_LoadShadowmap( dbspmodel_t *bmod )
{
if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 ))
{
if( bmod->shadowdatasize > 0 )
Con_Printf( S_ERROR "Mod_LoadShadowmap: has mismatched size (%zu should be %zu)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 );
return;
}
bmod->shadowdata_out = Mem_Malloc( loadmodel->mempool, bmod->shadowdatasize );
memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize );
}
/*
=================
Mod_LoadLighting
=================
*/
static void Mod_LoadLighting( dbspmodel_t *bmod )
{
int i, lightofs;
msurface_t *surf;
color24 *out;
byte *in;
if( !bmod->lightdatasize )
return;
switch( bmod->lightmap_samples )
{
case 1:
if( !Mod_LoadColoredLighting( bmod ))
{
loadmodel->lightdata = out = (color24 *)Mem_Malloc( loadmodel->mempool, bmod->lightdatasize * sizeof( color24 ));
in = bmod->lightdata;
// expand the white lighting data
for( i = 0; i < bmod->lightdatasize; i++, out++ )
out->r = out->g = out->b = *in++;
}
break;
case 3: // load colored lighting
loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, bmod->lightdatasize );
memcpy( loadmodel->lightdata, bmod->lightdata, bmod->lightdatasize );
SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING );
break;
default:
Host_Error( "Mod_LoadLighting: bad lightmap sample count %i\n", bmod->lightmap_samples );
break;
}
// not supposed to be load ?
if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
{
Mod_LoadLightVecs( bmod );
Mod_LoadShadowmap( bmod );
if( bmod->isworld && bmod->deluxdatasize )
SetBits( world.flags, FWORLD_HAS_DELUXEMAP );
}
surf = loadmodel->surfaces;
// setup lightdata pointers
for( i = 0; i < loadmodel->numsurfaces; i++, surf++ )
{
if( bmod->version == QBSP2_VERSION )
lightofs = bmod->surfaces32[i].lightofs;
else lightofs = bmod->surfaces[i].lightofs;
if( loadmodel->lightdata && lightofs != -1 )
{
int offset = (lightofs / bmod->lightmap_samples);
// NOTE: we divide offset by three because lighting and deluxemap keep their pointers
// into three-bytes structs and shadowmap just monochrome
surf->samples = loadmodel->lightdata + offset;
// if deluxemap is present setup it too
if( bmod->deluxedata_out )
surf->info->deluxemap = bmod->deluxedata_out + offset;
// will be used by mods
if( bmod->shadowdata_out )
surf->info->shadowmap = bmod->shadowdata_out + offset;
}
}
}
/*
=================
Mod_LumpLooksLikeEntities
=================
*/
static int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen )
{
// look for "classname" string
return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0;
}
/*
=================
Mod_LoadBmodelLumps
loading and processing bmodel
=================
*/
qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld )
{
const dheader_t *header = (const dheader_t *)mod_base;
const dextrahdr_t *extrahdr = (const dextrahdr_t *)(mod_base + sizeof( dheader_t ));
dbspmodel_t *bmod = &srcmodel;
model_t *mod = loadmodel;
char wadvalue[2048];
size_t len = 0;
int i, ret, flags = 0;
// always reset the intermediate struct
memset( bmod, 0, sizeof( dbspmodel_t ));
memset( &loadstat, 0, sizeof( loadstat_t ));
Q_strncpy( loadstat.name, loadmodel->name, sizeof( loadstat.name ));
wadvalue[0] = '\0';
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, loadmodel->name );
return false;
}
#endif
switch( header->version )
{
case HLBSP_VERSION:
if( extrahdr->id == IDEXTRAHEADER )
{
SetBits( flags, LUMP_BSP30EXT );
}
// only relevant for half-life maps
else if( !Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_ENTITIES].fileofs, header->lumps[LUMP_ENTITIES].filelen ) &&
Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))
{
// blue-shift swapped lumps
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
break;
}
// intended fallthrough
case Q1BSP_VERSION:
case QBSP2_VERSION:
// everything else
srclumps[0].lumpnumber = LUMP_ENTITIES;
srclumps[1].lumpnumber = LUMP_PLANES;
break;
default:
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", loadmodel->name, header->version, HLBSP_VERSION );
loadstat.numerrors++;
return false;
}
bmod->version = header->version; // share up global
if( isworld )
{
world.flags = 0; // clear world settings
SetBits( flags, LUMP_SAVESTATS|LUMP_SILENT );
}
bmod->isworld = isworld;
bmod->isbsp30ext = FBitSet( flags, LUMP_BSP30EXT );
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );
if( !bmod->isworld && loadstat.numerrors )
{
Con_DPrintf( "Mod_Load%s: %i error(s), %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numerrors, loadstat.numwarnings );
return false; // there were errors, we can't load this map
}
else if( !bmod->isworld && loadstat.numwarnings )
Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings );
// load into heap
Mod_LoadEntities( bmod );
Mod_LoadPlanes( bmod );
Mod_LoadSubmodels( bmod );
Mod_LoadVertexes( bmod );
Mod_LoadEdges( bmod );
Mod_LoadSurfEdges( bmod );
Mod_LoadTextures( bmod );
Mod_LoadVisibility( bmod );
Mod_LoadTexInfo( bmod );
Mod_LoadSurfaces( bmod );
Mod_LoadLighting( bmod );
Mod_LoadMarkSurfaces( bmod );
Mod_LoadLeafs( bmod );
Mod_LoadNodes( bmod );
Mod_LoadClipnodes( bmod );
// preform some post-initalization
Mod_MakeHull0 ();
Mod_SetupSubmodels( bmod );
if( isworld )
{
loadmodel = mod; // restore pointer to world
#if !XASH_DEDICATED
Mod_InitDebugHulls(); // FIXME: build hulls for separate bmodels (shells, medkits etc)
world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer
world.shadowdata = bmod->shadowdata_out; // occlusion data pointer
#endif // XASH_DEDICATED
}
for( i = 0; i < bmod->wadlist.count; i++ )
{
if( !bmod->wadlist.wadusage[i] )
continue;
ret = Q_snprintf( &wadvalue[len], sizeof( wadvalue ), "%s.wad; ", bmod->wadlist.wadnames[i] );
if( ret == -1 )
{
Con_DPrintf( S_WARN "Too many wad files for output!\n" );
break;
}
len += ret;
}
if( COM_CheckString( wadvalue ))
{
wadvalue[Q_strlen( wadvalue ) - 2] = '\0'; // kill the last semicolon
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
}
return true;
}
static int Mod_LumpLooksLikeEntitiesFile( file_t *f, const dlump_t *l, int flags, const char *msg )
{
char *buf;
int ret;
if( FS_Seek( f, l->fileofs, SEEK_SET ) < 0 )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 %s lump past end of file\n", loadstat.name, msg );
return -1;
}
buf = Z_Malloc( l->filelen + 1 );
if( FS_Read( f, buf, l->filelen ) != l->filelen )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "can't read %s lump of map ^2%s^7", msg, loadstat.name );
Z_Free( buf );
return -1;
}
ret = Mod_LumpLooksLikeEntities( buf, l->filelen );
Z_Free( buf );
return ret;
}
/*
=================
Mod_TestBmodelLumps
check for possible errors
return real entities lump (for bshift swapped lumps)
=================
*/
qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities )
{
const dheader_t *header = (const dheader_t *)mod_base;
const dextrahdr_t *extrahdr = (const dextrahdr_t *)( mod_base + sizeof( dheader_t ));
int i, flags = LUMP_TESTONLY;
// always reset the intermediate struct
memset( &loadstat, 0, sizeof( loadstat_t ));
// store the name to correct show errors and warnings
Q_strncpy( loadstat.name, name, sizeof( loadstat.name ));
if( silent )
SetBits( flags, LUMP_SILENT );
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, name );
return false;
}
#endif
switch( header->version )
{
case HLBSP_VERSION:
if( extrahdr->id == IDEXTRAHEADER )
{
SetBits( flags, LUMP_BSP30EXT );
}
else
{
// only relevant for half-life maps
int ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_ENTITIES], flags, "entities" );
if( ret < 0 ) return false;
if( !ret )
{
ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_PLANES], flags, "planes" );
if( ret < 0 ) return false;
if( ret )
{
// blue-shift swapped lumps
*entities = header->lumps[LUMP_PLANES];
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
break;
}
}
}
// intended fallthrough
case Q1BSP_VERSION:
case QBSP2_VERSION:
// everything else
*entities = header->lumps[LUMP_ENTITIES];
srclumps[0].lumpnumber = LUMP_ENTITIES;
srclumps[1].lumpnumber = LUMP_PLANES;
break;
default:
// don't early out: let me analyze errors
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", name, header->version, HLBSP_VERSION );
loadstat.numerrors++;
break;
}
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );
if( loadstat.numerrors )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( "Mod_LoadWorld: %i error(s), %i warning(s)\n", loadstat.numerrors, loadstat.numwarnings );
return false; // there were errors, we can't load this map
}
else if( loadstat.numwarnings )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( "Mod_LoadWorld: %i warning(s)\n", loadstat.numwarnings );
}
return true;
}
/*
=================
Mod_LoadBrushModel
=================
*/
void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded )
{
char poolname[MAX_VA_STRING];
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", loadmodel->name );
if( loaded ) *loaded = false;
loadmodel->mempool = Mem_AllocPool( poolname );
loadmodel->type = mod_brush;
// loading all the lumps into heap
if( !Mod_LoadBmodelLumps( buffer, world.loading ))
return; // there were errors
if( world.loading ) worldmodel = mod;
if( loaded ) *loaded = true; // all done
}
/*
==================
Mod_CheckLump
check lump for existing
==================
*/
int Mod_CheckLump( const char *filename, const int lump, int *lumpsize )
{
file_t *f = FS_Open( filename, "rb", false );
byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
dextrahdr_t *extrahdr;
dheader_t *header;
if( !f ) return LUMP_LOAD_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_LOAD_BAD_HEADER;
}
header = (dheader_t *)buffer;
if( header->version != HLBSP_VERSION )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_LOAD_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_LOAD_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen <= 0 )
{
FS_Close( f );
return LUMP_LOAD_NOT_EXIST;
}
if( lumpsize )
*lumpsize = extrahdr->lumps[lump].filelen;
FS_Close( f );
return LUMP_LOAD_OK;
}
/*
==================
Mod_ReadLump
reading random lump by user request
==================
*/
int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize )
{
file_t *f = FS_Open( filename, "rb", false );
byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
dextrahdr_t *extrahdr;
dheader_t *header;
byte *data;
int length;
if( !f ) return LUMP_LOAD_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_LOAD_BAD_HEADER;
}
header = (dheader_t *)buffer;
if( header->version != HLBSP_VERSION )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_LOAD_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_LOAD_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen <= 0 )
{
FS_Close( f );
return LUMP_LOAD_NOT_EXIST;
}
data = malloc( extrahdr->lumps[lump].filelen + 1 );
length = extrahdr->lumps[lump].filelen;
if( !data )
{
FS_Close( f );
return LUMP_LOAD_MEM_FAILED;
}
FS_Seek( f, extrahdr->lumps[lump].fileofs, SEEK_SET );
if( FS_Read( f, data, length ) != length )
{
free( data );
FS_Close( f );
return LUMP_LOAD_CORRUPTED;
}
data[length] = 0; // write term
FS_Close( f );
if( lumpsize )
*lumpsize = length;
*lumpdata = data;
return LUMP_LOAD_OK;
}
/*
==================
Mod_SaveLump
writing lump by user request
only empty lumps is allows
==================
*/
int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize )
{
byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
int result, dummy = lumpsize;
dextrahdr_t *extrahdr;
dheader_t *header;
file_t *f;
if( !lumpdata || lumpsize <= 0 )
return LUMP_SAVE_NO_DATA;
// make sure what .bsp is placed into gamedir and not in pak
if( !FS_GetDiskPath( filename, true ))
return LUMP_SAVE_COULDNT_OPEN;
// first we should sure what we allow to rewrite this .bsp
result = Mod_CheckLump( filename, lump, &dummy );
if( result != LUMP_LOAD_NOT_EXIST )
return result;
f = FS_Open( filename, "e+b", true );
if( !f ) return LUMP_SAVE_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_SAVE_BAD_HEADER;
}
header = (dheader_t *)buffer;
// these checks below are redundant
if( header->version != HLBSP_VERSION )
{
FS_Close( f );
return LUMP_SAVE_BAD_VERSION;
}
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_SAVE_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_SAVE_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen != 0 )
{
FS_Close( f );
return LUMP_SAVE_ALREADY_EXIST;
}
FS_Seek( f, 0, SEEK_END );
// will be saved later
extrahdr->lumps[lump].fileofs = FS_Tell( f );
extrahdr->lumps[lump].filelen = lumpsize;
if( FS_Write( f, lumpdata, lumpsize ) != lumpsize )
{
FS_Close( f );
return LUMP_SAVE_CORRUPTED;
}
// update the header
FS_Seek( f, sizeof( dheader_t ), SEEK_SET );
if( FS_Write( f, extrahdr, sizeof( dextrahdr_t )) != sizeof( dextrahdr_t ))
{
FS_Close( f );
return LUMP_SAVE_CORRUPTED;
}
FS_Close( f );
return LUMP_SAVE_OK;
}