Initial commit.
This commit is contained in:
67
code/qcommon/MiniHeap.h
Normal file
67
code/qcommon/MiniHeap.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#if !defined(MINIHEAP_H_INC)
|
||||
#define MINIHEAP_H_INC
|
||||
|
||||
|
||||
class CMiniHeap
|
||||
{
|
||||
char *mHeap;
|
||||
char *mCurrentHeap;
|
||||
int mSize;
|
||||
#if _DEBUG
|
||||
int mMaxAlloc;
|
||||
#endif
|
||||
public:
|
||||
|
||||
// reset the heap back to the start
|
||||
void ResetHeap()
|
||||
{
|
||||
#if _DEBUG
|
||||
if ((int)mCurrentHeap - (int)mHeap>mMaxAlloc)
|
||||
{
|
||||
mMaxAlloc=(int)mCurrentHeap - (int)mHeap;
|
||||
}
|
||||
#endif
|
||||
mCurrentHeap = mHeap;
|
||||
}
|
||||
|
||||
// initialise the heap
|
||||
CMiniHeap(int size)
|
||||
{
|
||||
mHeap = (char *)Z_Malloc(size, TAG_GHOUL2, qtrue);
|
||||
mSize = size;
|
||||
#if _DEBUG
|
||||
mMaxAlloc=0;
|
||||
#endif
|
||||
if (mHeap)
|
||||
{
|
||||
ResetHeap();
|
||||
}
|
||||
}
|
||||
|
||||
// free up the heap
|
||||
~CMiniHeap()
|
||||
{
|
||||
if (mHeap)
|
||||
{
|
||||
// the quake heap will be long gone, no need to free it Z_Free(mHeap);
|
||||
}
|
||||
}
|
||||
|
||||
// give me some space from the heap please
|
||||
char *MiniHeapAlloc(int size)
|
||||
{
|
||||
if (size < (mSize - ((int)mCurrentHeap - (int)mHeap)))
|
||||
{
|
||||
char *tempAddress = mCurrentHeap;
|
||||
mCurrentHeap += size;
|
||||
return tempAddress;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
extern CMiniHeap *G2VertSpaceServer;
|
||||
|
||||
|
||||
#endif //MINIHEAP_H_INC
|
||||
162
code/qcommon/chash.h
Normal file
162
code/qcommon/chash.h
Normal file
@@ -0,0 +1,162 @@
|
||||
// Notes
|
||||
// Make sure extension is stripped if it needs to be
|
||||
|
||||
// Template class must have
|
||||
// 1. A GetName() accessor - a null terminated string case insensitive
|
||||
// 2. A Destroy() function - normally "delete this"
|
||||
// 3. SetNext(T *) and T *GetNext() functions
|
||||
|
||||
#define HASH_SIZE 1024
|
||||
|
||||
template <class T, int TSize = HASH_SIZE, int (*TCompare)(const char *, const char *) = &strcmp>
|
||||
|
||||
class CHash
|
||||
{
|
||||
private:
|
||||
T *mHashTable[TSize];
|
||||
T *mNext;
|
||||
int mCount;
|
||||
T *mPrevious; // Internal work variable
|
||||
long mHash; // Internal work variable
|
||||
|
||||
// Creates the hash value and sets the mHash member
|
||||
void CreateHash(const char *key)
|
||||
{
|
||||
int i = 0;
|
||||
char letter;
|
||||
|
||||
mHash = 0;
|
||||
letter = *key++;
|
||||
while (letter)
|
||||
{
|
||||
mHash += (long)(letter) * (i + 119);
|
||||
|
||||
i++;
|
||||
letter = *key++;
|
||||
}
|
||||
mHash &= TSize - 1;
|
||||
}
|
||||
public:
|
||||
// Constructor
|
||||
CHash(void)
|
||||
{
|
||||
memset(mHashTable, NULL, sizeof(mHashTable));
|
||||
mNext = NULL;
|
||||
mCount = 0;
|
||||
mPrevious = NULL;
|
||||
mHash = 0;
|
||||
}
|
||||
// Destructor
|
||||
~CHash(void)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
// Com_OPrintf("Shutting down %s hash table .....", typeid(T).name());
|
||||
#endif
|
||||
clear();
|
||||
#ifdef _DEBUG
|
||||
// Com_OPrintf(" done\n");
|
||||
#endif
|
||||
}
|
||||
// Returns the total number of entries in the hash table
|
||||
int count(void) const { return(mCount); }
|
||||
|
||||
// Inserts an item into the hash table
|
||||
void insert(T *item)
|
||||
{
|
||||
CreateHash(item->GetName());
|
||||
item->SetNext(mHashTable[mHash]);
|
||||
mHashTable[mHash] = item;
|
||||
mCount++;
|
||||
}
|
||||
// Finds an item in the hash table (sets the mPrevious member)
|
||||
T *find(const char *key)
|
||||
{
|
||||
CreateHash(key);
|
||||
T *item = mHashTable[mHash];
|
||||
mPrevious = NULL;
|
||||
while(item)
|
||||
{
|
||||
mNext = item->GetNext();
|
||||
if(!TCompare(item->GetName(), key))
|
||||
{
|
||||
return(item);
|
||||
}
|
||||
mPrevious = item;
|
||||
item = mNext;
|
||||
}
|
||||
return(NULL);
|
||||
}
|
||||
// Remove item from the hash table referenced by key
|
||||
bool remove(const char *key)
|
||||
{
|
||||
T *item = find(key);
|
||||
if(item)
|
||||
{
|
||||
T *next = item->GetNext();
|
||||
if(mPrevious)
|
||||
{
|
||||
mPrevious->SetNext(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
mHashTable[mHash] = next;
|
||||
}
|
||||
item->Destroy();
|
||||
mCount--;
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
// Remove item from hash referenced by item
|
||||
bool remove(T *item)
|
||||
{
|
||||
return(remove(item->GetName()));
|
||||
}
|
||||
// Returns the first valid entry
|
||||
T *head(void)
|
||||
{
|
||||
mHash = -1;
|
||||
mNext = NULL;
|
||||
return(next());
|
||||
}
|
||||
// Returns the next entry in the hash table
|
||||
T *next(void)
|
||||
{
|
||||
T *item;
|
||||
|
||||
assert(mHash < TSize);
|
||||
|
||||
if(mNext)
|
||||
{
|
||||
item = mNext;
|
||||
mNext = item->GetNext();
|
||||
return(item);
|
||||
}
|
||||
mHash++;
|
||||
|
||||
for( ; mHash < TSize; mHash++)
|
||||
{
|
||||
item = mHashTable[mHash];
|
||||
if(item)
|
||||
{
|
||||
mNext = item->GetNext();
|
||||
return(item);
|
||||
}
|
||||
}
|
||||
return(NULL);
|
||||
}
|
||||
// Destroy all entries in the hash table
|
||||
void clear(void)
|
||||
{
|
||||
T *item = head();
|
||||
while(item)
|
||||
{
|
||||
remove(item);
|
||||
item = next();
|
||||
}
|
||||
}
|
||||
// Override the [] operator
|
||||
T *operator[](const char *key) { return(find(key)); }
|
||||
};
|
||||
|
||||
// end
|
||||
1488
code/qcommon/cm_draw.cpp
Normal file
1488
code/qcommon/cm_draw.cpp
Normal file
File diff suppressed because it is too large
Load Diff
245
code/qcommon/cm_draw.h
Normal file
245
code/qcommon/cm_draw.h
Normal file
@@ -0,0 +1,245 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CDraw32 Class Interface
|
||||
//
|
||||
// Basic drawing routines for 32-bit per pixel buffer
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(CM_DRAW_H_INC)
|
||||
#define CM_DRAW_H_INC
|
||||
|
||||
// calc offset into image array for a pixel at (x,y)
|
||||
#define PIXPOS(x,y,stride) (((y)*(stride))+(x))
|
||||
|
||||
#ifndef MIN
|
||||
// handy macros
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||||
#define ABS(x) ((x)<0 ? -(x):(x))
|
||||
#define SIGN(x) (((x) < 0) ? -1 : (((x) > 0) ? 1 : 0))
|
||||
#endif
|
||||
|
||||
#ifndef CLAMP
|
||||
#define SWAP(a,b) { a^=b; b^=a; a^=b; }
|
||||
#define SQR(a) ((a)*(a))
|
||||
#define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : (v))
|
||||
#define LERP(t, a, b) (((b)-(a))*(t) + (a))
|
||||
|
||||
// round a to nearest integer towards 0
|
||||
#define FLOOR(a) ((a)>0 ? (int)(a) : -(int)(-a))
|
||||
|
||||
// round a to nearest integer away from 0
|
||||
#define CEILING(a) \
|
||||
((a)==(int)(a) ? (a) : (a)>0 ? 1+(int)(a) : -(1+(int)(-a)))
|
||||
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
class CPixel32
|
||||
{
|
||||
public:
|
||||
byte r;
|
||||
byte g;
|
||||
byte b;
|
||||
byte a;
|
||||
|
||||
CPixel32(byte R = 0, byte G = 0, byte B = 0, byte A = 255) : r(R), g(G), b(B), a(A) {}
|
||||
CPixel32(long l) {r = (l >> 24) & 0xff; g = (l >> 16) & 0xff; b = (l >> 8) & 0xff; a = l & 0xff;};
|
||||
|
||||
~CPixel32()
|
||||
{}
|
||||
};
|
||||
|
||||
#define PIX32_SIZE sizeof(CPixel32)
|
||||
|
||||
// standard image operator macros
|
||||
#define IMAGE_SIZE(width,height) ((width)*(height)*(PIX32_SIZE))
|
||||
|
||||
|
||||
inline CPixel32 AVE_PIX (CPixel32 x, CPixel32 y)
|
||||
{ CPixel32 t; t.r = (byte)(((int)x.r + (int)y.r)>>1);
|
||||
t.g = (byte)(((int)x.g + (int)y.g)>>1);
|
||||
t.b = (byte)(((int)x.b + (int)y.b)>>1);
|
||||
t.a = (byte)(((int)x.a + (int)y.a)>>1); return t;}
|
||||
|
||||
inline CPixel32 ALPHA_PIX (CPixel32 x, CPixel32 y, long alpha, long inv_alpha)
|
||||
{ CPixel32 t; t.r = (byte)((x.r*alpha + y.r*inv_alpha)>>8);
|
||||
t.g = (byte)((x.g*alpha + y.g*inv_alpha)>>8);
|
||||
t.b = (byte)((x.b*alpha + y.b*inv_alpha)>>8);
|
||||
// t.a = (byte)((x.a*alpha + y.a*inv_alpha)>>8); return t;}
|
||||
t.a = y.a; return t;}
|
||||
|
||||
inline CPixel32 LIGHT_PIX (CPixel32 p, long light)
|
||||
{ CPixel32 t;
|
||||
t.r = (byte)CLAMP(((p.r * light)>>10) + p.r, 0, 255);
|
||||
t.g = (byte)CLAMP(((p.g * light)>>10) + p.g, 0, 255);
|
||||
t.b = (byte)CLAMP(((p.b * light)>>10) + p.b, 0, 255);
|
||||
t.a = p.a; return t;}
|
||||
|
||||
// Colors are 32-bit RGBA
|
||||
|
||||
// draw class
|
||||
class CDraw32
|
||||
{
|
||||
public: // static drawing context - static so we set only ONCE for many draw calls
|
||||
static CPixel32* buffer; // pointer to pixel buffer (one active)
|
||||
static long buf_width; // size of buffer
|
||||
static long buf_height; // size of buffer
|
||||
static long stride; // stride of buffer in pixels
|
||||
static long clip_min_x; // clip bounds
|
||||
static long clip_min_y; // clip bounds
|
||||
static long clip_max_x; // clip bounds
|
||||
static long clip_max_y; // clip bounds
|
||||
static long* row_off; // Table for quick Y calculations
|
||||
|
||||
private:
|
||||
void BlitClip(long& dstX, long& dstY,
|
||||
long& width, long& height,
|
||||
long& srcX, long& srcY);
|
||||
|
||||
protected:
|
||||
public:
|
||||
CDraw32(); // constructor
|
||||
~CDraw32(); // destructor
|
||||
|
||||
// set the rect to clip drawing functions to
|
||||
static void SetClip(long min_x, long min_y,long max_x, long max_y)
|
||||
{clip_min_x = MAX(min_x,0); clip_max_x = MIN(max_x,buf_width-1);
|
||||
clip_min_y = MAX(min_y,0); clip_max_y = MIN(max_y,buf_height-1);}
|
||||
|
||||
static void GetClip(long& min_x, long& min_y,long& max_x, long& max_y)
|
||||
{min_x = clip_min_x; min_y = clip_min_y;
|
||||
max_x = clip_max_x; max_y = clip_max_y; }
|
||||
|
||||
// set the buffer to use for drawing off-screen
|
||||
static void SetBuffer(CPixel32* buf) {buffer = buf;};
|
||||
|
||||
// set the dimensions of the off-screen buffer
|
||||
static bool SetBufferSize(long width,long height,long stride_len);
|
||||
|
||||
// call this to free the table for quick y calcs before the program ends
|
||||
static void CleanUp(void)
|
||||
{if (row_off) delete [] row_off; row_off=NULL; buf_width=0; buf_height=0;}
|
||||
|
||||
// set a pixel at (x,y) to color (no clipping)
|
||||
void PutPixNC(long x, long y, CPixel32 color)
|
||||
{buffer[row_off[y] + x] = color;}
|
||||
|
||||
// set a pixel at (x,y) to color
|
||||
void PutPix(long x, long y, CPixel32 color)
|
||||
{ // clipping check
|
||||
if (x < clip_min_x || x > clip_max_x ||
|
||||
y < clip_min_y || y > clip_max_y)
|
||||
return;
|
||||
PutPixNC(x,y,color);
|
||||
}
|
||||
|
||||
// get the color of a pixel at (x,y)
|
||||
CPixel32 GetPix(long x, long y)
|
||||
{return buffer[row_off[y] + x];}
|
||||
|
||||
// set a pixel at (x,y) with 50% translucency (no clip)
|
||||
void PutPixAveNC(long x, long y, CPixel32 color)
|
||||
{ PutPixNC(x,y,AVE_PIX(GetPix(x, y), color)); }
|
||||
|
||||
// set a pixel at (x,y) with 50% translucency
|
||||
void PutPixAve(long x, long y, CPixel32 color)
|
||||
{ // clipping check
|
||||
if (x < clip_min_x || x > clip_max_x ||
|
||||
y < clip_min_y || y > clip_max_y)
|
||||
return;
|
||||
PutPixNC(x,y,AVE_PIX(GetPix(x, y), color));
|
||||
}
|
||||
|
||||
// set a pixel at (x,y) with translucency level (no clip)
|
||||
void PutPixAlphaNC(long x, long y, CPixel32 color)
|
||||
{ PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));}
|
||||
|
||||
// set a pixel at (x,y) with translucency level
|
||||
void PutPixAlpha(long x, long y, CPixel32 color)
|
||||
{ // clipping check
|
||||
if (x < clip_min_x || x > clip_max_x ||
|
||||
y < clip_min_y || y > clip_max_y)
|
||||
return;
|
||||
PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));}
|
||||
|
||||
// clear screen buffer to color from start to end line
|
||||
void ClearLines(CPixel32 color,long start,long end);
|
||||
|
||||
// clear screen buffer to color provided
|
||||
void ClearBuffer(CPixel32 color)
|
||||
{ClearLines(color,0,buf_height-1);};
|
||||
|
||||
// fill buffer alpha from start to end line
|
||||
void SetAlphaLines(byte alpha,long start,long end);
|
||||
|
||||
// clear screen buffer to color provided
|
||||
void SetAlphaBuffer(byte alpha)
|
||||
{SetAlphaLines(alpha,0,buf_height-1);};
|
||||
|
||||
// clip a line segment to the clip rect
|
||||
bool ClipLine(long& x1, long& y1, long& x2, long& y2);
|
||||
|
||||
// draw a solid colored line, no clipping
|
||||
void DrawLineNC(long x1, long y1, long x2, long y2, CPixel32 color);
|
||||
|
||||
// draw a solid color line
|
||||
void DrawLine(long x1, long y1, long x2, long y2, CPixel32 color)
|
||||
{ if (ClipLine(x1,y1,x2,y2)) DrawLineNC(x1,y1,x2,y2,color);}
|
||||
|
||||
void DrawLineAveNC(long x1, long y1, long x2, long y2, CPixel32 color);
|
||||
|
||||
// draw a translucent solid color line
|
||||
void DrawLineAve(long x1, long y1, long x2, long y2, CPixel32 color)
|
||||
{ if (ClipLine(x1,y1,x2,y2)) DrawLineAveNC(x1,y1,x2,y2,color);}
|
||||
|
||||
// draw an anti-aliased line, no clipping
|
||||
void DrawLineAANC(long x0, long y0, long x1, long y1, CPixel32 color);
|
||||
|
||||
// draw an anti-aliased line
|
||||
void DrawLineAA(long x1, long y1, long x2, long y2, CPixel32 color)
|
||||
{ if (ClipLine(x1,y1,x2,y2)) DrawLineAANC(x1,y1,x2,y2,color);}
|
||||
|
||||
// draw a filled rectangle, no clipping
|
||||
void DrawRectNC(long ulx, long uly, long width, long height,CPixel32 color);
|
||||
|
||||
// draw a filled rectangle
|
||||
void DrawRect(long ulx, long uly, long width, long height, CPixel32 color);
|
||||
|
||||
// draw a filled rectangle
|
||||
void DrawRectAve(long ulx, long uly, long width, long height,CPixel32 color);
|
||||
|
||||
// draw a box (unfilled rectangle) no clip
|
||||
void DrawBoxNC(long ulx, long uly, long width, long height, CPixel32 color);
|
||||
|
||||
// draw a box (unfilled rectangle)
|
||||
void DrawBox(long ulx, long uly, long width, long height, CPixel32 color);
|
||||
|
||||
// draw a box (unfilled rectangle)
|
||||
void DrawBoxAve(long ulx, long uly, long width, long height, CPixel32 color);
|
||||
|
||||
// draw a circle with fill and edge colors
|
||||
void DrawCircle(long xc, long yc, long r, CPixel32 edge, CPixel32 fill);
|
||||
|
||||
// draw a circle with fill and edge colors averaged with dest
|
||||
void DrawCircleAve(long xc, long yc, long r, CPixel32 edge, CPixel32 fill);
|
||||
|
||||
// draw a polygon (complex) with fill and edge colors
|
||||
void DrawPolygon(long nvert, POINT *point, CPixel32 edge, CPixel32 fill);
|
||||
|
||||
// simple blit function
|
||||
void BlitNC(long dstX, long dstY, long dstWidth, long dstHeight,
|
||||
CPixel32* srcImage, long srcX, long srcY, long srcStride);
|
||||
|
||||
void Blit(long dstX, long dstY, long dstWidth, long dstHeight,
|
||||
CPixel32* srcImage, long srcX, long srcY, long srcStride);
|
||||
|
||||
// blit image times color
|
||||
void BlitColor(long dstX, long dstY, long dstWidth, long dstHeight,
|
||||
CPixel32* srcImage, long srcX, long srcY, long srcStride, CPixel32 color);
|
||||
|
||||
void Emboss(long dstX, long dstY, long width, long height,
|
||||
CPixel32* clrImage, long clrX, long clrY, long clrStride);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif
|
||||
271
code/qcommon/cm_landscape.h
Normal file
271
code/qcommon/cm_landscape.h
Normal file
@@ -0,0 +1,271 @@
|
||||
#if !defined(CM_LANDSCAPE_H_INC)
|
||||
#define CM_LANDSCAPE_H_INC
|
||||
|
||||
#pragma warning (push, 3) //go back down to 3 for the stl include
|
||||
#include <list>
|
||||
#pragma warning (pop)
|
||||
|
||||
using namespace std;
|
||||
|
||||
// These are the root classes using data shared in both the server and the renderer.
|
||||
// This common data is also available to physics
|
||||
|
||||
#define HEIGHT_RESOLUTION 256
|
||||
|
||||
// Trying to make a guess at the optimal step through the patches
|
||||
// This is the average of 1 side and the diagonal presuming a square patch
|
||||
#define TERRAIN_STEP_MAGIC (1.0f / 1.2071f)
|
||||
|
||||
#define MIN_TERXELS 2
|
||||
#define MAX_TERXELS 8
|
||||
// Defined as 1 << (sqrt(MAX_TERXELS) + 1)
|
||||
#define MAX_VARIANCE_SIZE 16
|
||||
|
||||
// Maximum number of instances to pick from an instance file
|
||||
#define MAX_INSTANCE_TYPES 16
|
||||
|
||||
// Types of areas
|
||||
|
||||
typedef enum
|
||||
{
|
||||
AT_NONE,
|
||||
AT_FLAT,
|
||||
AT_BSP,
|
||||
AT_NPC,
|
||||
AT_GROUP,
|
||||
AT_RIVER,
|
||||
AT_OBJECTIVE,
|
||||
AT_PLAYER,
|
||||
|
||||
} areaType_t;
|
||||
|
||||
class CArea
|
||||
{
|
||||
private:
|
||||
vec3_t mPosition;
|
||||
float mRadius;
|
||||
float mAngle;
|
||||
float mAngleDiff;
|
||||
int mType;
|
||||
int mVillageID;
|
||||
public:
|
||||
CArea(void) {}
|
||||
~CArea(void) {}
|
||||
|
||||
void Init(vec3_t pos, float radius, float angle = 0.0f, int type = AT_NONE, float angleDiff = 0.0f, int villageID = 0)
|
||||
{
|
||||
VectorCopy(pos, mPosition);
|
||||
mRadius = radius;
|
||||
mAngle = angle;
|
||||
mAngleDiff = angleDiff;
|
||||
mType = type;
|
||||
mVillageID = villageID;
|
||||
}
|
||||
float GetRadius(void) const { return(mRadius); }
|
||||
float GetAngle(void) const { return(mAngle); }
|
||||
float GetAngleDiff(void) const { return(mAngleDiff); }
|
||||
vec3_t &GetPosition(void) { return(mPosition); }
|
||||
int GetType(void) const { return(mType); }
|
||||
int GetVillageID(void) const { return(mVillageID); }
|
||||
};
|
||||
|
||||
typedef list<CArea*> areaList_t;
|
||||
typedef list<CArea*>::iterator areaIter_t;
|
||||
|
||||
class CCMHeightDetails
|
||||
{
|
||||
private:
|
||||
int mContents;
|
||||
int mSurfaceFlags;
|
||||
public:
|
||||
CCMHeightDetails(void) {}
|
||||
~CCMHeightDetails(void) {}
|
||||
|
||||
// Accessors
|
||||
const int GetSurfaceFlags(void) const { return(mSurfaceFlags); }
|
||||
const int GetContents(void) const { return(mContents); }
|
||||
void SetFlags(const int con, const int sf) { mContents = con; mSurfaceFlags = sf; }
|
||||
};
|
||||
|
||||
class CCMPatch
|
||||
{
|
||||
protected:
|
||||
class CCMLandScape *owner; // Owning landscape
|
||||
int mHx, mHy; // Terxel coords of patch
|
||||
byte *mHeightMap; // Pointer to height map to use
|
||||
byte mCornerHeights[4]; // Heights at the corners of the patch
|
||||
vec3_t mWorldCoords; // World coordinate offset of this patch.
|
||||
vec3pair_t mBounds; // mins and maxs of the patch for culling
|
||||
int mNumBrushes; // number of brushes to collide with in the patch
|
||||
struct cbrush_s *mPatchBrushData; // List of brushes that make up the patch
|
||||
int mSurfaceFlags; // surfaceflag of the heightshader
|
||||
int mContentFlags; // contents of the heightshader
|
||||
public:
|
||||
// Constructors
|
||||
CCMPatch(void) {}
|
||||
~CCMPatch(void);
|
||||
|
||||
// Accessors
|
||||
const vec3_t &GetWorld(void) const { return(mWorldCoords); }
|
||||
const vec3_t &GetMins(void) const { return(mBounds[0]); }
|
||||
const vec3_t &GetMaxs(void) const { return(mBounds[1]); }
|
||||
const vec3pair_t &GetBounds(void) const { return(mBounds); }
|
||||
const int GetHeightMapX(void) const { return(mHx); }
|
||||
const int GetHeightMapY(void) const { return(mHy); }
|
||||
const int GetHeight(int corner) const { return(mCornerHeights[corner]); }
|
||||
const int GetNumBrushes(void) const { return(mNumBrushes); }
|
||||
struct cbrush_s *GetCollisionData(void) const { return(mPatchBrushData); }
|
||||
|
||||
void SetSurfaceFlags(const int in) { mSurfaceFlags = in; }
|
||||
const int GetSurfaceFlags(void) const { return(mSurfaceFlags); }
|
||||
void SetContents(const int in) { mContentFlags = in; }
|
||||
const int GetContents(void) const { return(mContentFlags); }
|
||||
|
||||
// Prototypes
|
||||
void Init(CCMLandScape *ls, int heightX, int heightY, vec3_t world, byte *hMap, byte *patchBrushData);
|
||||
void InitPlane(struct cbrushside_s *side, cplane_t *plane, vec3_t p0, vec3_t p1, vec3_t p2);
|
||||
void CreatePatchPlaneData(void);
|
||||
|
||||
void* GetAdjacentBrushX ( int x, int y );
|
||||
void* GetAdjacentBrushY ( int x, int y );
|
||||
};
|
||||
|
||||
class CRandomTerrain;
|
||||
|
||||
class CCMLandScape
|
||||
{
|
||||
private:
|
||||
int mRefCount; // Number of times this class is referenced
|
||||
thandle_t mTerrainHandle;
|
||||
byte *mHeightMap; // Pointer to byte array of height samples
|
||||
byte *mFlattenMap; // Pointer to byte array of flatten samples
|
||||
int mWidth, mHeight; // Width and height of heightMap excluding the 1 pixel edge
|
||||
int mTerxels; // Number of terxels per patch side
|
||||
vec3_t mTerxelSize; // Vector to scale heightMap samples to real world coords
|
||||
vec3pair_t mBounds; // Real world bounds of terrain brush
|
||||
vec3_t mSize; // Size of terrain brush in real world coords excluding 1 patch edge
|
||||
vec3_t mPatchSize; // Size of each patch in the x and y directions only
|
||||
float mPatchScalarSize; // Horizontal size of the patch
|
||||
int mBlockWidth, mBlockHeight; // Width and height of heightfield on blocks
|
||||
CCMPatch *mPatches;
|
||||
byte *mPatchBrushData; // Base memory from which the patch brush data is taken
|
||||
bool mHasPhysics; // Set to true unless disabled
|
||||
CRandomTerrain *mRandomTerrain;
|
||||
|
||||
int mBaseWaterHeight; // Base water height in terxels
|
||||
float mWaterHeight; // Real world height of the water
|
||||
int mWaterContents; // Contents of the water shader
|
||||
int mWaterSurfaceFlags; // Surface flags of the water shader
|
||||
|
||||
unsigned long holdrand;
|
||||
|
||||
list<CArea *> mAreas; // List of flattened areas on this landscape
|
||||
list<CArea *>::iterator mAreasIt;
|
||||
|
||||
CCMHeightDetails mHeightDetails[HEIGHT_RESOLUTION]; // Surfaceflags per height
|
||||
vec3_t *mCoords; // Temp storage for real world coords
|
||||
|
||||
public:
|
||||
CCMLandScape(const char *configstring, bool server);
|
||||
~CCMLandScape(void);
|
||||
|
||||
CCMPatch *GetPatch(int x, int y);
|
||||
|
||||
// Prototypes
|
||||
void PatchCollide(struct traceWork_s *tw, trace_t &trace, const vec3_t start, const vec3_t end, int checkcount);
|
||||
void TerrainPatchIterate(void (*IterateFunc)( CCMPatch *, void * ), void *userdata) const;
|
||||
float GetWorldHeight(vec3_t origin, const vec3pair_t bounds, bool aboveGround) const;
|
||||
float WaterCollide(const vec3_t begin, const vec3_t end, float fraction) const;
|
||||
void UpdatePatches(void);
|
||||
void GetTerxelLocalCoords ( int x, int y, vec3_t coords[8] );
|
||||
void LoadTerrainDef(const char *td);
|
||||
void SetShaders(int height, class CCMShader *shader);
|
||||
void FlattenArea(CArea *area, int height, bool save, bool forceHeight, bool smooth);
|
||||
void CarveLine ( vec3_t start, vec3_t end, int depth, int width );
|
||||
void CarveBezierCurve ( int numCtlPoints, vec3_t* ctlPoints, int steps, int depth, int size );
|
||||
void SaveArea(CArea *area);
|
||||
float FractionBelowLevel(CArea *area, int height);
|
||||
bool AreaCollision(CArea *area, int *areaTypes, int areaTypeCount);
|
||||
CArea *GetFirstArea(void);
|
||||
CArea *GetFirstObjectiveArea(void);
|
||||
CArea *GetPlayerArea(void);
|
||||
CArea *GetNextArea(void);
|
||||
CArea *GetNextObjectiveArea(void);
|
||||
|
||||
// Accessors
|
||||
const int GetRefCount(void) const { return(mRefCount); }
|
||||
void IncreaseRefCount(void) { mRefCount++; }
|
||||
void DecreaseRefCount(void) { mRefCount--; }
|
||||
const vec3pair_t &GetBounds(void) const { return(mBounds); }
|
||||
const vec3_t &GetMins(void) const { return(mBounds[0]); }
|
||||
const vec3_t &GetMaxs(void) const { return(mBounds[1]); }
|
||||
const vec3_t &GetSize(void) const { return(mSize); }
|
||||
const vec3_t &GetTerxelSize(void) const { return(mTerxelSize); }
|
||||
const vec3_t &GetPatchSize(void) const { return(mPatchSize); }
|
||||
const float GetPatchWidth(void) const { return(mPatchSize[0]); }
|
||||
const float GetPatchHeight(void) const { return(mPatchSize[1]); }
|
||||
const float GetPatchScalarSize(void) const { return(mPatchScalarSize); }
|
||||
const int GetTerxels(void) const { return(mTerxels); }
|
||||
const int GetRealWidth(void) const { return(mWidth + 1); }
|
||||
const int GetRealHeight(void) const { return(mHeight + 1); }
|
||||
const int GetRealArea(void) const { return((mWidth + 1) * (mHeight + 1)); }
|
||||
const int GetWidth(void) const { return(mWidth); }
|
||||
const int GetHeight(void) const { return(mHeight); }
|
||||
const int GetArea(void) const { return(mWidth * mHeight); }
|
||||
const int GetBlockWidth(void) const { return(mBlockWidth); }
|
||||
const int GetBlockHeight(void) const { return(mBlockHeight); }
|
||||
const int GetBlockCount(void) const { return(mBlockWidth * mBlockHeight); }
|
||||
byte *GetHeightMap(void) const { return(mHeightMap); }
|
||||
byte *GetFlattenMap(void) const { return(mFlattenMap); }
|
||||
const thandle_t GetTerrainId(void) const { return(mTerrainHandle); }
|
||||
void SetTerrainId(const thandle_t terrainId) { mTerrainHandle = terrainId; }
|
||||
const float CalcWorldHeight(int height) const { return((height * mTerxelSize[2]) + mBounds[0][2]); }
|
||||
const bool GetHasPhysics(void) const { return(mHasPhysics); }
|
||||
const bool GetIsRandom(void) const { return(mRandomTerrain != 0); }
|
||||
const int GetSurfaceFlags(int height) const { return(mHeightDetails[height].GetSurfaceFlags()); }
|
||||
const int GetContentFlags(int height) const { return(mHeightDetails[height].GetContents()); }
|
||||
void CalcRealCoords(void);
|
||||
vec3_t *GetCoords(void) const { return(mCoords); }
|
||||
|
||||
int GetBaseWaterHeight(void) const { return(mBaseWaterHeight); }
|
||||
void SetRealWaterHeight(int height) { mWaterHeight = height * mTerxelSize[2]; }
|
||||
float GetWaterHeight(void) const { return(mWaterHeight); }
|
||||
int GetWaterContents(void) const { return(mWaterContents); }
|
||||
int GetWaterSurfaceFlags(void) const { return(mWaterSurfaceFlags); }
|
||||
|
||||
CRandomTerrain *GetRandomTerrain(void) { return mRandomTerrain; }
|
||||
|
||||
void rand_seed(int seed);
|
||||
unsigned long get_rand_seed(void) { return holdrand; }
|
||||
|
||||
float flrand(float min, float max);
|
||||
int irand(int min, int max);
|
||||
};
|
||||
|
||||
void CM_TerrainPatchIterate(const class CCMLandScape *ls, void (*IterateFunc)( CCMPatch *, void * ), void *userdata);
|
||||
class CCMLandScape *CM_InitTerrain(const char *configstring, thandle_t terrainId, bool server);
|
||||
float CM_GetWorldHeight(const CCMLandScape *landscape, vec3_t origin, const vec3pair_t bounds, bool aboveGround);
|
||||
void CM_FlattenArea(CCMLandScape *landscape, CArea *area, int height, bool save, bool forceHeight, bool smooth);
|
||||
void CM_CarveBezierCurve (CCMLandScape *landscape, int numCtls, vec3_t* ctls, int steps, int depth, int size );
|
||||
void CM_SaveArea(CCMLandScape *landscape, CArea *area);
|
||||
float CM_FractionBelowLevel(CCMLandScape *landscape, CArea *area, int height);
|
||||
bool CM_AreaCollision(class CCMLandScape *landscape, class CArea *area, int *areaTypes, int areaTypeCount);
|
||||
CArea *CM_GetFirstArea(CCMLandScape *landscape);
|
||||
CArea *CM_GetFirstObjectiveArea(CCMLandScape *landscape);
|
||||
CArea *CM_GetPlayerArea(class CCMLandScape *common);
|
||||
CArea *CM_GetNextArea(CCMLandScape *landscape);
|
||||
CArea *CM_GetNextObjectiveArea(CCMLandScape *landscape);
|
||||
void CM_CircularIterate(byte *data, int width, int height, int xo, int yo, int insideRadius, int outsideRadius, int *user, void (*callback)(byte *, float, int *));
|
||||
|
||||
CRandomTerrain *CreateRandomTerrain(const char *config, CCMLandScape *landscape, byte *heightmap, int width, int height);
|
||||
|
||||
void SV_LoadMissionDef(const char *configstring, class CCMLandScape *landscape);
|
||||
void CL_CreateRandomTerrain(const char *config, class CCMLandScape *landscape, byte *image, int width, int height);
|
||||
void CL_LoadInstanceDef(const char *configstring, class CCMLandScape *landscape);
|
||||
void CL_LoadMissionDef(const char *configstring, class CCMLandScape *landscape);
|
||||
|
||||
extern cvar_t *com_terrainPhysics;
|
||||
|
||||
#endif
|
||||
|
||||
// end
|
||||
1298
code/qcommon/cm_load.cpp
Normal file
1298
code/qcommon/cm_load.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1280
code/qcommon/cm_load_xbox.cpp
Normal file
1280
code/qcommon/cm_load_xbox.cpp
Normal file
File diff suppressed because it is too large
Load Diff
321
code/qcommon/cm_local.h
Normal file
321
code/qcommon/cm_local.h
Normal file
@@ -0,0 +1,321 @@
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
#include "cm_polylib.h"
|
||||
#include "cm_landscape.h"
|
||||
|
||||
#ifdef _XBOX
|
||||
#include "sparc.h"
|
||||
#endif
|
||||
|
||||
#ifndef CM_LOCAL_H
|
||||
#define CM_LOCAL_H
|
||||
|
||||
#define BOX_MODEL_HANDLE (MAX_SUBMODELS-1)
|
||||
|
||||
#ifdef _XBOX
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
short children[2]; // negative numbers are leafs
|
||||
} cNode_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#else // _XBOX
|
||||
|
||||
typedef struct {
|
||||
cplane_t *plane;
|
||||
int children[2]; // negative numbers are leafs
|
||||
} cNode_t;
|
||||
|
||||
#endif // _XBOX
|
||||
|
||||
typedef struct {
|
||||
int cluster;
|
||||
int area;
|
||||
|
||||
int firstLeafBrush;
|
||||
int numLeafBrushes;
|
||||
|
||||
int firstLeafSurface;
|
||||
int numLeafSurfaces;
|
||||
} cLeaf_t;
|
||||
|
||||
typedef struct cmodel_s {
|
||||
vec3_t mins, maxs;
|
||||
cLeaf_t leaf; // submodels don't reference the main tree
|
||||
} cmodel_t;
|
||||
|
||||
#ifdef _XBOX
|
||||
#pragma pack (push, 1)
|
||||
typedef struct cbrushside_s {
|
||||
NotSoShort planeNum;
|
||||
unsigned char shaderNum;
|
||||
} cbrushside_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#else // _XBOX
|
||||
|
||||
typedef struct cbrushside_s {
|
||||
cplane_t *plane;
|
||||
int shaderNum;
|
||||
} cbrushside_t;
|
||||
|
||||
#endif // _XBOX
|
||||
|
||||
typedef struct cbrush_s {
|
||||
int shaderNum; // the shader that determined the contents
|
||||
int contents;
|
||||
vec3_t bounds[2];
|
||||
cbrushside_t *sides;
|
||||
unsigned short numsides;
|
||||
unsigned short checkcount; // to avoid repeated testings
|
||||
} cbrush_t;
|
||||
|
||||
class CCMShader
|
||||
{
|
||||
public:
|
||||
char shader[MAX_QPATH];
|
||||
class CCMShader *mNext;
|
||||
int surfaceFlags;
|
||||
int contentFlags;
|
||||
|
||||
const char *GetName(void) const { return(shader); }
|
||||
class CCMShader *GetNext(void) const { return(mNext); }
|
||||
void SetNext(class CCMShader *next) { mNext = next; }
|
||||
void Destroy(void) { }
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int checkcount; // to avoid repeated testings
|
||||
int surfaceFlags;
|
||||
int contents;
|
||||
struct patchCollide_s *pc;
|
||||
} cPatch_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int floodnum;
|
||||
int floodvalid;
|
||||
} cArea_t;
|
||||
|
||||
#ifdef _XBOX
|
||||
template <class T>
|
||||
class SPARC;
|
||||
typedef struct {
|
||||
char name[MAX_QPATH];
|
||||
|
||||
int numShaders;
|
||||
CCMShader *shaders;
|
||||
|
||||
int numBrushSides;
|
||||
cbrushside_t *brushsides;
|
||||
|
||||
int numPlanes;
|
||||
cplane_t *planes;
|
||||
|
||||
int numNodes;
|
||||
cNode_t *nodes;
|
||||
|
||||
int numLeafs;
|
||||
cLeaf_t *leafs;
|
||||
|
||||
int numLeafBrushes;
|
||||
int *leafbrushes;
|
||||
|
||||
int numLeafSurfaces;
|
||||
int *leafsurfaces;
|
||||
|
||||
int numSubModels;
|
||||
cmodel_t *cmodels;
|
||||
|
||||
int numBrushes;
|
||||
cbrush_t *brushes;
|
||||
|
||||
int numClusters;
|
||||
int clusterBytes;
|
||||
SPARC<byte> *visibility;
|
||||
qboolean vised; // if false, visibility is just a single cluster of ffs
|
||||
|
||||
int numEntityChars;
|
||||
char *entityString;
|
||||
|
||||
int numAreas;
|
||||
cArea_t *areas;
|
||||
int *areaPortals; // [ numAreas*numAreas ] reference counts
|
||||
|
||||
int numSurfaces;
|
||||
cPatch_t **surfaces; // non-patches will be NULL
|
||||
|
||||
int floodvalid;
|
||||
int checkcount; // incremented on each trace
|
||||
|
||||
// CCMLandScape *landScape; // Removing terrain from Xbox
|
||||
} clipMap_t;
|
||||
|
||||
#else // _XBOX
|
||||
|
||||
typedef struct {
|
||||
char name[MAX_QPATH];
|
||||
|
||||
int numShaders;
|
||||
//dshader_t *shaders;
|
||||
CCMShader *shaders;
|
||||
|
||||
int numBrushSides;
|
||||
cbrushside_t *brushsides;
|
||||
|
||||
int numPlanes;
|
||||
cplane_t *planes;
|
||||
|
||||
int numNodes;
|
||||
cNode_t *nodes;
|
||||
|
||||
int numLeafs;
|
||||
cLeaf_t *leafs;
|
||||
|
||||
int numLeafBrushes;
|
||||
int *leafbrushes;
|
||||
|
||||
int numLeafSurfaces;
|
||||
int *leafsurfaces;
|
||||
|
||||
int numSubModels;
|
||||
cmodel_t *cmodels;
|
||||
|
||||
int numBrushes;
|
||||
cbrush_t *brushes;
|
||||
|
||||
int numClusters;
|
||||
int clusterBytes;
|
||||
byte *visibility;
|
||||
qboolean vised; // if false, visibility is just a single cluster of ffs
|
||||
|
||||
int numEntityChars;
|
||||
char *entityString;
|
||||
|
||||
int numAreas;
|
||||
cArea_t *areas;
|
||||
int *areaPortals; // [ numAreas*numAreas ] reference counts
|
||||
|
||||
int numSurfaces;
|
||||
cPatch_t **surfaces; // non-patches will be NULL
|
||||
|
||||
int floodvalid;
|
||||
int checkcount; // incremented on each trace
|
||||
|
||||
CCMLandScape *landScape;
|
||||
} clipMap_t;
|
||||
|
||||
#endif // _XBOX
|
||||
|
||||
|
||||
// keep 1/8 unit away to keep the position valid before network snapping
|
||||
// and to avoid various numeric issues
|
||||
#define SURFACE_CLIP_EPSILON (0.125)
|
||||
|
||||
extern clipMap_t cmg;
|
||||
extern int c_pointcontents;
|
||||
extern int c_traces, c_brush_traces, c_patch_traces;
|
||||
extern cvar_t *cm_noAreas;
|
||||
extern cvar_t *cm_noCurves;
|
||||
extern cvar_t *cm_playerCurveClip;
|
||||
|
||||
extern clipMap_t SubBSP[MAX_SUB_BSP];
|
||||
extern int NumSubBSP;
|
||||
|
||||
// cm_test.c
|
||||
|
||||
// Used for oriented capsule collision detection
|
||||
typedef struct
|
||||
{
|
||||
qboolean use;
|
||||
float radius;
|
||||
float halfheight;
|
||||
vec3_t offset;
|
||||
} sphere_t;
|
||||
|
||||
typedef struct traceWork_s {
|
||||
vec3_t start;
|
||||
vec3_t end;
|
||||
vec3_t size[2]; // size of the box being swept through the model
|
||||
vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x]
|
||||
float maxOffset; // longest corner length from origin
|
||||
vec3_t extents; // greatest of abs(size[0]) and abs(size[1])
|
||||
|
||||
vec3_t bounds[2]; // enclosing box of start and end surrounding by size
|
||||
vec3pair_t localBounds; // enclosing box of start and end surrounding by size for a segment
|
||||
|
||||
vec3_t modelOrigin;// origin of the model tracing through
|
||||
int contents; // ored contents of the model tracing through
|
||||
qboolean isPoint; // optimized case
|
||||
sphere_t sphere; // sphere for oriendted capsule collision
|
||||
|
||||
float baseEnterFrac; // global enter fraction (before processing subsections of the brush)
|
||||
float baseLeaveFrac; // global leave fraction (before processing subsections of the brush)
|
||||
float enterFrac; // fraction where the ray enters the brush
|
||||
float leaveFrac; // fraction where the ray leaves the brush
|
||||
cbrushside_t *leadside;
|
||||
cplane_t *clipplane;
|
||||
bool startout;
|
||||
bool getout;
|
||||
|
||||
trace_t trace; // returned from trace call
|
||||
// make sure nothing goes under here for Ghoul2 collision purposes
|
||||
} traceWork_t;
|
||||
|
||||
typedef struct leafList_s {
|
||||
int count;
|
||||
int maxcount;
|
||||
qboolean overflowed;
|
||||
int *list;
|
||||
vec3_t bounds[2];
|
||||
int lastLeaf; // for overflows where each leaf can't be stored individually
|
||||
void (*storeLeafs)( struct leafList_s *ll, int nodenum );
|
||||
} leafList_t;
|
||||
|
||||
|
||||
int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxlist, int listsize );
|
||||
|
||||
void CM_StoreLeafs( leafList_t *ll, int nodenum );
|
||||
void CM_StoreBrushes( leafList_t *ll, int nodenum );
|
||||
|
||||
void CM_BoxLeafnums_r( leafList_t *ll, int nodenum );
|
||||
|
||||
cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap = 0 );
|
||||
void CM_CleanLeafCache(void);
|
||||
|
||||
// cm_load.c
|
||||
void CM_ModelBounds( clipMap_t &cm, clipHandle_t model, vec3_t mins, vec3_t maxs );
|
||||
|
||||
// cm_patch.c
|
||||
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
|
||||
void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
void CM_ClearLevelPatches( void );
|
||||
|
||||
// cm_shader.cpp
|
||||
void CM_SetupShaderProperties( void );
|
||||
void CM_ShutdownShaderProperties(void);
|
||||
CCMShader *CM_GetShaderInfo( const char *name );
|
||||
CCMShader *CM_GetShaderInfo( int shaderNum );
|
||||
void CM_GetModelFormalName ( const char* model, const char* skin, char* name, int size );
|
||||
|
||||
//cm_trace.cpp
|
||||
void CM_CalcExtents(const vec3_t start, const vec3_t end, const traceWork_t *tw, vec3pair_t bounds);
|
||||
void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount);
|
||||
bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds);
|
||||
|
||||
|
||||
//RM_Terrain.cpp
|
||||
int Round(float value);
|
||||
|
||||
//random utils for cm_terrain (and others?)
|
||||
#define VectorInc(v) ((v)[0] += 1.0f,(v)[1] += 1.0f,(v)[2] +=1.0f)
|
||||
#define VectorDec(v) ((v)[0] -= 1.0f,(v)[1] -= 1.0f,(v)[2] -=1.0f)
|
||||
#define VectorInverseScaleVector(a,b,c) ((c)[0]=(a)[0]/(b)[0],(c)[1]=(a)[1]/(b)[1],(c)[2]=(a)[2]/(b)[2])
|
||||
#define VectorScaleVectorAdd(c,a,b,o) ((o)[0]=(c)[0]+((a)[0]*(b)[0]),(o)[1]=(c)[1]+((a)[1]*(b)[1]),(o)[2]=(c)[2]+((a)[2]*(b)[2]))
|
||||
|
||||
#define minimum(x,y) ((x)<(y)?(x):(y))
|
||||
#define maximum(x,y) ((x)>(y)?(x):(y))
|
||||
|
||||
#endif
|
||||
2930
code/qcommon/cm_patch.cpp
Normal file
2930
code/qcommon/cm_patch.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
code/qcommon/cm_patch.h
Normal file
121
code/qcommon/cm_patch.h
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
//#define CULL_BBOX
|
||||
|
||||
/*
|
||||
|
||||
This file does not reference any globals, and has these entry points:
|
||||
|
||||
void CM_ClearLevelPatches( void );
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points );
|
||||
void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) );
|
||||
|
||||
|
||||
Issues for collision against curved surfaces:
|
||||
|
||||
Surface edges need to be handled differently than surface planes
|
||||
|
||||
Plane expansion causes raw surfaces to expand past expanded bounding box
|
||||
|
||||
Position test of a volume against a surface is tricky.
|
||||
|
||||
Position test of a point against a surface is not well defined, because the surface has no volume.
|
||||
|
||||
|
||||
Tracing leading edge points instead of volumes?
|
||||
Position test by tracing corner to corner? (8*7 traces -- ouch)
|
||||
|
||||
coplanar edges
|
||||
triangulated patches
|
||||
degenerate patches
|
||||
|
||||
endcaps
|
||||
degenerate
|
||||
|
||||
WARNING: this may misbehave with meshes that have rows or columns that only
|
||||
degenerate a few triangles. Completely degenerate rows and columns are handled
|
||||
properly.
|
||||
*/
|
||||
|
||||
|
||||
#define MAX_FACETS 1024
|
||||
#define MAX_PATCH_PLANES 2048
|
||||
|
||||
typedef struct {
|
||||
float plane[4];
|
||||
int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
|
||||
} patchPlane_t;
|
||||
|
||||
#ifdef _XBOX
|
||||
//Facets are now two structures - a maximum sized version that's used
|
||||
//temporarily during load time, and smaller version that only allocates
|
||||
//as much memory as needed. The load version is copied into the small
|
||||
//version after it's been assembled.
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
int surfacePlane;
|
||||
int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
|
||||
short borderPlanes[4+6+16];
|
||||
unsigned char borderInward[4+6+16];
|
||||
unsigned char borderNoAdjust[4+6+16];
|
||||
} facetLoad_t;
|
||||
|
||||
typedef struct {
|
||||
int surfacePlane;
|
||||
int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
|
||||
char *data;
|
||||
|
||||
short *GetBorderPlanes(void) { return (short*)data; }
|
||||
char *GetBorderInward(void) { return data + (numBorders * 2); }
|
||||
char *GetBorderNoAdjust(void)
|
||||
{ return data + (numBorders * 2) + numBorders; }
|
||||
|
||||
const short *GetBorderPlanes(void) const { return (short*)data; }
|
||||
const char *GetBorderInward(void) const { return data + (numBorders * 2); }
|
||||
const char *GetBorderNoAdjust(void) const
|
||||
{ return data + (numBorders * 2) + numBorders; }
|
||||
} facet_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#else // _XBOX
|
||||
|
||||
typedef struct {
|
||||
int surfacePlane;
|
||||
int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
|
||||
int borderPlanes[4+6+16];
|
||||
int borderInward[4+6+16];
|
||||
qboolean borderNoAdjust[4+6+16];
|
||||
} facet_t;
|
||||
|
||||
#endif // _XBOX
|
||||
|
||||
typedef struct patchCollide_s {
|
||||
vec3_t bounds[2];
|
||||
int numPlanes; // surface planes plus edge planes
|
||||
patchPlane_t *planes;
|
||||
int numFacets;
|
||||
facet_t *facets;
|
||||
} patchCollide_t;
|
||||
|
||||
|
||||
#define CM_MAX_GRID_SIZE 129
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
qboolean wrapWidth;
|
||||
qboolean wrapHeight;
|
||||
vec3_t points[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE]; // [width][height]
|
||||
} cGrid_t;
|
||||
|
||||
#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve
|
||||
#define PLANE_TRI_EPSILON 0.1
|
||||
#define WRAP_POINT_EPSILON 0.1
|
||||
|
||||
#ifdef _XBOX
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points,
|
||||
facetLoad_t *facetbuf, int *gridbuf );
|
||||
#else
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
|
||||
#endif // _XBOX
|
||||
711
code/qcommon/cm_polylib.cpp
Normal file
711
code/qcommon/cm_polylib.cpp
Normal file
@@ -0,0 +1,711 @@
|
||||
|
||||
// this is only used for visualization tools in cm_ debug functions
|
||||
|
||||
|
||||
#include "cm_local.h"
|
||||
|
||||
|
||||
// counters are only bumped when running single threaded,
|
||||
// because they are an awefull coherence problem
|
||||
int c_active_windings;
|
||||
int c_peak_windings;
|
||||
int c_winding_allocs;
|
||||
int c_winding_points;
|
||||
|
||||
void pw(winding_t *w)
|
||||
{
|
||||
int i;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
AllocWinding
|
||||
=============
|
||||
*/
|
||||
winding_t *AllocWinding (int points)
|
||||
{
|
||||
winding_t *w;
|
||||
int s;
|
||||
|
||||
c_winding_allocs++;
|
||||
c_winding_points += points;
|
||||
c_active_windings++;
|
||||
if (c_active_windings > c_peak_windings)
|
||||
c_peak_windings = c_active_windings;
|
||||
|
||||
s = sizeof(vec_t)*3*points + sizeof(int);
|
||||
w = (winding_t *) Z_Malloc (s,TAG_BSP, qtrue);//TAG_WINDING);
|
||||
// memset (w, 0, s); // qtrue above does this
|
||||
return w;
|
||||
}
|
||||
|
||||
void FreeWinding (winding_t *w)
|
||||
{
|
||||
if (*(unsigned *)w == 0xdeaddead)
|
||||
Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding");
|
||||
*(unsigned *)w = 0xdeaddead;
|
||||
|
||||
c_active_windings--;
|
||||
Z_Free (w);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
RemoveColinearPoints
|
||||
============
|
||||
*/
|
||||
int c_removed;
|
||||
|
||||
void RemoveColinearPoints (winding_t *w)
|
||||
{
|
||||
int i, j, k;
|
||||
vec3_t v1, v2;
|
||||
int nump;
|
||||
vec3_t p[MAX_POINTS_ON_WINDING];
|
||||
|
||||
nump = 0;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
j = (i+1)%w->numpoints;
|
||||
k = (i+w->numpoints-1)%w->numpoints;
|
||||
VectorSubtract (w->p[j], w->p[i], v1);
|
||||
VectorSubtract (w->p[i], w->p[k], v2);
|
||||
VectorNormalize2(v1,v1);
|
||||
VectorNormalize2(v2,v2);
|
||||
if (DotProduct(v1, v2) < 0.999)
|
||||
{
|
||||
VectorCopy (w->p[i], p[nump]);
|
||||
nump++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nump == w->numpoints)
|
||||
return;
|
||||
|
||||
c_removed += w->numpoints - nump;
|
||||
w->numpoints = nump;
|
||||
memcpy (w->p, p, nump*sizeof(p[0]));
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
WindingPlane
|
||||
============
|
||||
*/
|
||||
void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist)
|
||||
{
|
||||
vec3_t v1, v2;
|
||||
|
||||
VectorSubtract (w->p[1], w->p[0], v1);
|
||||
VectorSubtract (w->p[2], w->p[0], v2);
|
||||
CrossProduct (v2, v1, normal);
|
||||
VectorNormalize2(normal, normal);
|
||||
*dist = DotProduct (w->p[0], normal);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
WindingArea
|
||||
=============
|
||||
*/
|
||||
vec_t WindingArea (winding_t *w)
|
||||
{
|
||||
int i;
|
||||
vec3_t d1, d2, cross;
|
||||
vec_t total;
|
||||
|
||||
total = 0;
|
||||
for (i=2 ; i<w->numpoints ; i++)
|
||||
{
|
||||
VectorSubtract (w->p[i-1], w->p[0], d1);
|
||||
VectorSubtract (w->p[i], w->p[0], d2);
|
||||
CrossProduct (d1, d2, cross);
|
||||
total += 0.5 * VectorLength ( cross );
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs)
|
||||
{
|
||||
vec_t v;
|
||||
int i,j;
|
||||
|
||||
mins[0] = mins[1] = mins[2] = WORLD_SIZE; // 99999; // WORLD_SIZE instead of MAX_WORLD_COORD so that...
|
||||
maxs[0] = maxs[1] = maxs[2] = -WORLD_SIZE; //-99999; // ... it's guaranteed to be outide of legal
|
||||
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{
|
||||
v = w->p[i][j];
|
||||
if (v < mins[j])
|
||||
mins[j] = v;
|
||||
if (v > maxs[j])
|
||||
maxs[j] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
WindingCenter
|
||||
=============
|
||||
*/
|
||||
void WindingCenter (winding_t *w, vec3_t center)
|
||||
{
|
||||
int i;
|
||||
float scale;
|
||||
|
||||
VectorCopy (vec3_origin, center);
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
VectorAdd (w->p[i], center, center);
|
||||
|
||||
scale = 1.0/w->numpoints;
|
||||
VectorScale (center, scale, center);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
BaseWindingForPlane
|
||||
=================
|
||||
*/
|
||||
winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist)
|
||||
{
|
||||
int i, x;
|
||||
vec_t max, v;
|
||||
vec3_t org, vright, vup;
|
||||
winding_t *w;
|
||||
|
||||
// find the major axis
|
||||
|
||||
max = -MAX_MAP_BOUNDS;
|
||||
x = -1;
|
||||
for (i=0 ; i<3; i++)
|
||||
{
|
||||
v = fabs(normal[i]);
|
||||
if (v > max)
|
||||
{
|
||||
x = i;
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
if (x==-1)
|
||||
Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found");
|
||||
|
||||
VectorCopy (vec3_origin, vup);
|
||||
switch (x)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
vup[2] = 1;
|
||||
break;
|
||||
case 2:
|
||||
vup[0] = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
v = DotProduct (vup, normal);
|
||||
VectorMA (vup, -v, normal, vup);
|
||||
VectorNormalize2(vup, vup);
|
||||
|
||||
VectorScale (normal, dist, org);
|
||||
|
||||
CrossProduct (vup, normal, vright);
|
||||
|
||||
VectorScale (vup, MAX_MAP_BOUNDS, vup);
|
||||
VectorScale (vright, MAX_MAP_BOUNDS, vright);
|
||||
|
||||
// project a really big axis aligned box onto the plane
|
||||
w = AllocWinding (4);
|
||||
|
||||
VectorSubtract (org, vright, w->p[0]);
|
||||
VectorAdd (w->p[0], vup, w->p[0]);
|
||||
|
||||
VectorAdd (org, vright, w->p[1]);
|
||||
VectorAdd (w->p[1], vup, w->p[1]);
|
||||
|
||||
VectorAdd (org, vright, w->p[2]);
|
||||
VectorSubtract (w->p[2], vup, w->p[2]);
|
||||
|
||||
VectorSubtract (org, vright, w->p[3]);
|
||||
VectorSubtract (w->p[3], vup, w->p[3]);
|
||||
|
||||
w->numpoints = 4;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CopyWinding
|
||||
==================
|
||||
*/
|
||||
winding_t *CopyWinding (winding_t *w)
|
||||
{
|
||||
int size;
|
||||
winding_t *c;
|
||||
|
||||
c = AllocWinding (w->numpoints);
|
||||
size = (int)((winding_t *)0)->p[w->numpoints];
|
||||
memcpy (c, w, size);
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
ReverseWinding
|
||||
==================
|
||||
*/
|
||||
winding_t *ReverseWinding (winding_t *w)
|
||||
{
|
||||
int i;
|
||||
winding_t *c;
|
||||
|
||||
c = AllocWinding (w->numpoints);
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
VectorCopy (w->p[w->numpoints-1-i], c->p[i]);
|
||||
}
|
||||
c->numpoints = w->numpoints;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
ClipWindingEpsilon
|
||||
=============
|
||||
*/
|
||||
void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
|
||||
vec_t epsilon, winding_t **front, winding_t **back)
|
||||
{
|
||||
vec_t dists[MAX_POINTS_ON_WINDING+4];
|
||||
int sides[MAX_POINTS_ON_WINDING+4];
|
||||
int counts[3];
|
||||
static vec_t dot; // VC 4.2 optimizer bug if not static
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec3_t mid;
|
||||
winding_t *f, *b;
|
||||
int maxpts;
|
||||
|
||||
counts[0] = counts[1] = counts[2] = 0;
|
||||
|
||||
// determine sides for each point
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
dot = DotProduct (in->p[i], normal);
|
||||
dot -= dist;
|
||||
dists[i] = dot;
|
||||
if (dot > epsilon)
|
||||
sides[i] = SIDE_FRONT;
|
||||
else if (dot < -epsilon)
|
||||
sides[i] = SIDE_BACK;
|
||||
else
|
||||
{
|
||||
sides[i] = SIDE_ON;
|
||||
}
|
||||
counts[sides[i]]++;
|
||||
}
|
||||
sides[i] = sides[0];
|
||||
dists[i] = dists[0];
|
||||
|
||||
*front = *back = NULL;
|
||||
|
||||
if (!counts[0])
|
||||
{
|
||||
*back = CopyWinding (in);
|
||||
return;
|
||||
}
|
||||
if (!counts[1])
|
||||
{
|
||||
*front = CopyWinding (in);
|
||||
return;
|
||||
}
|
||||
|
||||
maxpts = in->numpoints+4; // cant use counts[0]+2 because
|
||||
// of fp grouping errors
|
||||
|
||||
*front = f = AllocWinding (maxpts);
|
||||
*back = b = AllocWinding (maxpts);
|
||||
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
p1 = in->p[i];
|
||||
|
||||
if (sides[i] == SIDE_ON)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
VectorCopy (p1, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sides[i] == SIDE_FRONT)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
if (sides[i] == SIDE_BACK)
|
||||
{
|
||||
VectorCopy (p1, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
}
|
||||
|
||||
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
|
||||
continue;
|
||||
|
||||
// generate a split point
|
||||
p2 = in->p[(i+1)%in->numpoints];
|
||||
|
||||
dot = dists[i] / (dists[i]-dists[i+1]);
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{ // avoid round off error when possible
|
||||
if (normal[j] == 1)
|
||||
mid[j] = dist;
|
||||
else if (normal[j] == -1)
|
||||
mid[j] = -dist;
|
||||
else
|
||||
mid[j] = p1[j] + dot*(p2[j]-p1[j]);
|
||||
}
|
||||
|
||||
VectorCopy (mid, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
VectorCopy (mid, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
}
|
||||
|
||||
if (f->numpoints > maxpts || b->numpoints > maxpts)
|
||||
Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
|
||||
if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING)
|
||||
Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
ChopWindingInPlace
|
||||
=============
|
||||
*/
|
||||
void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon)
|
||||
{
|
||||
winding_t *in;
|
||||
vec_t dists[MAX_POINTS_ON_WINDING+4];
|
||||
int sides[MAX_POINTS_ON_WINDING+4];
|
||||
int counts[3];
|
||||
static vec_t dot; // VC 4.2 optimizer bug if not static
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec3_t mid;
|
||||
winding_t *f;
|
||||
int maxpts;
|
||||
|
||||
in = *inout;
|
||||
counts[0] = counts[1] = counts[2] = 0;
|
||||
|
||||
// determine sides for each point
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
dot = DotProduct (in->p[i], normal);
|
||||
dot -= dist;
|
||||
dists[i] = dot;
|
||||
if (dot > epsilon)
|
||||
sides[i] = SIDE_FRONT;
|
||||
else if (dot < -epsilon)
|
||||
sides[i] = SIDE_BACK;
|
||||
else
|
||||
{
|
||||
sides[i] = SIDE_ON;
|
||||
}
|
||||
counts[sides[i]]++;
|
||||
}
|
||||
sides[i] = sides[0];
|
||||
dists[i] = dists[0];
|
||||
|
||||
if (!counts[0])
|
||||
{
|
||||
FreeWinding (in);
|
||||
*inout = NULL;
|
||||
return;
|
||||
}
|
||||
if (!counts[1])
|
||||
return; // inout stays the same
|
||||
|
||||
maxpts = in->numpoints+4; // cant use counts[0]+2 because
|
||||
// of fp grouping errors
|
||||
|
||||
f = AllocWinding (maxpts);
|
||||
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
p1 = in->p[i];
|
||||
|
||||
if (sides[i] == SIDE_ON)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sides[i] == SIDE_FRONT)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
|
||||
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
|
||||
continue;
|
||||
|
||||
// generate a split point
|
||||
p2 = in->p[(i+1)%in->numpoints];
|
||||
|
||||
dot = dists[i] / (dists[i]-dists[i+1]);
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{ // avoid round off error when possible
|
||||
if (normal[j] == 1)
|
||||
mid[j] = dist;
|
||||
else if (normal[j] == -1)
|
||||
mid[j] = -dist;
|
||||
else
|
||||
mid[j] = p1[j] + dot*(p2[j]-p1[j]);
|
||||
}
|
||||
|
||||
VectorCopy (mid, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
|
||||
if (f->numpoints > maxpts)
|
||||
Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
|
||||
if (f->numpoints > MAX_POINTS_ON_WINDING)
|
||||
Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
|
||||
|
||||
FreeWinding (in);
|
||||
*inout = f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
ChopWinding
|
||||
|
||||
Returns the fragment of in that is on the front side
|
||||
of the cliping plane. The original is freed.
|
||||
=================
|
||||
*/
|
||||
winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist)
|
||||
{
|
||||
winding_t *f, *b;
|
||||
|
||||
ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b);
|
||||
FreeWinding (in);
|
||||
if (b)
|
||||
FreeWinding (b);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CheckWinding
|
||||
|
||||
=================
|
||||
*/
|
||||
void CheckWinding (winding_t *w)
|
||||
{
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec_t d, edgedist;
|
||||
vec3_t dir, edgenormal, facenormal;
|
||||
vec_t area;
|
||||
vec_t facedist;
|
||||
|
||||
if (w->numpoints < 3)
|
||||
Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints);
|
||||
|
||||
area = WindingArea(w);
|
||||
if (area < 1)
|
||||
Com_Error (ERR_DROP, "CheckWinding: %f area", area);
|
||||
|
||||
WindingPlane (w, facenormal, &facedist);
|
||||
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
p1 = w->p[i];
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS)
|
||||
Com_Error (ERR_DROP, "CheckFace: BOGUS_RANGE: %f",p1[j]);
|
||||
|
||||
j = i+1 == w->numpoints ? 0 : i+1;
|
||||
|
||||
// check the point is on the face plane
|
||||
d = DotProduct (p1, facenormal) - facedist;
|
||||
if (d < -ON_EPSILON || d > ON_EPSILON)
|
||||
Com_Error (ERR_DROP, "CheckWinding: point off plane");
|
||||
|
||||
// check the edge isnt degenerate
|
||||
p2 = w->p[j];
|
||||
VectorSubtract (p2, p1, dir);
|
||||
|
||||
if (VectorLength (dir) < ON_EPSILON)
|
||||
Com_Error (ERR_DROP, "CheckWinding: degenerate edge");
|
||||
|
||||
CrossProduct (facenormal, dir, edgenormal);
|
||||
VectorNormalize2 (edgenormal, edgenormal);
|
||||
edgedist = DotProduct (p1, edgenormal);
|
||||
edgedist += ON_EPSILON;
|
||||
|
||||
// all other points must be on front side
|
||||
for (j=0 ; j<w->numpoints ; j++)
|
||||
{
|
||||
if (j == i)
|
||||
continue;
|
||||
d = DotProduct (w->p[j], edgenormal);
|
||||
if (d > edgedist)
|
||||
Com_Error (ERR_DROP, "CheckWinding: non-convex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
WindingOnPlaneSide
|
||||
============
|
||||
*/
|
||||
int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist)
|
||||
{
|
||||
qboolean front, back;
|
||||
int i;
|
||||
vec_t d;
|
||||
|
||||
front = qfalse;
|
||||
back = qfalse;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
d = DotProduct (w->p[i], normal) - dist;
|
||||
if (d < -ON_EPSILON)
|
||||
{
|
||||
if (front)
|
||||
return SIDE_CROSS;
|
||||
back = qtrue;
|
||||
continue;
|
||||
}
|
||||
if (d > ON_EPSILON)
|
||||
{
|
||||
if (back)
|
||||
return SIDE_CROSS;
|
||||
front = qtrue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (back)
|
||||
return SIDE_BACK;
|
||||
if (front)
|
||||
return SIDE_FRONT;
|
||||
return SIDE_ON;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
AddWindingToConvexHull
|
||||
|
||||
Both w and *hull are on the same plane
|
||||
=================
|
||||
*/
|
||||
#define MAX_HULL_POINTS 128
|
||||
void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) {
|
||||
int i, j, k;
|
||||
float *p, *copy;
|
||||
vec3_t dir;
|
||||
float d;
|
||||
int numHullPoints, numNew;
|
||||
vec3_t hullPoints[MAX_HULL_POINTS];
|
||||
vec3_t newHullPoints[MAX_HULL_POINTS];
|
||||
vec3_t hullDirs[MAX_HULL_POINTS];
|
||||
qboolean hullSide[MAX_HULL_POINTS];
|
||||
qboolean outside;
|
||||
|
||||
if ( !*hull ) {
|
||||
*hull = CopyWinding( w );
|
||||
return;
|
||||
}
|
||||
|
||||
numHullPoints = (*hull)->numpoints;
|
||||
memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) );
|
||||
|
||||
for ( i = 0 ; i < w->numpoints ; i++ ) {
|
||||
p = w->p[i];
|
||||
|
||||
// calculate hull side vectors
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
k = ( j + 1 ) % numHullPoints;
|
||||
|
||||
VectorSubtract( hullPoints[k], hullPoints[j], dir );
|
||||
VectorNormalize2( dir, dir );
|
||||
CrossProduct( normal, dir, hullDirs[j] );
|
||||
}
|
||||
|
||||
outside = qfalse;
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
VectorSubtract( p, hullPoints[j], dir );
|
||||
d = DotProduct( dir, hullDirs[j] );
|
||||
if ( d >= ON_EPSILON ) {
|
||||
outside = qtrue;
|
||||
}
|
||||
if ( d >= -ON_EPSILON ) {
|
||||
hullSide[j] = qtrue;
|
||||
} else {
|
||||
hullSide[j] = qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
// if the point is effectively inside, do nothing
|
||||
if ( !outside ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the back side to front side transition
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( j == numHullPoints ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// insert the point here
|
||||
VectorCopy( p, newHullPoints[0] );
|
||||
numNew = 1;
|
||||
|
||||
// copy over all points that aren't double fronts
|
||||
j = (j+1)%numHullPoints;
|
||||
for ( k = 0 ; k < numHullPoints ; k++ ) {
|
||||
if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) {
|
||||
continue;
|
||||
}
|
||||
copy = hullPoints[ (j+k+1) % numHullPoints ];
|
||||
VectorCopy( copy, newHullPoints[numNew] );
|
||||
numNew++;
|
||||
}
|
||||
|
||||
numHullPoints = numNew;
|
||||
memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) );
|
||||
}
|
||||
|
||||
FreeWinding( *hull );
|
||||
w = AllocWinding( numHullPoints );
|
||||
w->numpoints = numHullPoints;
|
||||
*hull = w;
|
||||
memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) );
|
||||
}
|
||||
|
||||
|
||||
51
code/qcommon/cm_polylib.h
Normal file
51
code/qcommon/cm_polylib.h
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
// this is only used for visualization tools in cm_ debug functions
|
||||
|
||||
#ifndef CM_POLYLIB_H
|
||||
#define CM_POLYLIB_H
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int numpoints;
|
||||
vec3_t p[4]; // variable sized
|
||||
} winding_t;
|
||||
|
||||
#define MAX_POINTS_ON_WINDING 64
|
||||
|
||||
#define SIDE_FRONT 0
|
||||
#define SIDE_BACK 1
|
||||
#define SIDE_ON 2
|
||||
#define SIDE_CROSS 3
|
||||
|
||||
#define CLIP_EPSILON 0.1
|
||||
|
||||
#define MAX_MAP_BOUNDS 65535
|
||||
|
||||
// you can define on_epsilon in the makefile as tighter
|
||||
#ifndef ON_EPSILON
|
||||
#define ON_EPSILON 0.1f
|
||||
#endif
|
||||
|
||||
winding_t *AllocWinding (int points);
|
||||
vec_t WindingArea (winding_t *w);
|
||||
void WindingCenter (winding_t *w, vec3_t center);
|
||||
void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
|
||||
vec_t epsilon, winding_t **front, winding_t **back);
|
||||
winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist);
|
||||
winding_t *CopyWinding (winding_t *w);
|
||||
winding_t *ReverseWinding (winding_t *w);
|
||||
winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist);
|
||||
void CheckWinding (winding_t *w);
|
||||
void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist);
|
||||
void RemoveColinearPoints (winding_t *w);
|
||||
int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist);
|
||||
void FreeWinding (winding_t *w);
|
||||
void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs);
|
||||
|
||||
void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal );
|
||||
|
||||
void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon);
|
||||
// frees the original if clipped
|
||||
|
||||
void pw(winding_t *w);
|
||||
#endif
|
||||
72
code/qcommon/cm_public.h
Normal file
72
code/qcommon/cm_public.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef __CM_PUBLIC_H__
|
||||
#define __CM_PUBLIC_H__
|
||||
|
||||
#include "qfiles.h"
|
||||
|
||||
qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete);
|
||||
#ifdef _XBOX
|
||||
void CM_LoadMap( const char *name, qboolean clientload, int *checksum);
|
||||
#else
|
||||
void CM_LoadMap( const char *name, qboolean clientload, int *checksum, qboolean subBSP);
|
||||
#endif
|
||||
void CM_ClearMap( void );
|
||||
int CM_TotalMapContents();
|
||||
|
||||
clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels
|
||||
clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs );//, const int contents );
|
||||
|
||||
int CM_ModelContents( clipHandle_t model, int subBSPIndex );
|
||||
|
||||
|
||||
int CM_NumClusters (void);
|
||||
int CM_NumInlineModels( void );
|
||||
char *CM_EntityString (void);
|
||||
char *CM_SubBSPEntityString (int index);
|
||||
int CM_LoadSubBSP(const char *name, qboolean clientload);
|
||||
int CM_FindSubBSP(int modelIndex);
|
||||
|
||||
// returns an ORed contents mask
|
||||
int CM_PointContents( const vec3_t p, clipHandle_t model );
|
||||
int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles );
|
||||
|
||||
void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end,
|
||||
const vec3_t mins, const vec3_t maxs,
|
||||
clipHandle_t model, int brushmask);
|
||||
void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
|
||||
const vec3_t mins, const vec3_t maxs,
|
||||
clipHandle_t model, int brushmask,
|
||||
const vec3_t origin, const vec3_t angles);
|
||||
|
||||
#ifdef _XBOX
|
||||
const byte *CM_ClusterPVS (int cluster);
|
||||
#else
|
||||
byte *CM_ClusterPVS (int cluster);
|
||||
#endif
|
||||
|
||||
int CM_PointLeafnum( const vec3_t p );
|
||||
|
||||
// only returns non-solid leafs
|
||||
// overflow if return listsize and if *lastLeaf != list[listsize-1]
|
||||
int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList,
|
||||
int listsize, int *lastLeaf );
|
||||
|
||||
int CM_LeafCluster (int leafnum);
|
||||
int CM_LeafArea (int leafnum);
|
||||
|
||||
void CM_AdjustAreaPortalState( int area1, int area2, qboolean open );
|
||||
qboolean CM_AreasConnected( int area1, int area2 );
|
||||
|
||||
int CM_WriteAreaBits( byte *buffer, int area );
|
||||
|
||||
//for savegames
|
||||
void CM_WritePortalState ();
|
||||
void CM_ReadPortalState ();
|
||||
|
||||
// cm_marks.c
|
||||
int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
|
||||
int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
|
||||
|
||||
// cm_patch.c
|
||||
void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) );
|
||||
|
||||
#endif //__CM_PUBLIC_H__
|
||||
1086
code/qcommon/cm_randomterrain.cpp
Normal file
1086
code/qcommon/cm_randomterrain.cpp
Normal file
File diff suppressed because it is too large
Load Diff
89
code/qcommon/cm_randomterrain.h
Normal file
89
code/qcommon/cm_randomterrain.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#if !defined(CM_RANDOMTERRAIN_H_INC)
|
||||
#define CM_RANDOMTERRAIN_H_INC
|
||||
|
||||
#ifdef DEBUG_LINKING
|
||||
#pragma message("...including cm_randomterrain.h")
|
||||
#endif
|
||||
|
||||
//class CPathInfo;
|
||||
|
||||
#define SPLINE_MERGE_SIZE 3
|
||||
#define CIRCLE_STAMP_SIZE 128
|
||||
|
||||
|
||||
class CPathInfo
|
||||
{
|
||||
private:
|
||||
vec4_t *mPoints, *mWork;
|
||||
vec_t *mWeights;
|
||||
int mNumPoints;
|
||||
float mMinWidth, mMaxWidth;
|
||||
float mInc;
|
||||
float mDepth, mBreadth;
|
||||
float mDeviation;
|
||||
byte mCircleStamp[CIRCLE_STAMP_SIZE][CIRCLE_STAMP_SIZE];
|
||||
|
||||
void CreateCircle(void);
|
||||
void Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight);
|
||||
|
||||
public:
|
||||
CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey,
|
||||
float minWidth, float maxWidth, float depth, float deviation, float breadth,
|
||||
CPathInfo *Connected, unsigned CreationFlags);
|
||||
~CPathInfo(void);
|
||||
|
||||
int GetNumPoints(void) { return mNumPoints; }
|
||||
float *GetPoint(int index) { return mPoints[index]; }
|
||||
float GetWidth(int index) { return mPoints[index][3]; }
|
||||
|
||||
void GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector);
|
||||
void DrawPath(unsigned char *Data, int DataWidth, int DataHeight );
|
||||
};
|
||||
|
||||
|
||||
const int MAX_RANDOM_PATHS = 30;
|
||||
|
||||
// Path Creation Flags
|
||||
#define PATH_CREATION_CONNECT_FRONT 0x00000001
|
||||
|
||||
|
||||
|
||||
class CRandomTerrain
|
||||
{
|
||||
private:
|
||||
|
||||
class CCMLandScape *mLandScape;
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
int mArea;
|
||||
int mBorder;
|
||||
byte *mGrid;
|
||||
CPathInfo *mPaths[MAX_RANDOM_PATHS];
|
||||
|
||||
public:
|
||||
CRandomTerrain(void);
|
||||
~CRandomTerrain(void);
|
||||
|
||||
CCMLandScape *GetLandScape(void) { return mLandScape; }
|
||||
const vec3pair_t &GetBounds(void) const { return mLandScape->GetBounds(); }
|
||||
void rand_seed(int seed) { mLandScape->rand_seed(seed); }
|
||||
int get_rand_seed(void) { return mLandScape->get_rand_seed();}
|
||||
float flrand(float min, float max) { return mLandScape->flrand(min, max); }
|
||||
int irand(int min, int max) { return mLandScape->irand(min, max); }
|
||||
|
||||
void Init(class CCMLandScape *landscape, byte *data, int width, int height);
|
||||
void Shutdown(void);
|
||||
bool CreatePath(int PathID, int ConnectedID, unsigned CreationFlags, int numPoints,
|
||||
float bx, float by, float ex, float ey,
|
||||
float minWidth, float maxWidth, float depth, float deviation, float breadth );
|
||||
bool GetPathInfo(int PathNum, float PercentInto, vec2_t Coord, vec2_t Vector);
|
||||
void ParseGenerate(const char *GenerateFile);
|
||||
void Smooth ( void );
|
||||
void Generate(int symmetric);
|
||||
void ClearPaths(void);
|
||||
};
|
||||
|
||||
unsigned RMG_CreateSeed(char *TextSeed);
|
||||
|
||||
#endif // CM_RANDOM_H_INC
|
||||
529
code/qcommon/cm_shader.cpp
Normal file
529
code/qcommon/cm_shader.cpp
Normal file
@@ -0,0 +1,529 @@
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
|
||||
#include "cm_local.h"
|
||||
#include "memory.h"
|
||||
#include "chash.h"
|
||||
|
||||
class CCMShaderText
|
||||
{
|
||||
private:
|
||||
char mName[MAX_QPATH];
|
||||
class CCMShaderText *mNext;
|
||||
const char *mData;
|
||||
public:
|
||||
// Constructors
|
||||
CCMShaderText(const char *name, const char *data) { Q_strncpyz(mName, name, MAX_QPATH); mNext = NULL; mData = data; }
|
||||
~CCMShaderText(void) {}
|
||||
|
||||
// Accessors
|
||||
const char *GetName(void) const { return(mName); }
|
||||
class CCMShaderText *GetNext(void) const { return(mNext); }
|
||||
void SetNext(class CCMShaderText *next) { mNext = next; }
|
||||
void Destroy(void) { delete this; }
|
||||
|
||||
const char *GetData(void) const { return(mData); }
|
||||
};
|
||||
|
||||
char *shaderText = NULL;
|
||||
CHash<CCMShaderText> shaderTextTable;
|
||||
CHash<CCMShader> cmShaderTable;
|
||||
|
||||
const char *SkipWhitespace( const char *data, qboolean *hasNewLines );
|
||||
|
||||
//rwwFIXMEFIXME: Called at RE_BeginRegistration because Hunk_Clear
|
||||
//destroys the memory cmShaderTable is on. This is a temp solution
|
||||
//I guess.
|
||||
void ShaderTableCleanup()
|
||||
{
|
||||
cmShaderTable.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_CreateShaderTextHash
|
||||
=====================
|
||||
*/
|
||||
void CM_CreateShaderTextHash(void)
|
||||
{
|
||||
const char *p;
|
||||
qboolean hasNewLines;
|
||||
char *token;
|
||||
CCMShaderText *shader;
|
||||
|
||||
p = shaderText;
|
||||
// look for label
|
||||
while (p)
|
||||
{
|
||||
p = SkipWhitespace(p, &hasNewLines);
|
||||
token = COM_ParseExt( &p, qtrue );
|
||||
if ( !token[0] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
shader = new CCMShaderText(token, p);
|
||||
shaderTextTable.insert(shader);
|
||||
|
||||
SkipBracedSection(&p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_LoadShaderFiles
|
||||
|
||||
Finds and loads all .shader files, combining them into
|
||||
a single large text block that can be scanned for shader names
|
||||
=====================
|
||||
*/
|
||||
#define MAX_SHADER_FILES 1024
|
||||
|
||||
void CM_LoadShaderFiles( void )
|
||||
{
|
||||
char **shaderFiles1;
|
||||
int numShaders1;
|
||||
char *buffers[MAX_SHADER_FILES];
|
||||
int numShaders;
|
||||
int i;
|
||||
int sum = 0;
|
||||
|
||||
// scan for shader files
|
||||
shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 );
|
||||
|
||||
if ( !shaderFiles1 || !numShaders1 )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
numShaders = numShaders1;
|
||||
if ( numShaders > MAX_SHADER_FILES )
|
||||
{
|
||||
numShaders = MAX_SHADER_FILES;
|
||||
}
|
||||
|
||||
// load and parse shader files
|
||||
for ( i = 0; i < numShaders1; i++ )
|
||||
{
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles1[i] );
|
||||
Com_DPrintf( "...loading '%s'\n", filename );
|
||||
FS_ReadFile( filename, (void **)&buffers[i] );
|
||||
if ( !buffers[i] )
|
||||
{
|
||||
Com_Error( ERR_DROP, "Couldn't load %s", filename );
|
||||
}
|
||||
sum += COM_Compress( buffers[i] );
|
||||
}
|
||||
|
||||
// build single large buffer
|
||||
shaderText = (char *)Z_Malloc( sum + numShaders * 2, TAG_SHADERTEXT, qtrue);
|
||||
|
||||
// free in reverse order, so the temp files are all dumped
|
||||
for ( i = numShaders - 1; i >= 0 ; i-- )
|
||||
{
|
||||
strcat( shaderText, "\n" );
|
||||
strcat( shaderText, buffers[i] );
|
||||
FS_FreeFile( buffers[i] );
|
||||
}
|
||||
|
||||
// free up memory
|
||||
FS_FreeFileList( shaderFiles1 );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_GetShaderText
|
||||
==================
|
||||
*/
|
||||
|
||||
const char *CM_GetShaderText(const char *key)
|
||||
{
|
||||
CCMShaderText *st;
|
||||
|
||||
st = shaderTextTable[key];
|
||||
if(st)
|
||||
{
|
||||
return(st->GetData());
|
||||
}
|
||||
return(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_FreeShaderText
|
||||
==================
|
||||
*/
|
||||
|
||||
void CM_FreeShaderText(void)
|
||||
{
|
||||
shaderTextTable.clear();
|
||||
if(shaderText)
|
||||
{
|
||||
Z_Free(shaderText);
|
||||
shaderText = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_LoadShaderText
|
||||
|
||||
Loads in all the .shader files so it can be accessed by the server and the renderer
|
||||
Creates a hash table to quickly access the shader text
|
||||
==================
|
||||
*/
|
||||
|
||||
void CM_LoadShaderText(bool forceReload)
|
||||
{
|
||||
if(forceReload)
|
||||
{
|
||||
CM_FreeShaderText();
|
||||
}
|
||||
if(shaderText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Com_Printf("Loading shader text .....\n");
|
||||
CM_LoadShaderFiles();
|
||||
CM_CreateShaderTextHash();
|
||||
|
||||
Com_Printf("..... %d shader definitions loaded\n", shaderTextTable.count());
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
ParseSurfaceParm
|
||||
|
||||
surfaceparm <name>
|
||||
===============
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
int clearSolid, surfaceFlags, contents;
|
||||
} infoParm_t;
|
||||
|
||||
infoParm_t svInfoParms[] =
|
||||
{
|
||||
// Game content Flags
|
||||
{"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag
|
||||
{"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag
|
||||
{"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging
|
||||
{"slime", ~CONTENTS_SOLID, 0, CONTENTS_SLIME }, // mildly damaging
|
||||
{"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER },
|
||||
{"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering
|
||||
{"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, /* block shots, but not people */
|
||||
{"playerclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_PLAYERCLIP }, /* block only the player */
|
||||
{"monsterclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_MONSTERCLIP },
|
||||
{"botclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_BOTCLIP }, /* NPC do not enter */
|
||||
{"trigger", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TRIGGER },
|
||||
{"nodrop", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc)
|
||||
{"terrain", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TERRAIN }, /* use special terrain collsion */
|
||||
{"ladder", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_LADDER }, // climb up in it like water
|
||||
{"abseil", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_ABSEIL }, // can abseil down this brush
|
||||
{"outside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors)
|
||||
{"inside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors)
|
||||
|
||||
{"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp
|
||||
{"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component
|
||||
|
||||
/* Game surface flags */
|
||||
{"sky", -1, SURF_SKY, 0 }, /* emit light from an environment map */
|
||||
{"slick", -1, SURF_SLICK, 0 },
|
||||
|
||||
{"nodamage", -1, SURF_NODAMAGE, 0 },
|
||||
{"noimpact", -1, SURF_NOIMPACT, 0 }, /* don't make impact explosions or marks */
|
||||
{"nomarks", -1, SURF_NOMARKS, 0 }, /* don't make impact marks, but still explode */
|
||||
{"nodraw", -1, SURF_NODRAW, 0 }, /* don't generate a drawsurface (or a lightmap) */
|
||||
{"nosteps", -1, SURF_NOSTEPS, 0 },
|
||||
{"nodlight", -1, SURF_NODLIGHT, 0 }, /* don't ever add dynamic lights */
|
||||
{"metalsteps", -1, SURF_METALSTEPS,0 },
|
||||
{"nomiscents", -1, SURF_NOMISCENTS,0 }, /* No misc ents on this surface */
|
||||
{"forcefield", -1, SURF_FORCEFIELD,0 },
|
||||
{"forcesight", -1, SURF_FORCESIGHT,0 }, // only visible with force sight
|
||||
};
|
||||
|
||||
void SV_ParseSurfaceParm( CCMShader * shader, const char **text )
|
||||
{
|
||||
char *token;
|
||||
int numsvInfoParms = sizeof(svInfoParms) / sizeof(svInfoParms[0]);
|
||||
int i;
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
for ( i = 0 ; i < numsvInfoParms ; i++ )
|
||||
{
|
||||
if ( !Q_stricmp( token, svInfoParms[i].name ) )
|
||||
{
|
||||
shader->surfaceFlags |= svInfoParms[i].surfaceFlags;
|
||||
shader->contentFlags |= svInfoParms[i].contents;
|
||||
shader->contentFlags &= svInfoParms[i].clearSolid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
ParseMaterial
|
||||
=================
|
||||
*/
|
||||
const char *svMaterialNames[MATERIAL_LAST] =
|
||||
{
|
||||
MATERIALS
|
||||
};
|
||||
|
||||
void SV_ParseMaterial( CCMShader *shader, const char **text )
|
||||
{
|
||||
char *token;
|
||||
int i;
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
if ( !token[0] )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader->shader );
|
||||
return;
|
||||
}
|
||||
for(i = 0; i < MATERIAL_LAST; i++)
|
||||
{
|
||||
if ( !Q_stricmp( token, svMaterialNames[i] ) )
|
||||
{
|
||||
shader->surfaceFlags &= ~MATERIAL_MASK;//safety, clear it first
|
||||
shader->surfaceFlags |= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
ParseVector
|
||||
===============
|
||||
*/
|
||||
qboolean CM_ParseVector( CCMShader *shader, const char **text, int count, float *v )
|
||||
{
|
||||
char *token;
|
||||
int i;
|
||||
|
||||
// FIXME: spaces are currently required after parens, should change parseext...
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
if ( strcmp( token, "(" ) )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader );
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
for ( i = 0 ; i < count ; i++ )
|
||||
{
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
if ( !token[0] )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: missing vector element in shader '%s'\n", shader->shader );
|
||||
return qfalse;
|
||||
}
|
||||
v[i] = atof( token );
|
||||
}
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
if ( strcmp( token, ")" ) )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader );
|
||||
return qfalse;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CM_ParseShader
|
||||
|
||||
The current text pointer is at the explicit text definition of the
|
||||
shader. Parse it into the global shader variable.
|
||||
|
||||
This extracts all the info from the shader required for physics and collision
|
||||
It is designed to *NOT* load any image files and not require any of the renderer to
|
||||
be initialised.
|
||||
=================
|
||||
*/
|
||||
void CM_ParseShader( CCMShader *shader, const char **text )
|
||||
{
|
||||
char *token;
|
||||
|
||||
token = COM_ParseExt( text, qtrue );
|
||||
if ( token[0] != '{' )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader->shader );
|
||||
return;
|
||||
}
|
||||
|
||||
while ( true )
|
||||
{
|
||||
token = COM_ParseExt( text, qtrue );
|
||||
if ( !token[0] )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: no concluding '}' in shader %s\n", shader->shader );
|
||||
return;
|
||||
}
|
||||
|
||||
// end of shader definition
|
||||
if ( token[0] == '}' )
|
||||
{
|
||||
break;
|
||||
}
|
||||
// stage definition
|
||||
else if ( token[0] == '{' )
|
||||
{
|
||||
SkipBracedSection( text );
|
||||
continue;
|
||||
}
|
||||
// material deprecated as of 11 Jan 01
|
||||
// material undeprecated as of 7 May 01 - q3map_material deprecated
|
||||
else if ( !Q_stricmp( token, "material" ) || !Q_stricmp( token, "q3map_material" ) )
|
||||
{
|
||||
SV_ParseMaterial( shader, text );
|
||||
}
|
||||
// sun parms
|
||||
// q3map_sun deprecated as of 11 Jan 01
|
||||
else if ( !Q_stricmp( token, "sun" ) || !Q_stricmp( token, "q3map_sun" ) )
|
||||
{
|
||||
// float a, b;
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// shader->sunLight[0] = atof( token );
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// shader->sunLight[1] = atof( token );
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// shader->sunLight[2] = atof( token );
|
||||
|
||||
// VectorNormalize( shader->sunLight );
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// a = atof( token );
|
||||
// VectorScale( shader->sunLight, a, shader->sunLight);
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// a = DEG2RAD(atof( token ));
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
// b = DEG2RAD(atof( token ));
|
||||
|
||||
// shader->sunDirection[0] = cos( a ) * cos( b );
|
||||
// shader->sunDirection[1] = sin( a ) * cos( b );
|
||||
// shader->sunDirection[2] = sin( b );
|
||||
}
|
||||
else if ( !Q_stricmp( token, "surfaceParm" ) )
|
||||
{
|
||||
SV_ParseSurfaceParm( shader, text );
|
||||
continue;
|
||||
}
|
||||
else if ( !Q_stricmp( token, "fogParms" ) )
|
||||
{
|
||||
vec3_t fogColor;
|
||||
if ( !CM_ParseVector( shader, text, 3, fogColor ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
token = COM_ParseExt( text, qfalse );
|
||||
if ( !token[0] )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader->shader );
|
||||
continue;
|
||||
}
|
||||
// shader->depthForOpaque = atof( token );
|
||||
|
||||
// skip any old gradient directions
|
||||
SkipRestOfLine( (const char **)text );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CM_SetupShaderProperties
|
||||
|
||||
Scans thru the shaders loaded for the map, parses the text of that shader and
|
||||
extracts the interesting info *WITHOUT* loading up any images or requiring
|
||||
the renderer to be active.
|
||||
=================
|
||||
*/
|
||||
|
||||
void CM_SetupShaderProperties(void)
|
||||
{
|
||||
int i;
|
||||
const char *def;
|
||||
CCMShader *shader;
|
||||
|
||||
// Add all basic shaders to the cmShaderTable
|
||||
for(i = 0; i < cmg.numShaders; i++)
|
||||
{
|
||||
cmShaderTable.insert(CM_GetShaderInfo(i));
|
||||
}
|
||||
// Go through and parse evaluate shader names to shadernums
|
||||
for(i = 0; i < cmg.numShaders; i++)
|
||||
{
|
||||
shader = CM_GetShaderInfo(i);
|
||||
def = CM_GetShaderText(shader->shader);
|
||||
if(def)
|
||||
{
|
||||
CM_ParseShader(shader, &def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CM_ShutdownShaderProperties(void)
|
||||
{
|
||||
if(cmShaderTable.count())
|
||||
{
|
||||
Com_Printf("Shutting down cmShaderTable .....\n");
|
||||
cmShaderTable.clear();
|
||||
}
|
||||
}
|
||||
|
||||
CCMShader *CM_GetShaderInfo( const char *name )
|
||||
{
|
||||
CCMShader *out;
|
||||
const char *def;
|
||||
|
||||
out = cmShaderTable[name];
|
||||
if(out)
|
||||
{
|
||||
return(out);
|
||||
}
|
||||
|
||||
// Create a new CCMShader class
|
||||
//out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), h_high );
|
||||
out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), qtrue );
|
||||
// Set defaults
|
||||
Q_strncpyz(out->shader, name, MAX_QPATH);
|
||||
out->contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE;
|
||||
|
||||
// Parse in any text if it exists
|
||||
def = CM_GetShaderText(name);
|
||||
if(def)
|
||||
{
|
||||
CM_ParseShader(out, &def);
|
||||
}
|
||||
|
||||
cmShaderTable.insert(out);
|
||||
return(out);
|
||||
}
|
||||
|
||||
CCMShader *CM_GetShaderInfo( int shaderNum )
|
||||
{
|
||||
CCMShader *out;
|
||||
|
||||
if((shaderNum < 0) || (shaderNum >= cmg.numShaders))
|
||||
{
|
||||
return(NULL);
|
||||
}
|
||||
out = cmg.shaders + shaderNum;
|
||||
return(out);
|
||||
}
|
||||
|
||||
// end
|
||||
1714
code/qcommon/cm_terrain.cpp
Normal file
1714
code/qcommon/cm_terrain.cpp
Normal file
File diff suppressed because it is too large
Load Diff
489
code/qcommon/cm_terrainmap.cpp
Normal file
489
code/qcommon/cm_terrainmap.cpp
Normal file
@@ -0,0 +1,489 @@
|
||||
#include "../server/exe_headers.h"
|
||||
|
||||
#include "cm_local.h"
|
||||
#include "cm_patch.h"
|
||||
#include "cm_landscape.h"
|
||||
#include "../game/genericparser2.h"
|
||||
//#include "image.h"
|
||||
//#include "../qcommon/q_imath.h"
|
||||
#include "cm_terrainmap.h"
|
||||
#include "cm_draw.h"
|
||||
#include "../png/png.h"
|
||||
|
||||
static CTerrainMap *TerrainMap = 0;
|
||||
|
||||
void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height,
|
||||
qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode );
|
||||
|
||||
// simple function for getting a proper color for a side
|
||||
inline CPixel32 SideColor(int side)
|
||||
{
|
||||
CPixel32 col(255,255,255);
|
||||
switch (side)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case SIDE_BLUE:
|
||||
col = CPixel32(0,0,192);
|
||||
break;
|
||||
case SIDE_RED:
|
||||
col = CPixel32(192,0,0);
|
||||
break;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
CTerrainMap::CTerrainMap(CCMLandScape *landscape) :
|
||||
mLandscape(landscape)
|
||||
{
|
||||
ApplyBackground();
|
||||
ApplyHeightmap();
|
||||
|
||||
CDraw32 draw;
|
||||
draw.SetBuffer((CPixel32*) mImage);
|
||||
draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH);
|
||||
|
||||
// create version with paths and water shown
|
||||
int x,y;
|
||||
int water;
|
||||
int land;
|
||||
|
||||
for (y=0; y<TM_HEIGHT; y++)
|
||||
for (x=0; x<TM_WIDTH; x++)
|
||||
{
|
||||
CPixel32 cp = ((CPixel32*)mBufImage)[PIXPOS(x,y,TM_WIDTH)];
|
||||
land = CLAMP(((255 - cp.a)*2)/3,0,255);
|
||||
water = CLAMP((landscape->GetBaseWaterHeight() - cp.a)*4, 0, 255);
|
||||
cp.a = 255;
|
||||
|
||||
if (x > TM_BORDER && x < (TM_WIDTH-TM_BORDER) &&
|
||||
y > TM_BORDER && y < (TM_WIDTH-TM_BORDER))
|
||||
{
|
||||
cp = ALPHA_PIX (CPixel32(0,0,0), cp, land, 256-land);
|
||||
if (water > 0)
|
||||
cp = ALPHA_PIX (CPixel32(0,0,255), cp, water, 256-water);
|
||||
}
|
||||
|
||||
draw.PutPix(x, y, cp);
|
||||
}
|
||||
|
||||
// Load icons for symbols on map
|
||||
GLenum format;
|
||||
#ifdef _XBOX
|
||||
int mipcount;
|
||||
|
||||
R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &mipcount, &format);
|
||||
R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &mipcount, &format);
|
||||
R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &mipcount, &format);
|
||||
|
||||
R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &mipcount, &format);
|
||||
#else
|
||||
R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &format);
|
||||
R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &format);
|
||||
R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &format);
|
||||
|
||||
R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &format);
|
||||
#endif
|
||||
}
|
||||
|
||||
CTerrainMap::~CTerrainMap()
|
||||
{
|
||||
if (mSymStart)
|
||||
{
|
||||
Z_Free(mSymStart);
|
||||
mSymStart = NULL;
|
||||
}
|
||||
|
||||
if (mSymEnd)
|
||||
{
|
||||
Z_Free(mSymEnd);
|
||||
mSymEnd = NULL;
|
||||
}
|
||||
|
||||
if (mSymBld)
|
||||
{
|
||||
Z_Free(mSymBld);
|
||||
mSymBld = NULL;
|
||||
}
|
||||
|
||||
if (mSymObjective)
|
||||
{
|
||||
Z_Free(mSymObjective);
|
||||
mSymObjective = NULL;
|
||||
}
|
||||
|
||||
CDraw32::CleanUp();
|
||||
}
|
||||
|
||||
void CTerrainMap::ApplyBackground(void)
|
||||
{
|
||||
int x, y;
|
||||
byte *outPos;
|
||||
float xRel, yRel, xInc, yInc;
|
||||
byte *backgroundImage;
|
||||
int backgroundWidth, backgroundHeight, backgroundDepth;
|
||||
int pos;
|
||||
GLenum format;
|
||||
|
||||
memset(mImage, 255, sizeof(mBufImage));
|
||||
// R_LoadImage("textures\\kamchatka\\ice", &backgroundImage, &backgroundWidth, &backgroundHeight, &format);0
|
||||
backgroundDepth = 4;
|
||||
|
||||
#ifdef _XBOX
|
||||
int mipcount;
|
||||
|
||||
R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &mipcount, &format);
|
||||
#else
|
||||
R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &format);
|
||||
#endif
|
||||
if (backgroundImage)
|
||||
{
|
||||
outPos = (byte *)mBufImage;
|
||||
xInc = (float)backgroundWidth / (float)TM_WIDTH;
|
||||
yInc = (float)backgroundHeight / (float)TM_HEIGHT;
|
||||
|
||||
yRel = 0.0;
|
||||
for(y=0;y<TM_HEIGHT;y++)
|
||||
{
|
||||
xRel = 0.0;
|
||||
for(x=0;x<TM_WIDTH;x++)
|
||||
{
|
||||
pos = ((((int)yRel)*backgroundWidth) + ((int)xRel)) * 4;
|
||||
*outPos = backgroundImage[pos++];
|
||||
outPos++;
|
||||
*outPos = backgroundImage[pos++];
|
||||
outPos++;
|
||||
*outPos = backgroundImage[pos];
|
||||
outPos+=2;
|
||||
xRel += xInc;
|
||||
}
|
||||
yRel += yInc;
|
||||
}
|
||||
Z_Free(backgroundImage);
|
||||
}
|
||||
}
|
||||
|
||||
void CTerrainMap::ApplyHeightmap(void)
|
||||
{
|
||||
int x, y;
|
||||
byte *inPos = mLandscape->GetHeightMap();
|
||||
int width = mLandscape->GetRealWidth();
|
||||
int height = mLandscape->GetRealHeight();
|
||||
byte *outPos;
|
||||
unsigned tempColor;
|
||||
float xRel, yRel, xInc, yInc;
|
||||
int count;
|
||||
|
||||
outPos = (byte *)mBufImage;
|
||||
outPos += (((TM_BORDER * TM_WIDTH) + TM_BORDER) * 4);
|
||||
xInc = (float)width / (float)(TM_REAL_WIDTH);
|
||||
yInc = (float)height / (float)(TM_REAL_HEIGHT);
|
||||
|
||||
// add in height map as alpha
|
||||
yRel = 0.0;
|
||||
for(y=0;y<TM_REAL_HEIGHT;y++)
|
||||
{
|
||||
// x is flipped!
|
||||
xRel = width;
|
||||
for(x=0;x<TM_REAL_WIDTH;x++)
|
||||
{
|
||||
count = 1;
|
||||
tempColor = inPos[(((int)yRel)*width) + ((int)xRel)];
|
||||
if (yRel >= 1.0)
|
||||
{
|
||||
tempColor += inPos[(((int)(yRel-0.5))*width) + ((int)xRel)];
|
||||
count++;
|
||||
}
|
||||
if (yRel <= height-2)
|
||||
{
|
||||
tempColor += inPos[(((int)(yRel+0.5))*width) + ((int)xRel)];
|
||||
count++;
|
||||
}
|
||||
if (xRel >= 1.0)
|
||||
{
|
||||
tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel-0.5))];
|
||||
count++;
|
||||
}
|
||||
if (xRel <= width-2)
|
||||
{
|
||||
tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel+0.5))];
|
||||
count++;
|
||||
}
|
||||
tempColor /= count;
|
||||
|
||||
outPos[3] = tempColor;
|
||||
outPos += 4;
|
||||
|
||||
// x is flipped!
|
||||
xRel -= xInc;
|
||||
}
|
||||
outPos += TM_BORDER * 4 * 2;
|
||||
|
||||
yRel += yInc;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert position in game coords to automap coords
|
||||
void CTerrainMap::ConvertPos(int& x, int& y)
|
||||
{
|
||||
x = ((x - mLandscape->GetMins()[0]) / mLandscape->GetSize()[0]) * TM_REAL_WIDTH;
|
||||
y = ((y - mLandscape->GetMins()[1]) / mLandscape->GetSize()[1]) * TM_REAL_HEIGHT;
|
||||
|
||||
// x is flipped!
|
||||
x = TM_REAL_WIDTH - x - 1;
|
||||
|
||||
// border
|
||||
x += TM_BORDER;
|
||||
y += TM_BORDER;
|
||||
}
|
||||
|
||||
void CTerrainMap::AddStart(int x, int y, int side)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
draw.BlitColor(x-mSymStartWidth/2, y-mSymStartHeight/2, mSymStartWidth, mSymStartHeight,
|
||||
(CPixel32*)mSymStart, 0, 0, mSymStartWidth, SideColor(side));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddEnd(int x, int y, int side)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
draw.BlitColor(x-mSymEndWidth/2, y-mSymEndHeight/2, mSymEndWidth, mSymEndHeight,
|
||||
(CPixel32*)mSymEnd, 0, 0, mSymEndWidth, SideColor(side));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddObjective(int x, int y, int side)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
draw.BlitColor(x-mSymObjectiveWidth/2, y-mSymObjectiveHeight/2, mSymObjectiveWidth, mSymObjectiveHeight,
|
||||
(CPixel32*)mSymObjective, 0, 0, mSymObjectiveWidth, SideColor(side));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddBuilding(int x, int y, int side)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
draw.BlitColor(x-mSymBldWidth/2, y-mSymBldHeight/2, mSymBldWidth, mSymBldHeight,
|
||||
(CPixel32*)mSymBld, 0, 0, mSymBldWidth, SideColor(side));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddNPC(int x, int y, bool friendly)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
if (friendly)
|
||||
draw.DrawCircle(x,y,3, CPixel32(0,192,0), CPixel32(0,0,0,0));
|
||||
else
|
||||
draw.DrawCircle(x,y,3, CPixel32(192,0,0), CPixel32(0,0,0,0));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddNode(int x, int y)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
draw.DrawCircle(x,y,20, CPixel32(255,255,255), CPixel32(0,0,0,0));
|
||||
}
|
||||
|
||||
void CTerrainMap::AddWallRect(int x, int y, int side)
|
||||
{
|
||||
ConvertPos(x, y);
|
||||
|
||||
CDraw32 draw;
|
||||
switch (side)
|
||||
{
|
||||
default:
|
||||
draw.DrawBox(x-1,y-1,3,3,CPixel32(192,192,192,128));
|
||||
break;
|
||||
case SIDE_BLUE:
|
||||
draw.DrawBox(x-1,y-1,3,3,CPixel32(0,0,192,128));
|
||||
break;
|
||||
case SIDE_RED:
|
||||
draw.DrawBox(x-1,y-1,3,3,CPixel32(192,0,0,128));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CTerrainMap::AddPlayer(vec3_t origin, vec3_t angles)
|
||||
{
|
||||
// draw player start on automap
|
||||
CDraw32 draw;
|
||||
|
||||
vec3_t up;
|
||||
vec3_t pt[4] = {{0,0,0},{-5,-5,0},{10,0,0},{-5,5,0}};
|
||||
vec3_t p;
|
||||
int x,y,i;
|
||||
float facing;
|
||||
POINT poly[4];
|
||||
|
||||
facing = angles[1];
|
||||
|
||||
up[0] = 0;
|
||||
up[1] = 0;
|
||||
up[2] = 1;
|
||||
|
||||
x = (int)origin[0];
|
||||
y = (int)origin[1];
|
||||
ConvertPos(x, y);
|
||||
x++; y++;
|
||||
|
||||
for (i=0; i<4; i++)
|
||||
{
|
||||
RotatePointAroundVector( p, up, pt[i], facing );
|
||||
poly[i].x = (int)(-p[0] + x);
|
||||
poly[i].y = (int)(p[1] + y);
|
||||
}
|
||||
|
||||
// draw arrowhead shadow
|
||||
draw.DrawPolygon(4, poly, CPixel32(0,0,0,128), CPixel32(0,0,0,128));
|
||||
|
||||
// draw arrowhead
|
||||
for (i=0; i<4; i++)
|
||||
{
|
||||
poly[i].x--;
|
||||
poly[i].y--;
|
||||
}
|
||||
draw.DrawPolygon(4, poly, CPixel32(255,255,255), CPixel32(255,255,255));
|
||||
}
|
||||
|
||||
void CTerrainMap::Upload(vec3_t player_origin, vec3_t player_angles)
|
||||
{
|
||||
CDraw32 draw;
|
||||
|
||||
// copy completed map to mBufImage
|
||||
draw.SetBuffer((CPixel32*) mBufImage);
|
||||
draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH);
|
||||
|
||||
draw.Blit(0, 0, TM_WIDTH, TM_HEIGHT,
|
||||
(CPixel32*)mImage, 0, 0, TM_WIDTH);
|
||||
|
||||
// now draw player's location on map
|
||||
if (player_origin)
|
||||
{
|
||||
AddPlayer(player_origin, player_angles);
|
||||
}
|
||||
|
||||
draw.SetAlphaBuffer(255);
|
||||
|
||||
R_CreateAutomapImage("*automap", (unsigned char *)draw.buffer, TM_WIDTH, TM_HEIGHT, qfalse, qfalse, qtrue, qfalse);
|
||||
|
||||
draw.SetBuffer((CPixel32*) mImage);
|
||||
}
|
||||
|
||||
void CTerrainMap::SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed)
|
||||
{
|
||||
//ri.COM_SavePNG(va("save/%s_%s_%s.png", terrainName, missionName, seed),
|
||||
// (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4);
|
||||
//rww - Use JPG here? This function seems to be only for debugging anyway.
|
||||
// PNG_Save(va("save/%s_%s_%s.png", terrainName, missionName, seed),
|
||||
// (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4);
|
||||
}
|
||||
|
||||
void CM_TM_Create(CCMLandScape *landscape)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
CM_TM_Free();
|
||||
}
|
||||
|
||||
TerrainMap = new CTerrainMap(landscape);
|
||||
}
|
||||
|
||||
void CM_TM_Free(void)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
delete TerrainMap;
|
||||
TerrainMap = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddStart(int x, int y, int side)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddStart(x, y, side);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddEnd(int x, int y, int side)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddEnd(x, y, side);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddObjective(int x, int y, int side)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddObjective(x, y, side);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddNPC(int x, int y, bool friendly)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddNPC(x, y, friendly);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddNode(int x, int y)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddNode(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddBuilding(int x, int y, int side)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddBuilding(x, y, side);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_AddWallRect(int x, int y, int side)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->AddWallRect(x, y, side);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->Upload(player_origin, player_angles);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{ // write out automap
|
||||
TerrainMap->SaveImageToDisk(terrainName, missionName, seed);
|
||||
}
|
||||
}
|
||||
|
||||
void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height)
|
||||
{
|
||||
if (TerrainMap)
|
||||
{
|
||||
TerrainMap->ConvertPos(x, y);
|
||||
x = x * Width / TM_WIDTH;
|
||||
y = y * Height / TM_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
77
code/qcommon/cm_terrainmap.h
Normal file
77
code/qcommon/cm_terrainmap.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#if !defined(CM_TERRAINMAP_H_INC)
|
||||
#define CM_TERRAINMAP_H_INC
|
||||
|
||||
#define TM_WIDTH 512
|
||||
#define TM_HEIGHT 512
|
||||
#define TM_BORDER 16
|
||||
#define TM_REAL_WIDTH (TM_WIDTH-TM_BORDER-TM_BORDER)
|
||||
#define TM_REAL_HEIGHT (TM_HEIGHT-TM_BORDER-TM_BORDER)
|
||||
|
||||
class CTerrainMap
|
||||
{
|
||||
private:
|
||||
byte mImage[TM_HEIGHT][TM_WIDTH][4]; // image to output
|
||||
byte mBufImage[TM_HEIGHT][TM_WIDTH][4]; // src data for image, color and bump
|
||||
|
||||
byte* mSymBld;
|
||||
int mSymBldWidth;
|
||||
int mSymBldHeight;
|
||||
|
||||
byte* mSymStart;
|
||||
int mSymStartWidth;
|
||||
int mSymStartHeight;
|
||||
|
||||
byte* mSymEnd;
|
||||
int mSymEndWidth;
|
||||
int mSymEndHeight;
|
||||
|
||||
byte* mSymObjective;
|
||||
int mSymObjectiveWidth;
|
||||
int mSymObjectiveHeight;
|
||||
|
||||
CCMLandScape *mLandscape;
|
||||
|
||||
void ApplyBackground(void);
|
||||
void ApplyHeightmap(void);
|
||||
|
||||
public:
|
||||
CTerrainMap(CCMLandScape *landscape);
|
||||
~CTerrainMap();
|
||||
|
||||
void ConvertPos(int& x, int& y);
|
||||
void AddBuilding(int x, int y, int side);
|
||||
void AddStart(int x, int y, int side);
|
||||
void AddEnd(int x, int y, int side);
|
||||
void AddObjective(int x, int y, int side);
|
||||
void AddNPC(int x, int y, bool friendly);
|
||||
void AddWallRect(int x, int y, int side);
|
||||
void AddNode(int x, int y);
|
||||
void AddPlayer(vec3_t origin, vec3_t angles);
|
||||
|
||||
void Upload(vec3_t player_origin, vec3_t player_angles);
|
||||
void SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed);
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
SIDE_NONE =0,
|
||||
SIDE_BLUE =1,
|
||||
SIDE_RED = 2
|
||||
};
|
||||
|
||||
void CM_TM_Create(CCMLandScape *landscape);
|
||||
void CM_TM_Free(void);
|
||||
void CM_TM_AddStart(int x, int y, int side = SIDE_NONE);
|
||||
void CM_TM_AddEnd(int x, int y, int side = SIDE_NONE);
|
||||
void CM_TM_AddObjective(int x, int y, int side = SIDE_NONE);
|
||||
void CM_TM_AddNPC(int x, int y, bool friendly);
|
||||
void CM_TM_AddWallRect(int x, int y, int side = SIDE_NONE);
|
||||
void CM_TM_AddNode(int x, int y);
|
||||
void CM_TM_AddBuilding(int x, int y, int side = SIDE_NONE);
|
||||
void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles);
|
||||
void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed);
|
||||
void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height);
|
||||
|
||||
#endif CM_TERRAINMAP_H_INC
|
||||
|
||||
793
code/qcommon/cm_test.cpp
Normal file
793
code/qcommon/cm_test.cpp
Normal file
@@ -0,0 +1,793 @@
|
||||
#include "cm_local.h"
|
||||
#pragma warning (push, 3) //go back down to 3 for the stl include
|
||||
#pragma warning (pop)
|
||||
using namespace std;
|
||||
|
||||
#ifdef _XBOX
|
||||
#include "../renderer/tr_local.h"
|
||||
#endif
|
||||
|
||||
class CPoint
|
||||
{
|
||||
public:
|
||||
float x,y,z;
|
||||
CPoint(float _x,float _y,float _z):
|
||||
x(_x),
|
||||
y(_y),
|
||||
z(_z)
|
||||
{
|
||||
}
|
||||
bool operator== (const CPoint& _P) const {return((x==_P.x)&&(y==_P.y)&&(z==_P.z));}
|
||||
};
|
||||
/*
|
||||
class CPointComparator
|
||||
{
|
||||
public:
|
||||
bool operator()(const CPoint& _A,const CPoint& _B) const {return((_A.x==_B.x)&&(_A.y==_B.y)&&(_A.z==_B.z));}
|
||||
};
|
||||
*/
|
||||
|
||||
// Fixed memory version of pointToLeaf that doesn't use STL
|
||||
// cuts down on memory fragmentation
|
||||
struct PointAndLeaf
|
||||
{
|
||||
// Default constructor for array construction below
|
||||
PointAndLeaf() : point(0, 0, 0), leaf(0) { }
|
||||
|
||||
CPoint point;
|
||||
int leaf;
|
||||
};
|
||||
|
||||
// I think it is a patholoically bad idea to do a 64 item linear search for a cache,
|
||||
// so I reduced this to something more manageable.
|
||||
// hopefully getting rid of water checks on maps with no water will leave us less
|
||||
// reliant on this cache. -gwg
|
||||
|
||||
#define MAX_POINTS_TO_LEAVES 16
|
||||
|
||||
static PointAndLeaf pointToLeaf[MAX_POINTS_TO_LEAVES];
|
||||
static int oldestPointToLeaf = 0, sizePointToLeaf = 0;
|
||||
|
||||
//static hlist<pair<CPoint,int> > pointToLeaf;
|
||||
//static hlist<pair<CPoint,int> > pointToContents;
|
||||
|
||||
void CM_CleanLeafCache(void)
|
||||
{
|
||||
oldestPointToLeaf = sizePointToLeaf = 0;
|
||||
// pointToLeaf.clear();
|
||||
#if 0 // VVFIXME
|
||||
hlist<pair<CPoint,int> >::iterator l;
|
||||
for(l=pointToLeaf.begin();l!=pointToLeaf.end();l++)
|
||||
{
|
||||
pointToLeaf.erase(l);
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
for(l=pointToContents.begin();l!=pointToContents.end();l++)
|
||||
{
|
||||
pointToContents.erase(l);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_PointLeafnum_r
|
||||
|
||||
==================
|
||||
*/
|
||||
int CM_PointLeafnum_r( const vec3_t p, int num, clipMap_t *local ) {
|
||||
float d;
|
||||
cNode_t *node;
|
||||
cplane_t *plane;
|
||||
|
||||
#ifdef _XBOX
|
||||
if(!tr.world) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (num >= 0)
|
||||
{
|
||||
node = local->nodes + num;
|
||||
#ifdef _XBOX
|
||||
plane = cmg.planes + tr.world->nodes[num].planeNum;
|
||||
#else
|
||||
plane = node->plane;
|
||||
#endif
|
||||
|
||||
if (plane->type < 3)
|
||||
d = p[plane->type] - plane->dist;
|
||||
else
|
||||
d = DotProduct (plane->normal, p) - plane->dist;
|
||||
if (d < 0)
|
||||
num = node->children[1];
|
||||
else
|
||||
num = node->children[0];
|
||||
}
|
||||
|
||||
c_pointcontents++; // optimize counter
|
||||
|
||||
return -1 - num;
|
||||
}
|
||||
|
||||
int CM_PointLeafnum( const vec3_t p ) {
|
||||
if ( !cmg.numNodes ) { // map not loaded
|
||||
return 0;
|
||||
}
|
||||
return CM_PointLeafnum_r (p, 0, &cmg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================================================================
|
||||
|
||||
LEAF LISTING
|
||||
|
||||
======================================================================
|
||||
*/
|
||||
|
||||
|
||||
void CM_StoreLeafs( leafList_t *ll, int nodenum ) {
|
||||
int leafNum;
|
||||
|
||||
leafNum = -1 - nodenum;
|
||||
|
||||
// store the lastLeaf even if the list is overflowed
|
||||
if ( cmg.leafs[ leafNum ].cluster != -1 ) {
|
||||
ll->lastLeaf = leafNum;
|
||||
}
|
||||
|
||||
if ( ll->count >= ll->maxcount) {
|
||||
ll->overflowed = qtrue;
|
||||
return;
|
||||
}
|
||||
ll->list[ ll->count++ ] = leafNum;
|
||||
}
|
||||
|
||||
void CM_StoreBrushes( leafList_t *ll, int nodenum ) {
|
||||
int i, k;
|
||||
int leafnum;
|
||||
int brushnum;
|
||||
cLeaf_t *leaf;
|
||||
cbrush_t *b;
|
||||
|
||||
leafnum = -1 - nodenum;
|
||||
|
||||
leaf = &cmg.leafs[leafnum];
|
||||
|
||||
for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) {
|
||||
brushnum = cmg.leafbrushes[leaf->firstLeafBrush+k];
|
||||
b = &cmg.brushes[brushnum];
|
||||
if ( b->checkcount == cmg.checkcount ) {
|
||||
continue; // already checked this brush in another leaf
|
||||
}
|
||||
b->checkcount = cmg.checkcount;
|
||||
for ( i = 0 ; i < 3 ; i++ ) {
|
||||
if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i != 3 ) {
|
||||
continue;
|
||||
}
|
||||
if ( ll->count >= ll->maxcount) {
|
||||
ll->overflowed = qtrue;
|
||||
return;
|
||||
}
|
||||
((cbrush_t **)ll->list)[ ll->count++ ] = b;
|
||||
}
|
||||
#if 0
|
||||
// store patches?
|
||||
for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
|
||||
patch = cmg.surfaces[ cmg.leafsurfaces[ leaf->firstleafsurface + k ] ];
|
||||
if ( !patch ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
CM_BoxLeafnums
|
||||
|
||||
Fills in a list of all the leafs touched
|
||||
=============
|
||||
*/
|
||||
void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) {
|
||||
cplane_t *plane;
|
||||
cNode_t *node;
|
||||
int s;
|
||||
|
||||
#ifdef _XBOX
|
||||
if(!tr.world) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
if (nodenum < 0) {
|
||||
ll->storeLeafs( ll, nodenum );
|
||||
return;
|
||||
}
|
||||
|
||||
node = &cmg.nodes[nodenum];
|
||||
|
||||
#ifdef _XBOX
|
||||
plane = cmg.planes + tr.world->nodes[nodenum].planeNum;
|
||||
#else
|
||||
plane = node->plane;
|
||||
#endif
|
||||
s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane );
|
||||
if (s == 1) {
|
||||
nodenum = node->children[0];
|
||||
} else if (s == 2) {
|
||||
nodenum = node->children[1];
|
||||
} else {
|
||||
// go down both
|
||||
CM_BoxLeafnums_r( ll, node->children[0] );
|
||||
nodenum = node->children[1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_BoxLeafnums
|
||||
==================
|
||||
*/
|
||||
int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList, int listsize, int *lastLeaf) {
|
||||
leafList_t ll;
|
||||
|
||||
cmg.checkcount++;
|
||||
|
||||
VectorCopy( mins, ll.bounds[0] );
|
||||
VectorCopy( maxs, ll.bounds[1] );
|
||||
ll.count = 0;
|
||||
ll.maxcount = listsize;
|
||||
ll.list = boxList;
|
||||
ll.storeLeafs = CM_StoreLeafs;
|
||||
ll.lastLeaf = 0;
|
||||
ll.overflowed = qfalse;
|
||||
|
||||
CM_BoxLeafnums_r( &ll, 0 );
|
||||
|
||||
*lastLeaf = ll.lastLeaf;
|
||||
return ll.count;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_BoxBrushes
|
||||
==================
|
||||
*/
|
||||
int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxlist, int listsize ) {
|
||||
leafList_t ll;
|
||||
|
||||
cmg.checkcount++;
|
||||
|
||||
VectorCopy( mins, ll.bounds[0] );
|
||||
VectorCopy( maxs, ll.bounds[1] );
|
||||
ll.count = 0;
|
||||
ll.maxcount = listsize;
|
||||
ll.list = (int *)boxlist;
|
||||
ll.storeLeafs = CM_StoreBrushes;
|
||||
ll.lastLeaf = 0;
|
||||
ll.overflowed = qfalse;
|
||||
|
||||
CM_BoxLeafnums_r( &ll, 0 );
|
||||
|
||||
return ll.count;
|
||||
}
|
||||
|
||||
|
||||
//====================================================================
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_PointContents
|
||||
|
||||
==================
|
||||
*/
|
||||
|
||||
#if 1
|
||||
|
||||
int CM_PointContents( const vec3_t p, clipHandle_t model ) {
|
||||
int leafnum=0;
|
||||
int i, k;
|
||||
int brushnum;
|
||||
cLeaf_t *leaf;
|
||||
cbrush_t *b;
|
||||
int contents;
|
||||
float d;
|
||||
cmodel_t *clipm;
|
||||
clipMap_t *local;
|
||||
|
||||
if (!cmg.numNodes) { // map not loaded
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( model ) {
|
||||
clipm = CM_ClipHandleToModel( model, &local );
|
||||
leaf = &clipm->leaf;
|
||||
}
|
||||
else
|
||||
{
|
||||
local = &cmg;
|
||||
CPoint pt(p[0],p[1],p[2]);
|
||||
/* map<CPoint,int,CPointComparator>::iterator l=pointToLeaf.find(pt);
|
||||
if(l!=pointToLeaf.end())
|
||||
{
|
||||
leafnum=(*l).second;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pointToLeaf.size()>=64)
|
||||
{
|
||||
pointToLeaf.clear();
|
||||
Com_Printf("Cleared cache\n");
|
||||
}
|
||||
leafnum=CM_PointLeafnum_r(p, 0);
|
||||
pointToLeaf[pt]=leafnum;
|
||||
}*/
|
||||
|
||||
int l = 0;
|
||||
for ( ; l < sizePointToLeaf; ++l)
|
||||
{
|
||||
if (pointToLeaf[l].point == pt)
|
||||
{
|
||||
leafnum = pointToLeaf[l].leaf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (l == sizePointToLeaf)
|
||||
{ // Didn't find it
|
||||
if (sizePointToLeaf < MAX_POINTS_TO_LEAVES)
|
||||
{ // We're adding a new one, rather than replacing
|
||||
sizePointToLeaf++;
|
||||
}
|
||||
else
|
||||
{ // Put it in the "oldest" slot
|
||||
l = oldestPointToLeaf++;
|
||||
oldestPointToLeaf &= (MAX_POINTS_TO_LEAVES-1);
|
||||
}
|
||||
|
||||
leafnum = CM_PointLeafnum_r(p, 0, local);
|
||||
pointToLeaf[l].leaf = leafnum;
|
||||
pointToLeaf[l].point = pt;
|
||||
}
|
||||
|
||||
/*
|
||||
hlist<pair<CPoint,int> >::iterator l;
|
||||
for(l=pointToLeaf.begin();l!=pointToLeaf.end();l++)
|
||||
{
|
||||
if((*l).first==pt)
|
||||
{
|
||||
leafnum=(*l).second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(l==pointToLeaf.end())
|
||||
{
|
||||
if(pointToLeaf.size()>=64)
|
||||
{
|
||||
pointToLeaf.pop_back();
|
||||
}
|
||||
leafnum=CM_PointLeafnum_r(p, 0, local);
|
||||
pointToLeaf.push_front(pair<CPoint,int>(pt,leafnum));
|
||||
}
|
||||
*/
|
||||
leaf = &local->leafs[leafnum];
|
||||
}
|
||||
|
||||
contents = 0;
|
||||
for (k=0 ; k<leaf->numLeafBrushes ; k++) {
|
||||
brushnum = local->leafbrushes[leaf->firstLeafBrush+k];
|
||||
b = &local->brushes[brushnum];
|
||||
|
||||
// see if the point is in the brush
|
||||
for ( i = 0 ; i < b->numsides ; i++ ) {
|
||||
#ifdef _XBOX
|
||||
d = DotProduct( p,
|
||||
cmg.planes[b->sides[i].planeNum.GetValue()].normal );
|
||||
#else
|
||||
d = DotProduct( p, b->sides[i].plane->normal );
|
||||
#endif
|
||||
// FIXME test for Cash
|
||||
// if ( d >= b->sides[i].plane->dist ) {
|
||||
#ifdef _XBOX
|
||||
if ( d > cmg.planes[b->sides[i].planeNum.GetValue()].dist ) {
|
||||
#else
|
||||
if ( d > b->sides[i].plane->dist ) {
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == b->numsides ) {
|
||||
contents |= b->contents;
|
||||
#ifndef _XBOX // Removing terrain from Xbox
|
||||
if(cmg.landScape && (contents & CONTENTS_TERRAIN) )
|
||||
{
|
||||
if(p[2] < cmg.landScape->GetWaterHeight())
|
||||
{
|
||||
contents |= cmg.landScape->GetWaterContents();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int CM_PointContents( const vec3_t p, clipHandle_t model ) {
|
||||
int leafnum=0;
|
||||
int i, k;
|
||||
int brushnum;
|
||||
cLeaf_t *leaf;
|
||||
cbrush_t *b;
|
||||
int contents;
|
||||
float d;
|
||||
cmodel_t *clipm;
|
||||
|
||||
if (!cmg.numNodes) { // map not loaded
|
||||
return 0;
|
||||
}
|
||||
|
||||
CPoint pt(p[0],p[1],p[2]);
|
||||
if ( model )
|
||||
{
|
||||
clipm = CM_ClipHandleToModel( model );
|
||||
leaf = &clipm->leaf;
|
||||
}
|
||||
else
|
||||
{
|
||||
hlist<pair<CPoint,int> >::iterator l;
|
||||
for(l=pointToContents.begin();l!=pointToContents.end();l++)
|
||||
{
|
||||
if((*l).first==pt)
|
||||
{
|
||||
// Breakout early.
|
||||
return((*l).second);
|
||||
}
|
||||
}
|
||||
|
||||
leafnum=CM_PointLeafnum_r(p, 0);
|
||||
leaf = &cmg.leafs[leafnum];
|
||||
}
|
||||
|
||||
contents = 0;
|
||||
for (k=0 ; k<leaf->numLeafBrushes ; k++)
|
||||
{
|
||||
brushnum = cmg.leafbrushes[leaf->firstLeafBrush+k];
|
||||
b = &cmg.brushes[brushnum];
|
||||
|
||||
// see if the point is in the brush
|
||||
for ( i = 0 ; i < b->numsides ; i++ )
|
||||
{
|
||||
d = DotProduct( p, b->sides[i].plane->normal );
|
||||
// FIXME test for Cash
|
||||
// if ( d >= b->sides[i].plane->dist ) {
|
||||
if ( d > b->sides[i].plane->dist )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == b->numsides )
|
||||
{
|
||||
contents |= b->contents;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result for next time.
|
||||
if(!model)
|
||||
{
|
||||
if(pointToContents.size()>=64)
|
||||
{
|
||||
pointToContents.pop_back();
|
||||
}
|
||||
pointToContents.push_front(pair<CPoint,int>(pt,contents));
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
#endif
|
||||
/*
|
||||
==================
|
||||
CM_TransformedPointContents
|
||||
|
||||
Handles offseting and rotation of the end points for moving and
|
||||
rotating entities
|
||||
==================
|
||||
*/
|
||||
int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) {
|
||||
vec3_t p_l;
|
||||
vec3_t temp;
|
||||
vec3_t forward, right, up;
|
||||
|
||||
// subtract origin offset
|
||||
VectorSubtract (p, origin, p_l);
|
||||
|
||||
// rotate start and end into the models frame of reference
|
||||
if ( model != BOX_MODEL_HANDLE &&
|
||||
(angles[0] || angles[1] || angles[2]) )
|
||||
{
|
||||
AngleVectors (angles, forward, right, up);
|
||||
|
||||
VectorCopy (p_l, temp);
|
||||
p_l[0] = DotProduct (temp, forward);
|
||||
p_l[1] = -DotProduct (temp, right);
|
||||
p_l[2] = DotProduct (temp, up);
|
||||
}
|
||||
|
||||
return CM_PointContents( p_l, model );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
PVS
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
#ifdef _XBOX
|
||||
extern trGlobals_t tr;
|
||||
const byte *CM_ClusterPVS (int cluster) {
|
||||
if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cmg.visibility->Decompress(cluster * cmg.clusterBytes,
|
||||
cmg.numClusters);
|
||||
}
|
||||
#else
|
||||
byte *CM_ClusterPVS (int cluster) {
|
||||
if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) {
|
||||
return cmg.visibility;
|
||||
}
|
||||
|
||||
return cmg.visibility + cluster * cmg.clusterBytes;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
AREAPORTALS
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
#ifdef _XBOX
|
||||
void CM_FloodArea_r( int areaNum, int floodnum) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int *con;
|
||||
|
||||
area = &cmg.areas[ areaNum ];
|
||||
|
||||
if ( area->floodvalid == cmg.floodvalid ) {
|
||||
if (area->floodnum == floodnum)
|
||||
return;
|
||||
Com_Error (ERR_DROP, "FloodArea_r: reflooded");
|
||||
}
|
||||
|
||||
area->floodnum = floodnum;
|
||||
area->floodvalid = cmg.floodvalid;
|
||||
con = cmg.areaPortals + areaNum * cmg.numAreas;
|
||||
for ( i=0 ; i < cmg.numAreas ; i++ ) {
|
||||
if ( con[i] > 0 ) {
|
||||
CM_FloodArea_r( i, floodnum );
|
||||
}
|
||||
}
|
||||
}
|
||||
#else // _XBOX
|
||||
|
||||
void CM_FloodArea_r( int areaNum, int floodnum, clipMap_t &cm) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int *con;
|
||||
|
||||
area = &cmg.areas[ areaNum ];
|
||||
|
||||
if ( area->floodvalid == cmg.floodvalid ) {
|
||||
if (area->floodnum == floodnum)
|
||||
return;
|
||||
Com_Error (ERR_DROP, "FloodArea_r: reflooded");
|
||||
}
|
||||
|
||||
area->floodnum = floodnum;
|
||||
area->floodvalid = cmg.floodvalid;
|
||||
con = cmg.areaPortals + areaNum * cmg.numAreas;
|
||||
for ( i=0 ; i < cmg.numAreas ; i++ ) {
|
||||
if ( con[i] > 0 ) {
|
||||
CM_FloodArea_r( i, floodnum, cm );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // XBOX
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_FloodAreaConnections
|
||||
|
||||
====================
|
||||
*/
|
||||
#ifdef _XBOX
|
||||
void CM_FloodAreaConnections( void ) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int floodnum;
|
||||
|
||||
// all current floods are now invalid
|
||||
cmg.floodvalid++;
|
||||
floodnum = 0;
|
||||
|
||||
for (i = 0 ; i < cmg.numAreas ; i++) {
|
||||
area = &cmg.areas[i];
|
||||
if (area->floodvalid == cmg.floodvalid) {
|
||||
continue; // already flooded into
|
||||
}
|
||||
floodnum++;
|
||||
CM_FloodArea_r (i, floodnum);
|
||||
}
|
||||
|
||||
}
|
||||
#else // _XBOX
|
||||
void CM_FloodAreaConnections( clipMap_t &cm ) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int floodnum;
|
||||
|
||||
// all current floods are now invalid
|
||||
cmg.floodvalid++;
|
||||
floodnum = 0;
|
||||
|
||||
for (i = 0 ; i < cmg.numAreas ; i++) {
|
||||
area = &cmg.areas[i];
|
||||
if (area->floodvalid == cmg.floodvalid) {
|
||||
continue; // already flooded into
|
||||
}
|
||||
floodnum++;
|
||||
CM_FloodArea_r (i, floodnum, cm);
|
||||
}
|
||||
|
||||
}
|
||||
#endif // _XBOX
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_AdjustAreaPortalState
|
||||
|
||||
====================
|
||||
*/
|
||||
void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) {
|
||||
if ( area1 < 0 || area2 < 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( area1 >= cmg.numAreas || area2 >= cmg.numAreas ) {
|
||||
Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number");
|
||||
}
|
||||
|
||||
if ( open ) {
|
||||
cmg.areaPortals[ area1 * cmg.numAreas + area2 ]++;
|
||||
cmg.areaPortals[ area2 * cmg.numAreas + area1 ]++;
|
||||
} else {
|
||||
cmg.areaPortals[ area1 * cmg.numAreas + area2 ]--;
|
||||
cmg.areaPortals[ area2 * cmg.numAreas + area1 ]--;
|
||||
if ( cmg.areaPortals[ area2 * cmg.numAreas + area1 ] < 0 ) {
|
||||
Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _XBOX
|
||||
CM_FloodAreaConnections ();
|
||||
#else
|
||||
CM_FloodAreaConnections (cmg);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_AreasConnected
|
||||
|
||||
====================
|
||||
*/
|
||||
qboolean CM_AreasConnected( int area1, int area2 ) {
|
||||
#ifndef BSPC
|
||||
if ( cm_noAreas->integer ) {
|
||||
return qtrue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( area1 < 0 || area2 < 0 ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (area1 >= cmg.numAreas || area2 >= cmg.numAreas) {
|
||||
Com_Error (ERR_DROP, "area >= cmg.numAreas");
|
||||
}
|
||||
|
||||
if (cmg.areas[area1].floodnum == cmg.areas[area2].floodnum) {
|
||||
return qtrue;
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CM_WriteAreaBits
|
||||
|
||||
Writes a bit vector of all the areas
|
||||
that are in the same flood as the area parameter
|
||||
Returns the number of bytes needed to hold all the bits.
|
||||
|
||||
The bits are OR'd in, so you can CM_WriteAreaBits from multiple
|
||||
viewpoints and get the union of all visible areas.
|
||||
|
||||
This is used to cull non-visible entities from snapshots
|
||||
=================
|
||||
*/
|
||||
int CM_WriteAreaBits (byte *buffer, int area)
|
||||
{
|
||||
int i;
|
||||
int floodnum;
|
||||
int bytes;
|
||||
|
||||
bytes = (cmg.numAreas+7)>>3;
|
||||
|
||||
#ifndef BSPC
|
||||
if (cm_noAreas->integer || area == -1)
|
||||
#else
|
||||
if ( area == -1)
|
||||
#endif
|
||||
{ // for debugging, send everything
|
||||
memset (buffer, 255, bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
floodnum = cmg.areas[area].floodnum;
|
||||
for (i=0 ; i<cmg.numAreas ; i++)
|
||||
{
|
||||
if (cmg.areas[i].floodnum == floodnum || area == -1)
|
||||
buffer[i>>3] |= 1<<(i&7);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void CM_SnapPVS(vec3_t origin,byte *buffer)
|
||||
{
|
||||
int clientarea;
|
||||
int leafnum;
|
||||
int i;
|
||||
|
||||
leafnum = CM_PointLeafnum (origin);
|
||||
clientarea = CM_LeafArea (leafnum);
|
||||
|
||||
// calculate the visible areas
|
||||
memset(buffer,0,MAX_MAP_AREA_BYTES);
|
||||
CM_WriteAreaBits(buffer,clientarea);
|
||||
for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) {
|
||||
((int *)buffer)[i] = ((int *)buffer)[i] ^ -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
1223
code/qcommon/cm_trace.cpp
Normal file
1223
code/qcommon/cm_trace.cpp
Normal file
File diff suppressed because it is too large
Load Diff
722
code/qcommon/cmd.cpp
Normal file
722
code/qcommon/cmd.cpp
Normal file
@@ -0,0 +1,722 @@
|
||||
// cmd.c -- Quake script command processing module
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
#define MAX_CMD_BUFFER 8192
|
||||
int cmd_wait;
|
||||
msg_t cmd_text;
|
||||
byte cmd_text_buf[MAX_CMD_BUFFER];
|
||||
char cmd_defer_text_buf[MAX_CMD_BUFFER];
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Wait_f
|
||||
|
||||
Causes execution of the remainder of the command buffer to be delayed until
|
||||
next frame. This allows commands like:
|
||||
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
|
||||
============
|
||||
*/
|
||||
void Cmd_Wait_f( void ) {
|
||||
if ( Cmd_Argc() == 2 ) {
|
||||
cmd_wait = atoi( Cmd_Argv( 1 ) );
|
||||
} else {
|
||||
cmd_wait = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND BUFFER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_Init
|
||||
============
|
||||
*/
|
||||
void Cbuf_Init (void)
|
||||
{
|
||||
MSG_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_AddText
|
||||
|
||||
Adds command text at the end of the buffer, does NOT add a final \n
|
||||
============
|
||||
*/
|
||||
void Cbuf_AddText( const char *text ) {
|
||||
int l;
|
||||
|
||||
l = strlen (text);
|
||||
|
||||
if (cmd_text.cursize + l >= cmd_text.maxsize)
|
||||
{
|
||||
Com_Printf ("Cbuf_AddText: overflow\n");
|
||||
return;
|
||||
}
|
||||
MSG_WriteData (&cmd_text, text, strlen (text));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_InsertText
|
||||
|
||||
Adds command text immediately after the current command
|
||||
Adds a \n to the text
|
||||
============
|
||||
*/
|
||||
void Cbuf_InsertText( const char *text ) {
|
||||
int len;
|
||||
int i;
|
||||
|
||||
len = strlen( text ) + 1;
|
||||
if ( len + cmd_text.cursize > cmd_text.maxsize ) {
|
||||
Com_Printf( "Cbuf_InsertText overflowed\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
// move the existing command text
|
||||
for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
|
||||
cmd_text.data[ i + len ] = cmd_text.data[ i ];
|
||||
}
|
||||
|
||||
// copy the new text in
|
||||
memcpy( cmd_text.data, text, len - 1 );
|
||||
|
||||
// add a \n
|
||||
cmd_text.data[ len - 1 ] = '\n';
|
||||
|
||||
cmd_text.cursize += len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_ExecuteText
|
||||
============
|
||||
*/
|
||||
void Cbuf_ExecuteText (int exec_when, const char *text)
|
||||
{
|
||||
switch (exec_when)
|
||||
{
|
||||
case EXEC_NOW:
|
||||
Cmd_ExecuteString (text);
|
||||
break;
|
||||
case EXEC_INSERT:
|
||||
Cbuf_InsertText (text);
|
||||
break;
|
||||
case EXEC_APPEND:
|
||||
Cbuf_AddText (text);
|
||||
break;
|
||||
default:
|
||||
Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_Execute
|
||||
============
|
||||
*/
|
||||
void Cbuf_Execute (void)
|
||||
{
|
||||
int i;
|
||||
char *text;
|
||||
char line[MAX_CMD_BUFFER];
|
||||
int quotes;
|
||||
|
||||
while (cmd_text.cursize)
|
||||
{
|
||||
if ( cmd_wait ) {
|
||||
// skip out while text still remains in buffer, leaving it
|
||||
// for next frame
|
||||
cmd_wait--;
|
||||
break;
|
||||
}
|
||||
|
||||
// find a \n or ; line break
|
||||
text = (char *)cmd_text.data;
|
||||
|
||||
quotes = 0;
|
||||
for (i=0 ; i< cmd_text.cursize ; i++)
|
||||
{
|
||||
if (text[i] == '"')
|
||||
quotes++;
|
||||
if ( !(quotes&1) && text[i] == ';')
|
||||
break; // don't break if inside a quoted string
|
||||
if (text[i] == '\n' || text[i] == '\r' )
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
memcpy (line, text, i);
|
||||
line[i] = 0;
|
||||
|
||||
// delete the text from the command buffer and move remaining commands down
|
||||
// this is necessary because commands (exec) can insert data at the
|
||||
// beginning of the text buffer
|
||||
|
||||
if (i == cmd_text.cursize)
|
||||
cmd_text.cursize = 0;
|
||||
else
|
||||
{
|
||||
i++;
|
||||
cmd_text.cursize -= i;
|
||||
memmove (text, text+i, cmd_text.cursize);
|
||||
}
|
||||
|
||||
// execute the command line
|
||||
Cmd_ExecuteString (line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
SCRIPT COMMANDS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Exec_f
|
||||
===============
|
||||
*/
|
||||
void Cmd_Exec_f( void ) {
|
||||
char *f;
|
||||
int len;
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("exec <filename> : execute a script file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
||||
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
|
||||
len = FS_ReadFile( filename, (void **)&f);
|
||||
if (!f) {
|
||||
Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
|
||||
return;
|
||||
}
|
||||
Com_Printf ("execing %s\n",Cmd_Argv(1));
|
||||
|
||||
Cbuf_InsertText (f);
|
||||
|
||||
FS_FreeFile (f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Vstr_f
|
||||
|
||||
Inserts the current value of a variable as command text
|
||||
===============
|
||||
*/
|
||||
void Cmd_Vstr_f( void ) {
|
||||
char *v;
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("vstr <variablename> : execute a variable command\n");
|
||||
return;
|
||||
}
|
||||
|
||||
v = Cvar_VariableString( Cmd_Argv( 1 ) );
|
||||
Cbuf_InsertText( va("%s\n", v ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Echo_f
|
||||
|
||||
Just prints the rest of the line to the console
|
||||
===============
|
||||
*/
|
||||
void Cmd_Echo_f (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=1 ; i<Cmd_Argc() ; i++)
|
||||
Com_Printf ("%s ",Cmd_Argv(i));
|
||||
Com_Printf ("\n");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND EXECUTION
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
#define CMD_MAX_NUM 256
|
||||
#define CMD_MAX_NAME 32
|
||||
typedef struct cmd_function_s
|
||||
{
|
||||
char name[CMD_MAX_NAME];
|
||||
xcommand_t function;
|
||||
} cmd_function_t;
|
||||
|
||||
|
||||
static int cmd_argc;
|
||||
static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
|
||||
static char cmd_tokenized[MAX_STRING_CHARS+MAX_STRING_TOKENS]; // will have 0 bytes inserted
|
||||
|
||||
static cmd_function_t cmd_functions[CMD_MAX_NUM] = {0}; // possible commands to execute
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argc
|
||||
============
|
||||
*/
|
||||
int Cmd_Argc( void ) {
|
||||
return cmd_argc;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argv
|
||||
============
|
||||
*/
|
||||
char *Cmd_Argv( int arg ) {
|
||||
if ( (unsigned)arg >= cmd_argc ) {
|
||||
return "";
|
||||
}
|
||||
return cmd_argv[arg];
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ArgvBuffer
|
||||
|
||||
The interpreted versions use this because
|
||||
they can't have pointers returned to them
|
||||
============
|
||||
*/
|
||||
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
|
||||
Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Args
|
||||
|
||||
Returns a single string containing argv(1) to argv(argc()-1)
|
||||
============
|
||||
*/
|
||||
char *Cmd_Args( void ) {
|
||||
static char cmd_args[MAX_STRING_CHARS];
|
||||
int i;
|
||||
|
||||
cmd_args[0] = 0;
|
||||
for ( i = 1 ; i < cmd_argc ; i++ ) {
|
||||
strcat( cmd_args, cmd_argv[i] );
|
||||
if ( i != cmd_argc ) {
|
||||
strcat( cmd_args, " " );
|
||||
}
|
||||
}
|
||||
|
||||
return cmd_args;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ArgsBuffer
|
||||
|
||||
The interpreted versions use this because
|
||||
they can't have pointers returned to them
|
||||
============
|
||||
*/
|
||||
void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
|
||||
Q_strncpyz( buffer, Cmd_Args(), bufferLength );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_TokenizeString
|
||||
|
||||
Parses the given string into command line tokens.
|
||||
The text is copied to a seperate buffer and 0 characters
|
||||
are inserted in the apropriate place, The argv array
|
||||
will point into this temporary buffer.
|
||||
============
|
||||
*/
|
||||
void Cmd_TokenizeString( const char *text_in ) {
|
||||
const unsigned char *text;
|
||||
char *textOut;
|
||||
|
||||
// clear previous args
|
||||
cmd_argc = 0;
|
||||
|
||||
if ( !text_in ) {
|
||||
return;
|
||||
}
|
||||
|
||||
text = (const unsigned char *)text_in;
|
||||
textOut = cmd_tokenized;
|
||||
|
||||
while ( 1 ) {
|
||||
if ( cmd_argc == MAX_STRING_TOKENS ) {
|
||||
return; // this is usually something malicious
|
||||
}
|
||||
|
||||
while ( 1 ) {
|
||||
// skip whitespace
|
||||
while ( *text && *text <= ' ' ) {
|
||||
text++;
|
||||
}
|
||||
if ( !*text ) {
|
||||
return; // all tokens parsed
|
||||
}
|
||||
|
||||
// skip // comments
|
||||
if ( text[0] == '/' && text[1] == '/' ) {
|
||||
return; // all tokens parsed
|
||||
}
|
||||
|
||||
// skip /* */ comments
|
||||
if ( text[0] == '/' && text[1] =='*' ) {
|
||||
while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
|
||||
text++;
|
||||
}
|
||||
if ( !*text ) {
|
||||
return; // all tokens parsed
|
||||
}
|
||||
text += 2;
|
||||
} else {
|
||||
break; // we are ready to parse a token
|
||||
}
|
||||
}
|
||||
|
||||
// handle quoted strings
|
||||
if ( *text == '"' ) {
|
||||
cmd_argv[cmd_argc] = textOut;
|
||||
cmd_argc++;
|
||||
text++;
|
||||
while ( *text && *text != '"' ) {
|
||||
*textOut++ = *text++;
|
||||
}
|
||||
*textOut++ = 0;
|
||||
if ( !*text ) {
|
||||
return; // all tokens parsed
|
||||
}
|
||||
text++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// regular token
|
||||
cmd_argv[cmd_argc] = textOut;
|
||||
cmd_argc++;
|
||||
|
||||
// skip until whitespace, quote, or command
|
||||
while ( *text > ' ' ) {
|
||||
if ( text[0] == '"' ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( text[0] == '/' && text[1] == '/' ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// skip /* */ comments
|
||||
if ( text[0] == '/' && text[1] =='*' ) {
|
||||
break;
|
||||
}
|
||||
|
||||
*textOut++ = *text++;
|
||||
}
|
||||
|
||||
*textOut++ = 0;
|
||||
|
||||
if ( !*text ) {
|
||||
return; // all tokens parsed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_AddCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
|
||||
cmd_function_t *cmd;
|
||||
cmd_function_t *add = NULL;
|
||||
int c;
|
||||
|
||||
// fail if the command already exists
|
||||
for ( c = 0; c < CMD_MAX_NUM; ++c )
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
// allow completion-only commands to be silently doubled
|
||||
if ( function != NULL ) {
|
||||
Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( add == NULL && cmd->name[0] == '\0')
|
||||
{
|
||||
add = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
if ( add == NULL )
|
||||
{
|
||||
Com_Printf ("Cmd_AddCommand: Too many commands registered\n", cmd_name);
|
||||
return;
|
||||
}
|
||||
|
||||
Q_strncpyz(add->name, cmd_name, CMD_MAX_NAME, qtrue);
|
||||
add->function = function;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_RemoveCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_RemoveCommand( const char *cmd_name ) {
|
||||
cmd_function_t *cmd;
|
||||
|
||||
for ( int c = 0; c < CMD_MAX_NUM; ++c )
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
cmd->name[0] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *Cmd_CompleteCommandNext (char *partial, char *last)
|
||||
{
|
||||
cmd_function_t *cmd, *base;
|
||||
int len, c;
|
||||
|
||||
len = strlen(partial);
|
||||
|
||||
if (!len)
|
||||
return NULL;
|
||||
|
||||
// start past last match
|
||||
base = NULL;
|
||||
if(last)
|
||||
{
|
||||
for (c = 0; c < CMD_MAX_NUM; ++c)
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if(!strcmp(last, cmd->name))
|
||||
{
|
||||
base = cmd + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(base == NULL)
|
||||
{ //not found, either error or at end of list
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base = cmd_functions;
|
||||
}
|
||||
|
||||
|
||||
for (c = base - cmd_functions; c < CMD_MAX_NUM; ++c)
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if (!strcmp (partial,cmd->name))
|
||||
return cmd->name;
|
||||
}
|
||||
|
||||
// check for partial match
|
||||
for (c = base - cmd_functions; c < CMD_MAX_NUM; ++c)
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if (!strncmp (partial,cmd->name, len))
|
||||
return cmd->name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_CompleteCommand
|
||||
============
|
||||
*/
|
||||
char *Cmd_CompleteCommand( const char *partial ) {
|
||||
cmd_function_t *cmd;
|
||||
int len;
|
||||
|
||||
len = strlen(partial);
|
||||
|
||||
if (!len)
|
||||
return NULL;
|
||||
|
||||
// check for exact match
|
||||
for (int c = 0; c < CMD_MAX_NUM; ++c)
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if (!Q_stricmp( partial, cmd->name))
|
||||
return cmd->name;
|
||||
}
|
||||
|
||||
// check for partial match
|
||||
for (int c = 0; c < CMD_MAX_NUM; ++c)
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if (!Q_stricmpn (partial,cmd->name, len))
|
||||
return cmd->name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ExecuteString
|
||||
|
||||
A complete command line has been parsed, so try to execute it
|
||||
============
|
||||
*/
|
||||
extern void Key_SetCatcher( int catcher );
|
||||
extern void Menus_CloseAll(void);
|
||||
|
||||
void Cmd_ExecuteString( const char *text ) {
|
||||
|
||||
// execute the command line
|
||||
Cmd_TokenizeString( text );
|
||||
if ( !Cmd_Argc() ) {
|
||||
return; // no tokens
|
||||
}
|
||||
|
||||
cvar_t* levelSelectCheat = Cvar_Get("levelSelectCheat", "-1", CVAR_SAVEGAME);
|
||||
if( (!strcmp(text,"use end_level") || strstr(text, "maptransition") || !strcmp(text, "uimenu ingameMissionSelect") || !strcmp(text, "uimenu ingameGotoTier")) && // level end
|
||||
levelSelectCheat->integer != -1 ) // was cheating
|
||||
{
|
||||
Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" ); // disconnect the player
|
||||
Key_SetCatcher( KEYCATCH_UI ); // set them in the ui
|
||||
Menus_CloseAll(); // close all the menus
|
||||
Cvar_Set("levelSelectCheat", "-1");
|
||||
return;
|
||||
}
|
||||
|
||||
// check registered command functions
|
||||
for ( int c = 0; c < CMD_MAX_NUM; ++c )
|
||||
{
|
||||
if ( !Q_stricmp( cmd_argv[0],cmd_functions[c].name ) ) {
|
||||
// rearrange the links so that the command will be
|
||||
// near the head of the list next time it is used
|
||||
cmd_function_t temp = cmd_functions[c];
|
||||
cmd_functions[c] = cmd_functions[0];
|
||||
cmd_functions[0] = temp;
|
||||
|
||||
// perform the action
|
||||
if ( !temp.function ) {
|
||||
// let the cgame or game handle it
|
||||
break;
|
||||
} else {
|
||||
temp.function ();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check cvars
|
||||
if ( Cvar_Command() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check client game commands
|
||||
if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check server game commands
|
||||
if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check ui commands
|
||||
if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send it as a server command if we are connected
|
||||
// this will usually result in a chat message
|
||||
CL_ForwardCommandToServer ();
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_List_f
|
||||
============
|
||||
*/
|
||||
void Cmd_List_f (void)
|
||||
{
|
||||
cmd_function_t *cmd;
|
||||
int i;
|
||||
char *match;
|
||||
|
||||
if ( Cmd_Argc() > 1 ) {
|
||||
match = Cmd_Argv( 1 );
|
||||
} else {
|
||||
match = NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for ( int c = 0; c < CMD_MAX_NUM; ++c )
|
||||
{
|
||||
cmd = cmd_functions + c;
|
||||
if (match && !Com_Filter(match, cmd->name, qfalse)) continue;
|
||||
Com_Printf ("%s\n", cmd->name);
|
||||
i++;
|
||||
}
|
||||
Com_Printf ("%i commands\n", i);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Init
|
||||
============
|
||||
*/
|
||||
void Cmd_Init (void)
|
||||
{
|
||||
//
|
||||
// register our commands
|
||||
//
|
||||
Cmd_AddCommand ("cmdlist",Cmd_List_f);
|
||||
Cmd_AddCommand ("exec",Cmd_Exec_f);
|
||||
Cmd_AddCommand ("vstr",Cmd_Vstr_f);
|
||||
Cmd_AddCommand ("echo",Cmd_Echo_f);
|
||||
Cmd_AddCommand ("wait", Cmd_Wait_f);
|
||||
}
|
||||
|
||||
1662
code/qcommon/common.cpp
Normal file
1662
code/qcommon/common.cpp
Normal file
File diff suppressed because it is too large
Load Diff
951
code/qcommon/cvar.cpp
Normal file
951
code/qcommon/cvar.cpp
Normal file
@@ -0,0 +1,951 @@
|
||||
// cvar.c -- dynamic variable tracking
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
cvar_t *cvar_vars;
|
||||
cvar_t *cvar_cheats;
|
||||
int cvar_modifiedFlags;
|
||||
|
||||
#define MAX_CVARS 1024
|
||||
cvar_t cvar_indexes[MAX_CVARS];
|
||||
int cvar_numIndexes;
|
||||
|
||||
|
||||
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force);
|
||||
|
||||
static char *lastMemPool = NULL;
|
||||
static int memPoolSize;
|
||||
|
||||
|
||||
//If the string came from the memory pool, don't really free it. The entire
|
||||
//memory pool will be wiped during the next level load.
|
||||
static void Cvar_FreeString(char *string)
|
||||
{
|
||||
if(!lastMemPool || string < lastMemPool ||
|
||||
string >= lastMemPool + memPoolSize) {
|
||||
Z_Free(string);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_ValidateString
|
||||
============
|
||||
*/
|
||||
static qboolean Cvar_ValidateString( const char *s ) {
|
||||
if ( !s ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, '\\' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, '\"' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, ';' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_FindVar
|
||||
============
|
||||
*/
|
||||
static cvar_t *Cvar_FindVar( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
for (var=cvar_vars ; var ; var=var->next) {
|
||||
if (!Q_stricmp(var_name, var->name)) {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableValue
|
||||
============
|
||||
*/
|
||||
float Cvar_VariableValue( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return 0;
|
||||
return var->value;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableIntegerValue
|
||||
============
|
||||
*/
|
||||
int Cvar_VariableIntegerValue( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return 0;
|
||||
return var->integer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableString
|
||||
============
|
||||
*/
|
||||
char *Cvar_VariableString( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return "";
|
||||
return var->string;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableStringBuffer
|
||||
============
|
||||
*/
|
||||
void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var) {
|
||||
*buffer = 0;
|
||||
}
|
||||
else {
|
||||
Q_strncpyz( buffer, var->string, bufsize );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_CompleteVariable
|
||||
============
|
||||
*/
|
||||
char *Cvar_CompleteVariable( const char *partial ) {
|
||||
cvar_t *cvar;
|
||||
int len;
|
||||
|
||||
len = strlen(partial);
|
||||
|
||||
if ( !len ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// check partial match
|
||||
for ( cvar=cvar_vars ; cvar ; cvar=cvar->next ) {
|
||||
if ( !Q_stricmpn (partial,cvar->name, len) ) {
|
||||
if ( (cvar->flags & CVAR_CHEAT) && !cvar_cheats->integer ) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return cvar->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_CompleteVariableNext - get the next cvar in alphabetical order.
|
||||
|
||||
============
|
||||
*/
|
||||
char *Cvar_CompleteVariableNext (char *partial, char *last)
|
||||
{
|
||||
cvar_t *cvar, *base;
|
||||
int len;
|
||||
|
||||
len = strlen(partial);
|
||||
|
||||
if (!len)
|
||||
return NULL;
|
||||
|
||||
// this check needed since cvars may be resetting from cmd searches
|
||||
base = NULL;
|
||||
if(last)
|
||||
{
|
||||
for (cvar=cvar_vars; cvar; cvar = cvar->next)
|
||||
{
|
||||
if(!Q_stricmp(last, cvar->name))
|
||||
{
|
||||
base = cvar->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(base == NULL)
|
||||
{ //not found, either error or at end of list
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base = cvar_vars;
|
||||
}
|
||||
|
||||
// check partial match
|
||||
for (cvar=base ; cvar ; cvar=cvar->next)
|
||||
{
|
||||
if (!Q_stricmpn (partial,cvar->name, len)) {
|
||||
if ( (cvar->flags & CVAR_CHEAT) && !cvar_cheats->integer ) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return cvar->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Get
|
||||
|
||||
If the variable already exists, the value will not be set unless CVAR_ROM
|
||||
The flags will be or'ed in if the variable exists.
|
||||
============
|
||||
*/
|
||||
cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
|
||||
cvar_t *var;
|
||||
|
||||
if ( !var_name || ! var_value ) {
|
||||
Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" );
|
||||
}
|
||||
|
||||
if ( !Cvar_ValidateString( var_name ) ) {
|
||||
Com_Printf("invalid cvar name string: %s\n", var_name );
|
||||
var_name = "BADNAME";
|
||||
}
|
||||
|
||||
#if 0 // FIXME: values with backslash happen
|
||||
if ( !Cvar_ValidateString( var_value ) ) {
|
||||
Com_Printf("invalid cvar value string: %s\n", var_value );
|
||||
var_value = "BADVALUE";
|
||||
}
|
||||
#endif
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if ( var ) {
|
||||
// if the C code is now specifying a variable that the user already
|
||||
// set a value for, take the new value as the reset value
|
||||
if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED )
|
||||
&& var_value[0] ) {
|
||||
var->flags &= ~CVAR_USER_CREATED;
|
||||
Cvar_FreeString( var->resetString );
|
||||
var->resetString = CopyString( var_value );
|
||||
|
||||
// ZOID--needs to be set so that cvars the game sets as
|
||||
// SERVERINFO get sent to clients
|
||||
cvar_modifiedFlags |= flags;
|
||||
}
|
||||
|
||||
var->flags |= flags;
|
||||
// only allow one non-empty reset string without a warning
|
||||
if ( !var->resetString[0] ) {
|
||||
// we don't have a reset string yet
|
||||
Cvar_FreeString( var->resetString );
|
||||
var->resetString = CopyString( var_value );
|
||||
} else if ( var_value[0] && strcmp( var->resetString, var_value ) ) {
|
||||
Com_Printf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
|
||||
var_name, var->resetString, var_value );
|
||||
}
|
||||
// if we have a latched string, take that value now
|
||||
if ( var->latchedString ) {
|
||||
char *s;
|
||||
|
||||
s = var->latchedString;
|
||||
var->latchedString = NULL; // otherwise cvar_set2 would free it
|
||||
Cvar_Set2( var_name, s, qtrue );
|
||||
Cvar_FreeString( s );
|
||||
}
|
||||
|
||||
// use a CVAR_SET for rom sets, get won't override
|
||||
#if 0
|
||||
// CVAR_ROM always overrides
|
||||
if ( flags & CVAR_ROM ) {
|
||||
Cvar_Set2( var_name, var_value, qtrue );
|
||||
}
|
||||
#endif
|
||||
return var;
|
||||
}
|
||||
|
||||
//
|
||||
// allocate a new cvar
|
||||
//
|
||||
if ( cvar_numIndexes == MAX_CVARS ) {
|
||||
Com_Error( ERR_FATAL, "MAX_CVARS" );
|
||||
}
|
||||
var = &cvar_indexes[cvar_numIndexes];
|
||||
cvar_numIndexes++;
|
||||
var->name = CopyString (var_name);
|
||||
var->string = CopyString (var_value);
|
||||
var->modified = qtrue;
|
||||
var->modificationCount = 1;
|
||||
var->value = atof (var->string);
|
||||
var->integer = atoi(var->string);
|
||||
var->resetString = CopyString( var_value );
|
||||
|
||||
// link the variable in
|
||||
var->next = cvar_vars;
|
||||
cvar_vars = var;
|
||||
|
||||
var->flags = flags;
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set2
|
||||
============
|
||||
*/
|
||||
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) {
|
||||
cvar_t *var;
|
||||
|
||||
Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
|
||||
|
||||
if ( !Cvar_ValidateString( var_name ) ) {
|
||||
Com_Printf("invalid cvar name string: %s\n", var_name );
|
||||
var_name = "BADNAME";
|
||||
}
|
||||
|
||||
#if 0 // FIXME
|
||||
if ( value && !Cvar_ValidateString( value ) ) {
|
||||
Com_Printf("invalid cvar value string: %s\n", value );
|
||||
var_value = "BADVALUE";
|
||||
}
|
||||
#endif
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var) {
|
||||
if ( !value ) {
|
||||
return NULL;
|
||||
}
|
||||
// create it
|
||||
if ( !force ) {
|
||||
return Cvar_Get( var_name, value, CVAR_USER_CREATED );
|
||||
} else {
|
||||
return Cvar_Get (var_name, value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!value ) {
|
||||
value = var->resetString;
|
||||
}
|
||||
|
||||
// note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
|
||||
cvar_modifiedFlags |= var->flags;
|
||||
|
||||
if (!force)
|
||||
{
|
||||
if (var->flags & CVAR_ROM)
|
||||
{
|
||||
Com_Printf ("%s is read only.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
if (var->flags & CVAR_INIT)
|
||||
{
|
||||
Com_Printf ("%s is write protected.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
if (var->flags & CVAR_LATCH)
|
||||
{
|
||||
if (var->latchedString)
|
||||
{
|
||||
if (strcmp(value, var->latchedString) == 0)
|
||||
return var;
|
||||
Cvar_FreeString (var->latchedString);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strcmp(value, var->string) == 0)
|
||||
return var;
|
||||
}
|
||||
|
||||
Com_Printf ("%s will be changed upon restarting.\n", var_name);
|
||||
var->latchedString = CopyString(value);
|
||||
var->modified = qtrue;
|
||||
var->modificationCount++;
|
||||
return var;
|
||||
}
|
||||
|
||||
if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer )
|
||||
{
|
||||
Com_Printf ("%s is cheat protected.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (var->latchedString)
|
||||
{
|
||||
Cvar_FreeString (var->latchedString);
|
||||
var->latchedString = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(value, var->string))
|
||||
return var; // not changed
|
||||
|
||||
var->modified = qtrue;
|
||||
var->modificationCount++;
|
||||
|
||||
Cvar_FreeString (var->string); // free the old value string
|
||||
|
||||
var->string = CopyString(value);
|
||||
var->value = atof (var->string);
|
||||
var->integer = atoi (var->string);
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set
|
||||
============
|
||||
*/
|
||||
void Cvar_Set( const char *var_name, const char *value) {
|
||||
Cvar_Set2 (var_name, value, qtrue);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetValue
|
||||
============
|
||||
*/
|
||||
void Cvar_SetValue( const char *var_name, float value) {
|
||||
char val[32];
|
||||
|
||||
if ( value == (int)value ) {
|
||||
Com_sprintf (val, sizeof(val), "%i",(int)value);
|
||||
} else {
|
||||
Com_sprintf (val, sizeof(val), "%f",value);
|
||||
}
|
||||
Cvar_Set (var_name, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Reset
|
||||
============
|
||||
*/
|
||||
void Cvar_Reset( const char *var_name ) {
|
||||
Cvar_Set2( var_name, NULL, qfalse );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetCheatState
|
||||
|
||||
Any testing variables will be reset to the safe values
|
||||
============
|
||||
*/
|
||||
void Cvar_SetCheatState( void ) {
|
||||
cvar_t *var;
|
||||
|
||||
// set all default vars to the safe value
|
||||
for ( var = cvar_vars ; var ; var = var->next ) {
|
||||
if ( var->flags & CVAR_CHEAT) {
|
||||
Cvar_Set( var->name, var->resetString );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Command
|
||||
|
||||
Handles variable inspection and changing from the console
|
||||
============
|
||||
*/
|
||||
qboolean Cvar_Command( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
// check variables
|
||||
v = Cvar_FindVar (Cmd_Argv(0));
|
||||
if (!v) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// perform a variable print or set
|
||||
if ( Cmd_Argc() == 1 ) {
|
||||
Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString );
|
||||
if ( v->latchedString ) {
|
||||
Com_Printf( "latched: \"%s\"\n", v->latchedString );
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
//JFM toggle test
|
||||
char *value;
|
||||
value = Cmd_Argv(1);
|
||||
if (value[0] =='!') //toggle
|
||||
{
|
||||
char buff[5];
|
||||
sprintf(buff,"%i",!v->value);
|
||||
Cvar_Set2 (v->name, buff, qfalse);// toggle the value
|
||||
}
|
||||
else
|
||||
Cvar_Set2 (v->name, value, qfalse);// set the value if forcing isn't required
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Toggle_f
|
||||
|
||||
Toggles a cvar for easy single key binding
|
||||
============
|
||||
*/
|
||||
void Cvar_Toggle_f( void ) {
|
||||
int v;
|
||||
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf ("usage: toggle <variable>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
v = Cvar_VariableIntegerValue( Cmd_Argv( 1 ) );
|
||||
v = !v;
|
||||
|
||||
Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set_f
|
||||
|
||||
Allows setting and defining of arbitrary cvars from console, even if they
|
||||
weren't declared in C code.
|
||||
============
|
||||
*/
|
||||
void Cvar_Set_f( void ) {
|
||||
int i, c, l, len;
|
||||
char combined[MAX_STRING_TOKENS];
|
||||
|
||||
c = Cmd_Argc();
|
||||
if ( c < 3 ) {
|
||||
Com_Printf ("usage: set <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
combined[0] = 0;
|
||||
l = 0;
|
||||
for ( i = 2 ; i < c ; i++ ) {
|
||||
len = strlen ( Cmd_Argv( i ) + 1 );
|
||||
if ( l + len >= MAX_STRING_TOKENS - 2 ) {
|
||||
break;
|
||||
}
|
||||
strcat( combined, Cmd_Argv( i ) );
|
||||
if ( i != c-1 ) {
|
||||
strcat( combined, " " );
|
||||
}
|
||||
l += len;
|
||||
}
|
||||
Cvar_Set2 (Cmd_Argv(1), combined, qfalse);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetU_f
|
||||
|
||||
As Cvar_Set, but also flags it as userinfo
|
||||
============
|
||||
*/
|
||||
void Cvar_SetU_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: setu <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_USERINFO;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetS_f
|
||||
|
||||
As Cvar_Set, but also flags it as userinfo
|
||||
============
|
||||
*/
|
||||
void Cvar_SetS_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: sets <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_SERVERINFO;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetA_f
|
||||
|
||||
As Cvar_Set, but also flags it as archived
|
||||
============
|
||||
*/
|
||||
void Cvar_SetA_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: seta <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_ARCHIVE;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Reset_f
|
||||
============
|
||||
*/
|
||||
void Cvar_Reset_f( void ) {
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf ("usage: reset <variable>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Reset( Cmd_Argv( 1 ) );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_WriteVariables
|
||||
|
||||
Appends lines containing "set variable value" for all variables
|
||||
with the archive flag set to qtrue.
|
||||
============
|
||||
*/
|
||||
void Cvar_WriteVariables( fileHandle_t f ) {
|
||||
#ifndef _XBOX
|
||||
cvar_t *var;
|
||||
char buffer[1024];
|
||||
|
||||
for (var = cvar_vars ; var ; var = var->next) {
|
||||
if (var->flags & CVAR_ARCHIVE ) {
|
||||
// write the latched value, even if it hasn't taken effect yet
|
||||
if ( var->latchedString ) {
|
||||
Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString);
|
||||
} else {
|
||||
Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string);
|
||||
}
|
||||
FS_Printf (f, "%s", buffer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_List_f
|
||||
|
||||
============
|
||||
*/
|
||||
void Cvar_List_f( void ) {
|
||||
cvar_t *var;
|
||||
int i;
|
||||
char *match;
|
||||
|
||||
if ( Cmd_Argc() > 1 ) {
|
||||
match = Cmd_Argv( 1 );
|
||||
} else {
|
||||
match = NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (var = cvar_vars ; var ; var = var->next, i++)
|
||||
{
|
||||
if (match && !Com_Filter(match, var->name, qfalse)) continue;
|
||||
|
||||
if (var->flags & CVAR_SERVERINFO) {
|
||||
Com_Printf("S");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_USERINFO) {
|
||||
Com_Printf("U");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_ROM) {
|
||||
Com_Printf("R");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_INIT) {
|
||||
Com_Printf("I");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_ARCHIVE) {
|
||||
Com_Printf("A");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_LATCH) {
|
||||
Com_Printf("L");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_CHEAT) {
|
||||
if (!cvar_cheats->integer)
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
Com_Printf("C");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
|
||||
Com_Printf (" %s \"%s\"\n", var->name, var->string);
|
||||
}
|
||||
|
||||
Com_Printf ("\n%i total cvars\n", i);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Restart_f
|
||||
|
||||
Resets all cvars to their hardcoded values
|
||||
============
|
||||
*/
|
||||
void Cvar_Restart_f( void ) {
|
||||
cvar_t *var;
|
||||
cvar_t **prev;
|
||||
|
||||
prev = &cvar_vars;
|
||||
while ( 1 ) {
|
||||
var = *prev;
|
||||
if ( !var ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// don't mess with rom values, or some inter-module
|
||||
// communication will get broken (com_cl_running, etc)
|
||||
if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) {
|
||||
prev = &var->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// throw out any variables the user created
|
||||
if ( var->flags & CVAR_USER_CREATED ) {
|
||||
*prev = var->next;
|
||||
if ( var->name ) {
|
||||
Cvar_FreeString( var->name );
|
||||
}
|
||||
if ( var->string ) {
|
||||
Cvar_FreeString( var->string );
|
||||
}
|
||||
if ( var->latchedString ) {
|
||||
Cvar_FreeString( var->latchedString );
|
||||
}
|
||||
if ( var->resetString ) {
|
||||
Cvar_FreeString( var->resetString );
|
||||
}
|
||||
// clear the var completely, since we
|
||||
// can't remove the index from the list
|
||||
memset( var, 0, sizeof( var ) );
|
||||
continue;
|
||||
}
|
||||
Cvar_Set( var->name, var->resetString );
|
||||
prev = &var->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_InfoString
|
||||
=====================
|
||||
*/
|
||||
char *Cvar_InfoString( int bit ) {
|
||||
static char info[MAX_INFO_STRING];
|
||||
cvar_t *var;
|
||||
|
||||
info[0] = 0;
|
||||
|
||||
for (var = cvar_vars ; var ; var = var->next) {
|
||||
if (var->flags & bit) {
|
||||
Info_SetValueForKey (info, var->name, var->string);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_InfoStringBuffer
|
||||
=====================
|
||||
*/
|
||||
void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) {
|
||||
Q_strncpyz(buff,Cvar_InfoString(bit),buffsize);
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_Register
|
||||
|
||||
basically a slightly modified Cvar_Get for the interpreted modules
|
||||
=====================
|
||||
*/
|
||||
void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) {
|
||||
cvar_t *cv;
|
||||
|
||||
cv = Cvar_Get( varName, defaultValue, flags );
|
||||
if ( !vmCvar ) {
|
||||
return;
|
||||
}
|
||||
vmCvar->handle = cv - cvar_indexes;
|
||||
vmCvar->modificationCount = -1;
|
||||
Cvar_Update( vmCvar );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_Register
|
||||
|
||||
updates an interpreted modules' version of a cvar
|
||||
=====================
|
||||
*/
|
||||
void Cvar_Update( vmCvar_t *vmCvar ) {
|
||||
cvar_t *cv;
|
||||
|
||||
if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) {
|
||||
Com_Error( ERR_DROP, "Cvar_Update: handle out of range" );
|
||||
}
|
||||
|
||||
cv = cvar_indexes + vmCvar->handle;
|
||||
|
||||
if ( cv->modificationCount == vmCvar->modificationCount ) {
|
||||
return;
|
||||
}
|
||||
if ( !cv->string ) {
|
||||
return; // variable might have been cleared by a cvar_restart
|
||||
}
|
||||
vmCvar->modificationCount = cv->modificationCount;
|
||||
Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) );
|
||||
vmCvar->value = cv->value;
|
||||
vmCvar->integer = cv->integer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Init
|
||||
|
||||
Reads in all archived cvars
|
||||
============
|
||||
*/
|
||||
void Cvar_Init (void) {
|
||||
cvar_cheats = Cvar_Get("helpUsObi", "0", CVAR_SYSTEMINFO );
|
||||
|
||||
Cmd_AddCommand ("toggle", Cvar_Toggle_f);
|
||||
Cmd_AddCommand ("set", Cvar_Set_f);
|
||||
Cmd_AddCommand ("sets", Cvar_SetS_f);
|
||||
Cmd_AddCommand ("setu", Cvar_SetU_f);
|
||||
Cmd_AddCommand ("seta", Cvar_SetA_f);
|
||||
Cmd_AddCommand ("reset", Cvar_Reset_f);
|
||||
Cmd_AddCommand ("cvarlist", Cvar_List_f);
|
||||
Cmd_AddCommand ("cvar_restart", Cvar_Restart_f);
|
||||
}
|
||||
|
||||
|
||||
static void Cvar_Realloc(char **string, char *memPool, int &memPoolUsed)
|
||||
{
|
||||
if(string && *string)
|
||||
{
|
||||
char *temp = memPool + memPoolUsed;
|
||||
strcpy(temp, *string);
|
||||
memPoolUsed += strlen(*string) + 1;
|
||||
Cvar_FreeString(*string);
|
||||
*string = temp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Turns many small allocation blocks into one big one.
|
||||
void Cvar_Defrag(void)
|
||||
{
|
||||
cvar_t *var;
|
||||
int totalMem = 0;
|
||||
int nextMemPoolSize;
|
||||
|
||||
for (var = cvar_vars; var; var = var->next)
|
||||
{
|
||||
if (var->name) {
|
||||
totalMem += strlen(var->name) + 1;
|
||||
}
|
||||
if (var->string) {
|
||||
totalMem += strlen(var->string) + 1;
|
||||
}
|
||||
if (var->resetString) {
|
||||
totalMem += strlen(var->resetString) + 1;
|
||||
}
|
||||
if (var->latchedString) {
|
||||
totalMem += strlen(var->latchedString) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
char *mem = (char*)Z_Malloc(totalMem, TAG_SMALL, qfalse);
|
||||
nextMemPoolSize = totalMem;
|
||||
totalMem = 0;
|
||||
|
||||
for (var = cvar_vars; var; var = var->next)
|
||||
{
|
||||
Cvar_Realloc(&var->name, mem, totalMem);
|
||||
Cvar_Realloc(&var->string, mem, totalMem);
|
||||
Cvar_Realloc(&var->resetString, mem, totalMem);
|
||||
Cvar_Realloc(&var->latchedString, mem, totalMem);
|
||||
}
|
||||
|
||||
if(lastMemPool) {
|
||||
Z_Free(lastMemPool);
|
||||
}
|
||||
lastMemPool = mem;
|
||||
memPoolSize = nextMemPoolSize;
|
||||
}
|
||||
|
||||
119
code/qcommon/files.h
Normal file
119
code/qcommon/files.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#ifndef __FILES_H
|
||||
#define __FILES_H
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Structures local to the files_* modules.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifdef _XBOX
|
||||
#include "../goblib/goblib.h"
|
||||
|
||||
typedef int wfhandle_t;
|
||||
#else
|
||||
#include "../zlib32/zip.h"
|
||||
#include "unzip.h"
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_ZPATH 256
|
||||
#define BASEGAME "base"
|
||||
|
||||
|
||||
|
||||
typedef struct fileInPack_s {
|
||||
char *name; // name of the file
|
||||
unsigned long pos; // file info position in zip
|
||||
struct fileInPack_s* next; // next file in the hash
|
||||
} fileInPack_t;
|
||||
|
||||
typedef struct {
|
||||
char pakFilename[MAX_OSPATH]; // c:\quake3\base\asset0.pk3
|
||||
#ifndef _XBOX
|
||||
unzFile handle;
|
||||
#endif
|
||||
int checksum;
|
||||
int numfiles;
|
||||
int hashSize; // hash table size (power of 2)
|
||||
fileInPack_t* *hashTable; // hash table
|
||||
fileInPack_t* buildBuffer; // buffer with the filenames etc.
|
||||
} pack_t;
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_OSPATH]; // c:\stvoy
|
||||
char gamedir[MAX_OSPATH]; // base
|
||||
} directory_t;
|
||||
|
||||
typedef struct searchpath_s {
|
||||
struct searchpath_s *next;
|
||||
|
||||
pack_t *pack; // only one of pack / dir will be non NULL
|
||||
directory_t *dir;
|
||||
} searchpath_t;
|
||||
|
||||
|
||||
#define MAX_FILE_HANDLES 16
|
||||
|
||||
typedef union qfile_gus {
|
||||
FILE* o;
|
||||
#ifndef _XBOX
|
||||
unzFile z;
|
||||
#endif
|
||||
} qfile_gut;
|
||||
|
||||
typedef struct qfile_us {
|
||||
qfile_gut file;
|
||||
qboolean unique;
|
||||
} qfile_ut;
|
||||
|
||||
typedef struct {
|
||||
qfile_ut handleFiles;
|
||||
qboolean handleSync;
|
||||
int baseOffset;
|
||||
int fileSize;
|
||||
int zipFilePos;
|
||||
qboolean zipFile;
|
||||
char name[MAX_QPATH];
|
||||
|
||||
#ifdef _XBOX
|
||||
GOBHandle ghandle;
|
||||
qboolean gob;
|
||||
qboolean used;
|
||||
wfhandle_t whandle;
|
||||
#endif
|
||||
} fileHandleData_t;
|
||||
|
||||
|
||||
extern fileHandleData_t fsh[MAX_FILE_HANDLES];
|
||||
|
||||
extern searchpath_t *fs_searchpaths;
|
||||
extern char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
|
||||
extern cvar_t *fs_debug;
|
||||
extern cvar_t *fs_basepath;
|
||||
extern cvar_t *fs_cdpath;
|
||||
extern cvar_t *fs_copyfiles;
|
||||
extern cvar_t *fs_gamedirvar;
|
||||
extern cvar_t *fs_restrict;
|
||||
extern int fs_readCount; // total bytes read
|
||||
extern int fs_loadCount; // total files read
|
||||
extern int fs_packFiles; // total number of files in packs
|
||||
|
||||
|
||||
void FS_Startup( const char *gameName );
|
||||
void FS_CreatePath(char *OSPath);
|
||||
char *FS_BuildOSPath( const char *base, const char *game, const char *qpath );
|
||||
char *FS_BuildOSPath( const char *qpath );
|
||||
char *FS_BuildOSPathUnMapped( const char *qpath );
|
||||
fileHandle_t FS_HandleForFile(void);
|
||||
qboolean FS_FilenameCompare( const char *s1, const char *s2 );
|
||||
int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp );
|
||||
void FS_Shutdown( void );
|
||||
void FS_SetRestrictions(void);
|
||||
void FS_CheckInit(void);
|
||||
void FS_ReplaceSeparators( char *path );
|
||||
|
||||
|
||||
#endif
|
||||
605
code/qcommon/files_common.cpp
Normal file
605
code/qcommon/files_common.cpp
Normal file
@@ -0,0 +1,605 @@
|
||||
|
||||
/*****************************************************************************
|
||||
* name: files.c
|
||||
*
|
||||
* desc: handle based filesystem for Quake III Arena
|
||||
*
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
#include "files.h"
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
QUAKE3 FILESYSTEM
|
||||
|
||||
All of Quake's data access is through a hierarchical file system, but the contents of
|
||||
the file system can be transparently merged from several sources.
|
||||
|
||||
A "qpath" is a reference to game file data. MAX_QPATH is 64 characters, which must include
|
||||
a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
|
||||
references outside the quake directory system.
|
||||
|
||||
The "base path" is the path to the directory holding all the game directories and usually
|
||||
the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
|
||||
command line to allow code debugging in a different directory. Basepath cannot
|
||||
be modified at all after startup. Any files that are created (demos, screenshots,
|
||||
etc) will be created relative to the base path, so base path should usually be writable.
|
||||
|
||||
The "cd path" is the path to an alternate hierarchy that will be searched if a file
|
||||
is not located in the base path. A user can do a partial install that copies some
|
||||
data to a base path created on their hard drive and leave the rest on the cd. Files
|
||||
are never writen to the cd path. It defaults to a value set by the installer, like
|
||||
"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3".
|
||||
|
||||
If a user runs the game directly from a CD, the base path would be on the CD. This
|
||||
should still function correctly, but all file writes will fail (harmlessly).
|
||||
|
||||
|
||||
The "base game" is the directory under the paths where data comes from by default, and
|
||||
can be either "base" or "demo".
|
||||
|
||||
The "current game" may be the same as the base game, or it may be the name of another
|
||||
directory under the paths that should be searched for files before looking in the base game.
|
||||
This is the basis for addons.
|
||||
|
||||
Clients automatically set the game directory after receiving a gamestate from a server,
|
||||
so only servers need to worry about +set fs_game.
|
||||
|
||||
No other directories outside of the base game and current game will ever be referenced by
|
||||
filesystem functions.
|
||||
|
||||
To save disk space and speed loading, directory trees can be collapsed into zip files.
|
||||
The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
|
||||
otherwise the are simply normal uncompressed zip files. A game directory can have multiple
|
||||
zip files of the form "asset0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
|
||||
from the highest number to the lowest, and will always take precedence over the filesystem.
|
||||
This allows a pk3 distributed as a patch to override all existing data.
|
||||
|
||||
Because we will have updated executables freely available online, there is no point to
|
||||
trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
|
||||
should be exactly the same executables as release versions, but with different data that
|
||||
automatically restricts where game media can come from to prevent add-ons from working.
|
||||
|
||||
After the paths are initialized, quake will look for the product.txt file. If not
|
||||
found and verified, the game will run in restricted mode. In restricted mode, only
|
||||
files contained in demo/asset0.pk3 will be available for loading, and only if the zip header is
|
||||
verified to not have been modified. A single exception is made for jaconfig.cfg. Files
|
||||
can still be written out in restricted mode, so screenshots and demos are allowed.
|
||||
Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
|
||||
if there is a valid product.txt under the basepath or cdpath.
|
||||
|
||||
If not running in restricted mode, and a file is not found in any local filesystem,
|
||||
an attempt will be made to download it and save it under the base path.
|
||||
|
||||
If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
|
||||
path, it will be copied over to the base path. This is a development aid to help build
|
||||
test releases and to copy working sets over slow network links.
|
||||
(If set to 2, copying will only take place if the two filetimes are NOT EQUAL)
|
||||
|
||||
|
||||
The qpath "sound/newstuff/test.wav" would be searched for in the following places:
|
||||
|
||||
base path + current game's zip files
|
||||
base path + current game's directory
|
||||
cd path + current game's zip files
|
||||
cd path + current game's directory
|
||||
base path + base game's zip files
|
||||
base path + base game's directory
|
||||
cd path + base game's zip files
|
||||
cd path + base game's directory
|
||||
server download, to be written to base path + current game's directory
|
||||
|
||||
|
||||
The filesystem can be safely shutdown and reinitialized with different
|
||||
basedir / cddir / game combinations, but all other subsystems that rely on it
|
||||
(sound, video) must also be forced to restart.
|
||||
|
||||
Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
|
||||
subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
|
||||
load the file with a request to cache. Only one file will be kept cached at a time,
|
||||
so any models that are going to be referenced by both subsystems should alternate
|
||||
between the CM_ load function and the ref load function.
|
||||
|
||||
|
||||
|
||||
|
||||
TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
|
||||
game is currently active. This allows character models, skins, and sounds to be downloaded
|
||||
to a common directory no matter which game is active.
|
||||
|
||||
|
||||
How to prevent downloading zip files?
|
||||
Pass pk3 file names in systeminfo, and download before FS_Restart()?
|
||||
|
||||
|
||||
|
||||
Aborting a download disconnects the client from the server.
|
||||
|
||||
How to mark files as downloadable? Commercial add-ons won't be downloadable.
|
||||
|
||||
Non-commercial downloads will want to download the entire zip file.
|
||||
the game would have to be reset to actually read the zip in
|
||||
|
||||
Auto-update information
|
||||
|
||||
Path separators
|
||||
|
||||
Casing
|
||||
|
||||
separate server gamedir and client gamedir, so if the user starts
|
||||
a local game after having connected to a network game, it won't stick
|
||||
with the network game.
|
||||
|
||||
allow menu options for game selection?
|
||||
|
||||
Read / write config to floppy option.
|
||||
|
||||
Different version coexistance?
|
||||
|
||||
When building a pak file, make sure a jaconfig.cfg isn't present in it,
|
||||
or configs will never get loaded from disk!
|
||||
|
||||
todo:
|
||||
|
||||
downloading (outside fs?)
|
||||
game directory passing and restarting
|
||||
|
||||
=============================================================================
|
||||
|
||||
*/
|
||||
|
||||
// if this is defined, the executable positively won't work with any paks other
|
||||
// than the demo pak, even if productid is present. This is only used for our
|
||||
// last demo release to prevent the mac and linux users from using the demo
|
||||
// executable with the production windows pak before the mac/linux products
|
||||
// hit the shelves a little later
|
||||
//#define PRE_RELEASE_DEMO
|
||||
|
||||
|
||||
char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
|
||||
cvar_t *fs_debug;
|
||||
cvar_t *fs_basepath;
|
||||
cvar_t *fs_cdpath;
|
||||
cvar_t *fs_copyfiles;
|
||||
cvar_t *fs_gamedirvar;
|
||||
cvar_t *fs_restrict;
|
||||
searchpath_t *fs_searchpaths;
|
||||
int fs_readCount; // total bytes read
|
||||
int fs_loadCount; // total files read
|
||||
int fs_packFiles; // total number of files in packs
|
||||
|
||||
qboolean initialized = qfalse;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fileHandleData_t fsh[MAX_FILE_HANDLES];
|
||||
|
||||
void FS_CheckInit(void)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
FS_Initialized
|
||||
==============
|
||||
*/
|
||||
|
||||
qboolean FS_Initialized() {
|
||||
return (qboolean)(fs_searchpaths != NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fileHandle_t FS_HandleForFile(void) {
|
||||
int i;
|
||||
|
||||
for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
|
||||
#ifdef _XBOX
|
||||
if ( !fsh[i].used ) {
|
||||
#else
|
||||
if ( fsh[i].handleFiles.file.o == NULL ) {
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
Com_Printf( "FS_HandleForFile: all handles taken:\n" );
|
||||
for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
|
||||
Com_Printf( "%d. %s\n", i, fsh[i].name);
|
||||
}
|
||||
Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
FS_ReplaceSeparators
|
||||
|
||||
Fix things up differently for win/unix/mac
|
||||
====================
|
||||
*/
|
||||
void FS_ReplaceSeparators( char *path ) {
|
||||
char *s;
|
||||
|
||||
for ( s = path ; *s ; s++ ) {
|
||||
if ( *s == '/' || *s == '\\' ) {
|
||||
*s = PATH_SEP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
FS_BuildOSPath
|
||||
|
||||
Qpath may have either forward or backwards slashes
|
||||
===================
|
||||
*/
|
||||
|
||||
char *FS_BuildOSPath( const char *qpath )
|
||||
{
|
||||
char temp[MAX_OSPATH];
|
||||
static char ospath[2][MAX_OSPATH];
|
||||
static int toggle;
|
||||
|
||||
toggle ^= 1; // flip-flop to allow two returns without clash
|
||||
|
||||
Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath );
|
||||
|
||||
FS_ReplaceSeparators( temp );
|
||||
Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s",
|
||||
fs_basepath->string, temp );
|
||||
|
||||
return ospath[toggle];
|
||||
}
|
||||
|
||||
char *FS_BuildOSPathUnMapped( const char *qpath )
|
||||
{
|
||||
char temp[MAX_OSPATH];
|
||||
static char ospath[2][MAX_OSPATH];
|
||||
static int toggle;
|
||||
|
||||
toggle ^= 1; // flip-flop to allow two returns without clash
|
||||
|
||||
Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath );
|
||||
|
||||
FS_ReplaceSeparators( temp );
|
||||
Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s",
|
||||
"d:", temp );
|
||||
|
||||
return ospath[toggle];
|
||||
}
|
||||
|
||||
#ifndef _XBOX
|
||||
char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
|
||||
char temp[MAX_OSPATH];
|
||||
static char ospath[4][MAX_OSPATH];
|
||||
static int toggle;
|
||||
|
||||
toggle = (++toggle)&3; // allows four returns without clash (increased from 2 during fs_copyfiles 2 enhancement)
|
||||
|
||||
Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
|
||||
FS_ReplaceSeparators( temp );
|
||||
Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
|
||||
|
||||
return ospath[toggle];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
FS_CreatePath
|
||||
|
||||
Creates any directories needed to store the given filename
|
||||
============
|
||||
*/
|
||||
void FS_CreatePath (char *OSPath) {
|
||||
char *ofs;
|
||||
|
||||
// make absolutely sure that it can't back up the path
|
||||
// FIXME: is c: allowed???
|
||||
if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
|
||||
Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
|
||||
return;
|
||||
}
|
||||
|
||||
strlwr(OSPath);
|
||||
|
||||
for (ofs = OSPath+1 ; *ofs ; ofs++) {
|
||||
if (*ofs == PATH_SEP) {
|
||||
// create the directory
|
||||
*ofs = 0;
|
||||
Sys_Mkdir (OSPath);
|
||||
*ofs = PATH_SEP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
FS_SV_FOpenFileRead
|
||||
|
||||
===========
|
||||
*/
|
||||
int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
|
||||
char *ospath;
|
||||
fileHandle_t f;
|
||||
|
||||
if ( !fs_searchpaths ) {
|
||||
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||||
}
|
||||
|
||||
f = FS_HandleForFile();
|
||||
fsh[f].zipFile = qfalse;
|
||||
|
||||
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
|
||||
|
||||
// don't let sound stutter
|
||||
S_ClearSoundBuffer();
|
||||
|
||||
#ifdef _XBOX
|
||||
ospath = FS_BuildOSPath( filename );
|
||||
#else
|
||||
ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
|
||||
#endif
|
||||
// remove trailing slash
|
||||
ospath[strlen(ospath)-1] = '\0';
|
||||
|
||||
if ( fs_debug->integer ) {
|
||||
Com_Printf( "FS_SV_FOpenFileRead: %s\n", ospath );
|
||||
}
|
||||
|
||||
fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
|
||||
fsh[f].handleSync = qfalse;
|
||||
if (!fsh[f].handleFiles.file.o) {
|
||||
f = 0;
|
||||
}
|
||||
|
||||
*fp = f;
|
||||
if (f) {
|
||||
return FS_filelength(f);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
FS_FOpenFileAppend
|
||||
|
||||
===========
|
||||
*/
|
||||
fileHandle_t FS_FOpenFileAppend( const char *filename ) {
|
||||
char *ospath;
|
||||
fileHandle_t f;
|
||||
|
||||
if ( !fs_searchpaths ) {
|
||||
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||||
}
|
||||
|
||||
f = FS_HandleForFile();
|
||||
fsh[f].zipFile = qfalse;
|
||||
|
||||
Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
|
||||
|
||||
// don't let sound stutter
|
||||
S_ClearSoundBuffer();
|
||||
|
||||
#ifdef _XBOX
|
||||
ospath = FS_BuildOSPath( filename );
|
||||
#else
|
||||
ospath = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename );
|
||||
#endif
|
||||
|
||||
if ( fs_debug->integer ) {
|
||||
Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
|
||||
}
|
||||
|
||||
FS_CreatePath( ospath );
|
||||
fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
|
||||
fsh[f].handleSync = qfalse;
|
||||
if (!fsh[f].handleFiles.file.o) {
|
||||
f = 0;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===========
|
||||
FS_FilenameCompare
|
||||
|
||||
Ignore case and seprator char distinctions
|
||||
===========
|
||||
*/
|
||||
qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
|
||||
int c1, c2;
|
||||
|
||||
do {
|
||||
c1 = *s1++;
|
||||
c2 = *s2++;
|
||||
|
||||
if ( Q_islower(c1) ) {
|
||||
c1 -= ('a' - 'A');
|
||||
}
|
||||
if ( Q_islower(c2) ) {
|
||||
c2 -= ('a' - 'A');
|
||||
}
|
||||
|
||||
if ( c1 == '\\' || c1 == ':' ) {
|
||||
c1 = '/';
|
||||
}
|
||||
if ( c2 == '\\' || c2 == ':' ) {
|
||||
c2 = '/';
|
||||
}
|
||||
|
||||
if (c1 != c2) {
|
||||
return -1; // strings not equal
|
||||
}
|
||||
} while (c1);
|
||||
|
||||
return 0; // strings are equal
|
||||
}
|
||||
|
||||
|
||||
#define MAXPRINTMSG 4096
|
||||
void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
|
||||
va_list argptr;
|
||||
char msg[MAXPRINTMSG];
|
||||
|
||||
va_start (argptr,fmt);
|
||||
vsprintf (msg,fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
FS_Write(msg, strlen(msg), h);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
FS_WriteFile
|
||||
|
||||
Filename are relative to the quake search path
|
||||
============
|
||||
*/
|
||||
void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
|
||||
fileHandle_t f;
|
||||
|
||||
if ( !fs_searchpaths ) {
|
||||
Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
|
||||
}
|
||||
|
||||
if ( !qpath || !buffer ) {
|
||||
Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
|
||||
}
|
||||
|
||||
f = FS_FOpenFileWrite( qpath );
|
||||
if ( !f ) {
|
||||
Com_Printf( "Failed to open %s\n", qpath );
|
||||
return;
|
||||
}
|
||||
|
||||
FS_Write( buffer, size, f );
|
||||
|
||||
FS_FCloseFile( f );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
FS_Shutdown
|
||||
|
||||
Frees all resources and closes all files
|
||||
================
|
||||
*/
|
||||
void FS_Shutdown( void ) {
|
||||
searchpath_t *p, *next;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < MAX_FILE_HANDLES; i++) {
|
||||
if (fsh[i].fileSize) {
|
||||
FS_FCloseFile(i);
|
||||
}
|
||||
}
|
||||
|
||||
// free everything
|
||||
for ( p = fs_searchpaths ; p ; p = next ) {
|
||||
next = p->next;
|
||||
|
||||
if ( p->pack ) {
|
||||
#ifndef _XBOX
|
||||
unzClose(p->pack->handle);
|
||||
#endif
|
||||
Z_Free( p->pack->buildBuffer );
|
||||
Z_Free( p->pack );
|
||||
}
|
||||
if ( p->dir ) {
|
||||
Z_Free( p->dir );
|
||||
}
|
||||
Z_Free( p );
|
||||
}
|
||||
|
||||
// any FS_ calls will now be an error until reinitialized
|
||||
fs_searchpaths = NULL;
|
||||
|
||||
Cmd_RemoveCommand( "path" );
|
||||
Cmd_RemoveCommand( "dir" );
|
||||
Cmd_RemoveCommand( "touchFile" );
|
||||
|
||||
initialized = qfalse;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
================
|
||||
FS_InitFilesystem
|
||||
|
||||
Called only at inital startup, not when the filesystem
|
||||
is resetting due to a game change
|
||||
================
|
||||
*/
|
||||
void FS_InitFilesystem( void ) {
|
||||
// allow command line parms to override our defaults
|
||||
// we don't have to specially handle this, because normal command
|
||||
// line variable sets happen before the filesystem
|
||||
// has been initialized
|
||||
//
|
||||
// UPDATE: BTO (VV)
|
||||
// we have to specially handle this, because normal command
|
||||
// line variable sets don't happen until after the filesystem
|
||||
// has already been initialized
|
||||
Com_StartupVariable( "fs_cdpath" );
|
||||
Com_StartupVariable( "fs_basepath" );
|
||||
Com_StartupVariable( "fs_game" );
|
||||
Com_StartupVariable( "fs_copyfiles" );
|
||||
Com_StartupVariable( "fs_restrict" );
|
||||
|
||||
// try to start up normally
|
||||
FS_Startup( BASEGAME );
|
||||
initialized = qtrue;
|
||||
|
||||
// see if we are going to allow add-ons
|
||||
FS_SetRestrictions();
|
||||
|
||||
// if we can't find default.cfg, assume that the paths are
|
||||
// busted and error out now, rather than getting an unreadable
|
||||
// graphics screen when the font fails to load
|
||||
if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
|
||||
Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FS_Flush( fileHandle_t f ) {
|
||||
fflush(fsh[f].handleFiles.file.o);
|
||||
}
|
||||
|
||||
1031
code/qcommon/files_console.cpp
Normal file
1031
code/qcommon/files_console.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1741
code/qcommon/files_pc.cpp
Normal file
1741
code/qcommon/files_pc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
169
code/qcommon/fixedmap.h
Normal file
169
code/qcommon/fixedmap.h
Normal file
@@ -0,0 +1,169 @@
|
||||
#ifndef __FIXEDMAP_H
|
||||
#define __FIXEDMAP_H
|
||||
|
||||
|
||||
/*
|
||||
An STL map-like container. Quickly thrown together to replace STL maps
|
||||
in specific instances. Many gotchas. Use with caution.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
template < class T, class U >
|
||||
class VVFixedMap
|
||||
{
|
||||
private:
|
||||
struct Data {
|
||||
T data;
|
||||
U key;
|
||||
};
|
||||
|
||||
Data *items;
|
||||
unsigned int numItems;
|
||||
unsigned int maxItems;
|
||||
|
||||
VVFixedMap(void) {}
|
||||
|
||||
public:
|
||||
VVFixedMap(unsigned int maxItems)
|
||||
{
|
||||
items = new Data[maxItems];
|
||||
numItems = 0;
|
||||
this->maxItems = maxItems;
|
||||
}
|
||||
|
||||
|
||||
~VVFixedMap(void)
|
||||
{
|
||||
items -= ( maxItems - numItems );
|
||||
delete [] items;
|
||||
numItems = 0;
|
||||
}
|
||||
|
||||
|
||||
bool Insert(const T &newItem, const U &key)
|
||||
{
|
||||
Data *storage = NULL;
|
||||
|
||||
//Check for fullness.
|
||||
if(numItems >= maxItems) {
|
||||
assert( 0 );
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check for reuse.
|
||||
if(!FindUnsorted(key, storage)) {
|
||||
storage = items + numItems;
|
||||
numItems++;
|
||||
}
|
||||
else
|
||||
assert( 0 );
|
||||
|
||||
storage->data = newItem;
|
||||
storage->key = key;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Faster version of Insert(), but it doesn't check for dupes. Used
|
||||
// by the filecode cache (when we know the data we're inserting is good).
|
||||
bool InsertUnsafe(const T &newItem, const U &key)
|
||||
{
|
||||
//Check for fullness.
|
||||
if(numItems >= maxItems) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Data *storage = items + numItems;
|
||||
numItems++;
|
||||
|
||||
storage->data = newItem;
|
||||
storage->key = key;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Sort(void)
|
||||
{
|
||||
qsort(items, numItems, sizeof(Data),
|
||||
VVFixedMap< T, U >::FixedMapSorter);
|
||||
}
|
||||
|
||||
|
||||
//Binary search, items must have been sorted!
|
||||
T *Find(const U &key)
|
||||
{
|
||||
int i;
|
||||
int high;
|
||||
int low;
|
||||
|
||||
for(low = -1, high = numItems; high - low > 1; ) {
|
||||
i = (high + low) / 2;
|
||||
if(key < items[i].key) {
|
||||
high = i;
|
||||
} else if(key > items[i].key) {
|
||||
low = i;
|
||||
} else {
|
||||
return &items[i].data;
|
||||
}
|
||||
}
|
||||
|
||||
if(items[i+1].key == key) {
|
||||
return &items[i+1].data;
|
||||
} else if(items[i-1].key == key) {
|
||||
return &items[i-1].data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
//Slower, but don't need to call sort first.
|
||||
T *FindUnsorted(const U &key, Data *&storage)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0; i<numItems; i++) {
|
||||
if(items[i].key == key) {
|
||||
storage = items + i;
|
||||
return &items[i].data;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// returns the top item's data
|
||||
// and removes the item from the map
|
||||
T *Pop(void)
|
||||
{
|
||||
T* top = NULL;
|
||||
|
||||
if(numItems)
|
||||
{
|
||||
top = &items[0].data;
|
||||
items++;
|
||||
numItems--;
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
|
||||
static int FixedMapSorter(const void *a, const void *b)
|
||||
{
|
||||
if(((Data*)a)->key > ((Data*)b)->key) {
|
||||
return 1;
|
||||
} else if(((Data*)a)->key == ((Data*)b)->key) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
525
code/qcommon/hstring.cpp
Normal file
525
code/qcommon/hstring.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
#include "cm_local.h"
|
||||
#include "hstring.h"
|
||||
|
||||
#if defined (_DEBUG) && defined (_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
//#include <windows.h> // for Sleep for Z_Malloc recovery attempy
|
||||
#include "platform.h"
|
||||
#endif
|
||||
|
||||
// mapPoolBlockCount is defined differently in the executable (sv_main.cpp) and the game dll (g_main.cpp) cuz
|
||||
//we likely don't need as many blocks in the executable as we do in the game
|
||||
extern int mapPoolBlockCount;
|
||||
|
||||
// Used to fool optimizer during compilation of mem touch routines.
|
||||
int HaHaOptimizer2=0;
|
||||
|
||||
#ifndef _XBOX
|
||||
CMapPoolLow &GetMapPool()
|
||||
{
|
||||
// this may need to be ifdefed to be different for different modules
|
||||
static CMapPoolLow thePool;
|
||||
return thePool;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define MAPBLOCK_SIZE_NODES (1024)
|
||||
#define MAPNODE_FREE (0xa1)
|
||||
#define MAPNODE_INUSE (0x94)
|
||||
|
||||
struct SMapNode
|
||||
{
|
||||
unsigned char mData[MAP_NODE_SIZE-2];
|
||||
unsigned char mMapBlockNum;
|
||||
unsigned char mTag;
|
||||
};
|
||||
|
||||
class CMapBlock
|
||||
{
|
||||
int mId;
|
||||
char mRaw[(MAPBLOCK_SIZE_NODES+1)*MAP_NODE_SIZE];
|
||||
SMapNode *mNodes;
|
||||
int mLastNode;
|
||||
|
||||
public:
|
||||
CMapBlock(int id,vector <void *> &freeList) :
|
||||
mLastNode(0)
|
||||
{
|
||||
// Alloc node storage for MAPBLOCK_SIZE_NODES worth of nodes.
|
||||
mNodes=(SMapNode *)((((unsigned long)mRaw)+MAP_NODE_SIZE)&~(unsigned long)0x1f);
|
||||
// Set all nodes to initially be free.
|
||||
int i;
|
||||
for(i=0;i<MAPBLOCK_SIZE_NODES;i++)
|
||||
{
|
||||
mNodes[i].mMapBlockNum=id;
|
||||
mNodes[i].mTag=MAPNODE_FREE;
|
||||
freeList.push_back((void *)&mNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool bOwnsNode(void *node)
|
||||
{
|
||||
return((((SMapNode *)node)>=&mNodes[0])&&(((SMapNode *)node)<&mNodes[MAPBLOCK_SIZE_NODES]));
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef _XBOX
|
||||
CMapPoolLow::CMapPoolLow()
|
||||
{
|
||||
mLastBlockNum=-1;
|
||||
}
|
||||
|
||||
CMapPoolLow::~CMapPoolLow()
|
||||
{
|
||||
#if _DEBUG
|
||||
char mess[1000];
|
||||
#if _GAME
|
||||
if(mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||||
{
|
||||
sprintf(mess,"[MEM][GAME] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||||
OutputDebugString(mess);
|
||||
}
|
||||
sprintf(mess, "[MEM][GAME] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||||
OutputDebugString(mess);
|
||||
#elif _CGAME
|
||||
if (mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||||
{
|
||||
sprintf(mess, "[MEM][CGAME] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||||
OutputDebugString(mess);
|
||||
}
|
||||
sprintf(mess, "[MEM][CGAME] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||||
OutputDebugString(mess);
|
||||
#else
|
||||
if (mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||||
{
|
||||
sprintf(mess, "[MEM][EXE] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||||
OutputDebugString(mess);
|
||||
}
|
||||
sprintf(mess, "[MEM][EXE] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||||
OutputDebugString(mess);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int i;
|
||||
for (i=0;i<mMapBlocks.size();i++)
|
||||
{
|
||||
delete mMapBlocks[i];
|
||||
}
|
||||
}
|
||||
|
||||
void *CMapPoolLow::Alloc()
|
||||
{
|
||||
// Try to request a node. First we look in the free-list, but if that
|
||||
// happens to be empty, we allocate more storage in the current CMapBlock.
|
||||
void *node=0;
|
||||
if(mFreeList.size())
|
||||
{
|
||||
// Retrieve the node to be recycled.
|
||||
node=(void *)mFreeList[mFreeList.size()-1];
|
||||
mFreeList.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
// None free, so alloc another block.
|
||||
CMapBlock *block=new CMapBlock(mLastBlockNum+1,mFreeList);
|
||||
assert(block);
|
||||
mMapBlocks.push_back(block);
|
||||
mLastBlockNum++;
|
||||
node=(void *)mFreeList[mFreeList.size()-1];
|
||||
mFreeList.pop_back();
|
||||
}
|
||||
|
||||
// Validate we aren't somehow grabbing something that is already in use
|
||||
// and also that the end marker is intact.
|
||||
assert(((SMapNode *)node)->mTag==MAPNODE_FREE);
|
||||
assert((((SMapNode *)node)->mMapBlockNum)>=0);
|
||||
assert((((SMapNode *)node)->mMapBlockNum)<256);
|
||||
assert((((SMapNode *)node)->mMapBlockNum)<=mLastBlockNum);
|
||||
assert(mMapBlocks[((SMapNode *)node)->mMapBlockNum]->bOwnsNode(node));
|
||||
|
||||
// Ok, mark the node as in use.
|
||||
((SMapNode *)node)->mTag=MAPNODE_INUSE;
|
||||
|
||||
return(node);
|
||||
}
|
||||
|
||||
void CMapPoolLow::Free(void *p)
|
||||
{
|
||||
// Validate that someone isn't trying to double free this node and also
|
||||
// that the end marker is intact.
|
||||
assert(((SMapNode *)p)->mTag==MAPNODE_INUSE);
|
||||
assert((((SMapNode *)p)->mMapBlockNum)>=0);
|
||||
assert((((SMapNode *)p)->mMapBlockNum)<256);
|
||||
assert((((SMapNode *)p)->mMapBlockNum)<=mLastBlockNum);
|
||||
assert(mMapBlocks[((SMapNode *)p)->mMapBlockNum]->bOwnsNode(p));
|
||||
|
||||
// Ok, mark the the node as free.
|
||||
((SMapNode *)p)->mTag=MAPNODE_FREE;
|
||||
|
||||
// Add a new freelist entry to point at this node.
|
||||
mFreeList.push_back(p);
|
||||
}
|
||||
|
||||
void CMapPoolLow::TouchMem()
|
||||
{
|
||||
int i,j;
|
||||
unsigned char *memory;
|
||||
int totSize=0;
|
||||
for(i=0;i<mMapBlocks.size();i++)
|
||||
{
|
||||
memory=(unsigned char *)mMapBlocks[i];
|
||||
totSize+=sizeof(CMapBlock);
|
||||
for(j=0;j<sizeof(CMapBlock);j+=256)
|
||||
{
|
||||
HaHaOptimizer2+=memory[j];
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
Com_Printf("MapPool: Bytes touched %i\n",totSize);
|
||||
#endif
|
||||
}
|
||||
#endif // _XBOX
|
||||
|
||||
////////////
|
||||
// hString stuff
|
||||
////////////
|
||||
#define MAX_HASH (65536*2)
|
||||
|
||||
// Max number of strings we can ever deal with.
|
||||
#define MAX_HSTRINGS 100000
|
||||
|
||||
// Size of a string storage block in bytes.
|
||||
#define BLOCK_SIZE 65536
|
||||
|
||||
|
||||
int HashFunction(const char *key)
|
||||
{
|
||||
long hash=0;
|
||||
int i=0;
|
||||
unsigned char letter;
|
||||
|
||||
letter = (unsigned char)*key++;
|
||||
while (letter)
|
||||
{
|
||||
hash += (long)(letter) * (i + 119);
|
||||
i++;
|
||||
letter = (unsigned char)*key++;
|
||||
}
|
||||
hash &= MAX_HASH - 1;
|
||||
return (int)hash;
|
||||
}
|
||||
|
||||
|
||||
class CHashHelper
|
||||
{
|
||||
int mHashes[MAX_HASH];
|
||||
int mFindPtr;
|
||||
int mFindPtrStart;
|
||||
public:
|
||||
CHashHelper()
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<MAX_HASH;i++)
|
||||
{
|
||||
mHashes[i]=0;
|
||||
}
|
||||
}
|
||||
void Add(int hash,int value)
|
||||
{
|
||||
assert(hash>=0&&hash<MAX_HASH);
|
||||
assert(value); // 0 is the empty marker
|
||||
int i=hash;
|
||||
while (mHashes[i])
|
||||
{
|
||||
assert(mHashes[i]!=value); //please don't insert things twice
|
||||
i=(i+1)&(MAX_HASH-1);
|
||||
assert(i!=hash); //hash table is full?
|
||||
}
|
||||
mHashes[i]=value;
|
||||
}
|
||||
int FindFirst(int hash)
|
||||
{
|
||||
mFindPtr=hash;
|
||||
mFindPtrStart=hash;
|
||||
return FindNext();
|
||||
}
|
||||
int FindNext()
|
||||
{
|
||||
assert(mFindPtr>=0&&mFindPtr<MAX_HASH);
|
||||
int val=mHashes[mFindPtr];
|
||||
mFindPtr=(mFindPtr+1)&(MAX_HASH-1);
|
||||
assert(mFindPtr!=mFindPtrStart); //hash table full?
|
||||
return val;
|
||||
}
|
||||
void TouchMem()
|
||||
{
|
||||
int i;
|
||||
for(i=0;i<sizeof(mHashes);i+=256)
|
||||
{
|
||||
HaHaOptimizer2+=((unsigned char *)mHashes)[i];
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
Com_Printf("Hash helper: Bytes touched %i\n",sizeof(mHashes));
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
CHashHelper &HashHelper()
|
||||
{
|
||||
static CHashHelper It;
|
||||
return It;
|
||||
}
|
||||
|
||||
static char *gCharPtrs[MAX_HSTRINGS];
|
||||
|
||||
class CHSBlock
|
||||
{
|
||||
int mBytesUsed;
|
||||
char mRaw[BLOCK_SIZE];
|
||||
|
||||
public:
|
||||
CHSBlock(void) :
|
||||
mBytesUsed(0)
|
||||
{
|
||||
// So we can do a comparison of blocks for debug purposes.
|
||||
memset(mRaw,0,BLOCK_SIZE);
|
||||
};
|
||||
|
||||
char *Alloc(int sizeBytes)
|
||||
{
|
||||
// Remember to include 0 termintor in size.
|
||||
sizeBytes++;
|
||||
|
||||
// Is it WAAAY to big? If so we complain loudly.
|
||||
assert(sizeBytes<=BLOCK_SIZE);
|
||||
|
||||
// If we don't have space in the current block, return failure.
|
||||
if(sizeBytes>(BLOCK_SIZE-mBytesUsed))
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
// Return the pointer to the start of allocated space.
|
||||
char *ret=&mRaw[mBytesUsed];
|
||||
mBytesUsed+=sizeBytes;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool operator== (const CHSBlock &block) const
|
||||
{
|
||||
if(!memcmp(mRaw,block.mRaw,BLOCK_SIZE))
|
||||
{
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class CPool
|
||||
{
|
||||
vector<CHSBlock *> mBlockVec;
|
||||
|
||||
public:
|
||||
int mNextStringId;
|
||||
int mLastBlockNum;
|
||||
|
||||
CPool(void) :
|
||||
mNextStringId(1),
|
||||
mLastBlockNum(-1)
|
||||
{
|
||||
memset(gCharPtrs,0,MAX_HSTRINGS*4);
|
||||
}
|
||||
|
||||
~CPool(void)
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<mBlockVec.size();i++)
|
||||
{
|
||||
delete mBlockVec[i];
|
||||
}
|
||||
}
|
||||
|
||||
char *Alloc(int sizeBytes,int &id)
|
||||
{
|
||||
// Can't alloc more than MAX_HSTRINGS.
|
||||
assert(mNextStringId<MAX_HSTRINGS);
|
||||
char *raw=0;
|
||||
if (mLastBlockNum>=0)
|
||||
{
|
||||
// Get the pointer to the start of allocated space in the current block.
|
||||
raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes);
|
||||
}
|
||||
if(!raw)
|
||||
{
|
||||
// Ok, make a new empty block and append it.
|
||||
CHSBlock *block=new(CHSBlock);
|
||||
mBlockVec.push_back(block);
|
||||
mLastBlockNum++;
|
||||
raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes);
|
||||
}
|
||||
// Should never really happen!!
|
||||
assert(raw);
|
||||
|
||||
id=mNextStringId;
|
||||
gCharPtrs[mNextStringId]=raw;
|
||||
mNextStringId++;
|
||||
|
||||
return(raw);
|
||||
}
|
||||
|
||||
bool operator== (const CPool &pool) const
|
||||
{
|
||||
int i;
|
||||
for(i=0;i<mBlockVec.size();i++)
|
||||
{
|
||||
if(!(*mBlockVec[i]==*pool.mBlockVec[i]))
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
void TouchMem()
|
||||
{
|
||||
int i,j;
|
||||
unsigned char *memory;
|
||||
int totSize=0;
|
||||
for(i=0;i<mBlockVec.size();i++)
|
||||
{
|
||||
memory=(unsigned char *)mBlockVec[i];
|
||||
totSize+=sizeof(CHSBlock);
|
||||
for(j=0;j<sizeof(CHSBlock);j+=256)
|
||||
{
|
||||
HaHaOptimizer2+=memory[j];
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
Com_Printf("String Pool: Bytes touched %i\n",totSize);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
CPool &TheDebugPool(void);
|
||||
CPool &ThePool(void);
|
||||
|
||||
class CPoolChecker
|
||||
{
|
||||
public:
|
||||
CPoolChecker()
|
||||
{
|
||||
TheDebugPool();
|
||||
ThePool();
|
||||
}
|
||||
~CPoolChecker()
|
||||
{
|
||||
#if 0
|
||||
int i;
|
||||
for (i=1;i<ThePool().mNextStringId;i++)
|
||||
{
|
||||
OutputDebugString(gCharPtrs[i]);
|
||||
OutputDebugString("\n");
|
||||
}
|
||||
#endif
|
||||
#if _DEBUG
|
||||
char mess[1000];
|
||||
#if _GAME
|
||||
sprintf(mess,"[MEM][GAME] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||||
#elif _CGAME
|
||||
sprintf(mess,"[MEM][CGAME] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||||
#else
|
||||
sprintf(mess,"[MEM][EXE] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||||
#endif
|
||||
OutputDebugString(mess);
|
||||
#endif
|
||||
// if this fails it means the string storage is CORRUPTED, let someone know
|
||||
assert(TheDebugPool()==ThePool());
|
||||
}
|
||||
};
|
||||
|
||||
static CPoolChecker TheCPoolChecker;
|
||||
|
||||
CPool &TheDebugPool(void)
|
||||
{
|
||||
static CPool theDebugPool;
|
||||
return(theDebugPool);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
CPool &ThePool(void)
|
||||
{
|
||||
static CPool thePool;
|
||||
return(thePool);
|
||||
}
|
||||
|
||||
void TouchStringPool(void)
|
||||
{
|
||||
ThePool().TouchMem();
|
||||
HashHelper().TouchMem();
|
||||
}
|
||||
|
||||
//
|
||||
// Now the rest of the hString class.
|
||||
//
|
||||
#ifdef _XBOX
|
||||
namespace exeNamespace
|
||||
{
|
||||
#endif
|
||||
|
||||
void hstring::Init(const char *str)
|
||||
{
|
||||
if(!str)
|
||||
{
|
||||
mId=0;
|
||||
return;
|
||||
}
|
||||
int hash=HashFunction(str);
|
||||
int id=HashHelper().FindFirst(hash);
|
||||
while (id)
|
||||
{
|
||||
assert(id>0&&id<ThePool().mNextStringId);
|
||||
if (!strcmp(str,gCharPtrs[id]))
|
||||
{
|
||||
mId=id;
|
||||
return;
|
||||
}
|
||||
id=HashHelper().FindNext();
|
||||
}
|
||||
char *raw=ThePool().Alloc(strlen(str),mId);
|
||||
strcpy(raw,str);
|
||||
HashHelper().Add(hash,mId);
|
||||
#ifdef _DEBUG
|
||||
int test;
|
||||
raw=TheDebugPool().Alloc(strlen(str),test);
|
||||
assert(test==mId);
|
||||
strcpy(raw,str);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
const char *hstring::c_str(void) const
|
||||
{
|
||||
if(!mId)
|
||||
{
|
||||
return("");
|
||||
}
|
||||
assert(mId>0&&mId<ThePool().mNextStringId);
|
||||
return(gCharPtrs[mId]);
|
||||
}
|
||||
|
||||
string hstring::str(void) const
|
||||
{
|
||||
if(!mId)
|
||||
{
|
||||
return(string());
|
||||
}
|
||||
assert(mId>0&&mId<ThePool().mNextStringId);
|
||||
return string(gCharPtrs[mId]);
|
||||
}
|
||||
|
||||
#ifdef _XBOX
|
||||
} // exeNamespace
|
||||
#endif
|
||||
219
code/qcommon/hstring.h
Normal file
219
code/qcommon/hstring.h
Normal file
@@ -0,0 +1,219 @@
|
||||
#if !defined hString_H
|
||||
#define hString_H
|
||||
|
||||
#pragma warning (push, 3) //go back down to 3 for the stl include
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#pragma warning (pop)
|
||||
|
||||
using namespace std;
|
||||
|
||||
class hstring
|
||||
{
|
||||
int mId;
|
||||
|
||||
void Init(const char *str);
|
||||
|
||||
public:
|
||||
hstring()
|
||||
{
|
||||
mId=0;
|
||||
}
|
||||
hstring(const char *str)
|
||||
{
|
||||
Init(str);
|
||||
}
|
||||
hstring(const string &str)
|
||||
{
|
||||
Init(str.c_str());
|
||||
}
|
||||
hstring(const hstring &str)
|
||||
{
|
||||
mId=str.mId;
|
||||
}
|
||||
|
||||
operator string () const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
|
||||
const char *c_str(void) const;
|
||||
string str(void) const;
|
||||
|
||||
hstring& operator= (const char *str)
|
||||
{
|
||||
Init(str);
|
||||
return *this;
|
||||
}
|
||||
hstring& operator= (const string &str)
|
||||
{
|
||||
Init(str.c_str());
|
||||
return *this;
|
||||
}
|
||||
hstring& operator= (const hstring &str)
|
||||
{
|
||||
mId=str.mId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator== (const hstring &str) const
|
||||
{
|
||||
return((mId==str.mId)?true:false);
|
||||
}
|
||||
|
||||
int compare(const hstring &str) const
|
||||
{
|
||||
return strcmp(c_str(),str.c_str());
|
||||
}
|
||||
|
||||
bool operator< (const hstring &str) const
|
||||
{
|
||||
return((mId<str.mId)?true:false);
|
||||
}
|
||||
int length() const
|
||||
{
|
||||
return strlen(c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void TouchStringPool(void);
|
||||
|
||||
////////////
|
||||
// MapPool
|
||||
////////////
|
||||
#define MAP_NODE_SIZE (32)
|
||||
|
||||
class CMapBlock;
|
||||
class CMapPoolLow
|
||||
{
|
||||
vector <CMapBlock *> mMapBlocks;
|
||||
vector <void *> mFreeList;
|
||||
int mLastBlockNum;
|
||||
|
||||
public:
|
||||
CMapPoolLow();
|
||||
~CMapPoolLow();
|
||||
void *Alloc();
|
||||
void Free(void *p);
|
||||
void TouchMem();
|
||||
};
|
||||
|
||||
CMapPoolLow &GetMapPool();
|
||||
|
||||
template<class T>
|
||||
class CMapPool
|
||||
{
|
||||
CMapPoolLow &mPool;
|
||||
public:
|
||||
CMapPool() : mPool(GetMapPool())
|
||||
{
|
||||
|
||||
}
|
||||
template <class U>
|
||||
CMapPool(const U&) : mPool(GetMapPool())
|
||||
{
|
||||
}
|
||||
~CMapPool()
|
||||
{
|
||||
}
|
||||
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef size_t size_type;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
typedef CMapPool<U> other;
|
||||
};
|
||||
|
||||
// return address of values
|
||||
pointer address (reference value) const
|
||||
{
|
||||
return &value;
|
||||
}
|
||||
const_pointer address (const_reference value) const
|
||||
{
|
||||
return &value;
|
||||
}
|
||||
|
||||
// return maximum number of elements that can be allocated
|
||||
size_type max_size () const
|
||||
{
|
||||
// return mMaxSize;
|
||||
return 0xfffffff; //uh, take a guess
|
||||
}
|
||||
|
||||
// allocate but don't initialize num elements of type T
|
||||
pointer allocate (size_type num, const void* = 0)
|
||||
{
|
||||
assert(sizeof(T)<=(MAP_NODE_SIZE-2)); // to big for this pool
|
||||
assert(num==1); //allocator not design for this
|
||||
return (T*)mPool.Alloc();
|
||||
}
|
||||
void *_Charalloc(size_type size)
|
||||
{
|
||||
assert(size<=(MAP_NODE_SIZE-2)); // to big for this pool
|
||||
return mPool.Alloc();
|
||||
}
|
||||
|
||||
// initialize elements of allocated storage p with value value
|
||||
void construct (pointer p, const T& value)
|
||||
{
|
||||
// initialize memory with placement new
|
||||
new((void*)p)T(value);
|
||||
}
|
||||
|
||||
// destroy elements of initialized storage p
|
||||
void destroy (pointer p)
|
||||
{
|
||||
// destroy objects by calling their destructor
|
||||
p->~T();
|
||||
}
|
||||
|
||||
// deallocate storage p of deleted elements
|
||||
template<class U>
|
||||
void deallocate (U *p, size_type num)
|
||||
{
|
||||
assert(num==1); //allocator not design for this
|
||||
mPool.Free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T1,class T2>
|
||||
bool operator== (const CMapPool<T1>&,
|
||||
const CMapPool<T2>&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
template <class T1,class T2>
|
||||
bool operator!= (const CMapPool<T1>&,
|
||||
const CMapPool<T2>&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template <class K,class V,class Compare = less<K> >
|
||||
class hmap : public map<K,V,Compare,CMapPool<V> >{};
|
||||
|
||||
template <class K,class V,class Compare = less<K> >
|
||||
class hmultimap : public multimap<K,V,Compare,CMapPool<V> >{};
|
||||
|
||||
template <class K,class Compare = less<K> >
|
||||
class hset : public set<K,Compare,CMapPool<K> >{};
|
||||
|
||||
template <class K,class Compare = less<K> >
|
||||
class hmultiset : public multiset<K,Compare,CMapPool<K> >{};
|
||||
|
||||
template <class K>
|
||||
class hlist : public list<K,CMapPool<K> >{};
|
||||
|
||||
#endif // hString_H
|
||||
274
code/qcommon/md4.cpp
Normal file
274
code/qcommon/md4.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/* GLOBAL.H - RSAREF types and constants */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* POINTER defines a generic pointer type */
|
||||
typedef unsigned char *POINTER;
|
||||
|
||||
/* UINT2 defines a two byte word */
|
||||
typedef unsigned short int UINT2;
|
||||
|
||||
/* UINT4 defines a four byte word */
|
||||
typedef unsigned long int UINT4;
|
||||
|
||||
|
||||
/* MD4.H - header file for MD4C.C */
|
||||
|
||||
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function.
|
||||
License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work.
|
||||
RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this documentation and/or software. */
|
||||
|
||||
/* MD4 context. */
|
||||
typedef struct {
|
||||
UINT4 state[4]; /* state (ABCD) */
|
||||
UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
|
||||
unsigned char buffer[64]; /* input buffer */
|
||||
} MD4_CTX;
|
||||
|
||||
void MD4Init (MD4_CTX *);
|
||||
void MD4Update (MD4_CTX *, const unsigned char *, unsigned int);
|
||||
void MD4Final (unsigned char [16], MD4_CTX *);
|
||||
|
||||
|
||||
|
||||
/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */
|
||||
/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it is identified as the
|
||||
RSA Data Security, Inc. MD4 Message-Digest Algorithm
|
||||
in all material mentioning or referencing this software or this function.
|
||||
License is also granted to make and use derivative works provided that such works are identified as
|
||||
derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm
|
||||
in all material mentioning or referencing the derived work.
|
||||
RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided
|
||||
as is without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this documentation and/or software. */
|
||||
|
||||
/* Constants for MD4Transform routine. */
|
||||
#define S11 3
|
||||
#define S12 7
|
||||
#define S13 11
|
||||
#define S14 19
|
||||
#define S21 3
|
||||
#define S22 5
|
||||
#define S23 9
|
||||
#define S24 13
|
||||
#define S31 3
|
||||
#define S32 9
|
||||
#define S33 11
|
||||
#define S34 15
|
||||
|
||||
static void MD4Transform (UINT4 [4], const unsigned char [64]);
|
||||
static void Encode (unsigned char *, UINT4 *, unsigned int);
|
||||
static void Decode (UINT4 *, const unsigned char *, unsigned int);
|
||||
|
||||
static unsigned char PADDING[64] = {
|
||||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
/* F, G and H are basic MD4 functions. */
|
||||
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
|
||||
#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
|
||||
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
||||
|
||||
/* ROTATE_LEFT rotates x left n bits. */
|
||||
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
|
||||
|
||||
/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
|
||||
/* Rotation is separate from addition to prevent recomputation */
|
||||
#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
|
||||
/* MD4 initialization. Begins an MD4 operation, writing a new context. */
|
||||
void MD4Init (MD4_CTX *context)
|
||||
{
|
||||
context->count[0] = context->count[1] = 0;
|
||||
|
||||
/* Load magic initialization constants.*/
|
||||
context->state[0] = 0x67452301;
|
||||
context->state[1] = 0xefcdab89;
|
||||
context->state[2] = 0x98badcfe;
|
||||
context->state[3] = 0x10325476;
|
||||
}
|
||||
|
||||
/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */
|
||||
void MD4Update (MD4_CTX *context, const unsigned char *input, unsigned int inputLen)
|
||||
{
|
||||
unsigned int i, index, partLen;
|
||||
|
||||
/* Compute number of bytes mod 64 */
|
||||
index = (unsigned int)((context->count[0] >> 3) & 0x3F);
|
||||
|
||||
/* Update number of bits */
|
||||
if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3))
|
||||
context->count[1]++;
|
||||
|
||||
context->count[1] += ((UINT4)inputLen >> 29);
|
||||
|
||||
partLen = 64 - index;
|
||||
|
||||
/* Transform as many times as possible.*/
|
||||
if (inputLen >= partLen)
|
||||
{
|
||||
memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
|
||||
MD4Transform (context->state, context->buffer);
|
||||
|
||||
for (i = partLen; i + 63 < inputLen; i += 64)
|
||||
MD4Transform (context->state, &input[i]);
|
||||
|
||||
index = 0;
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
|
||||
/* Buffer remaining input */
|
||||
memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);
|
||||
}
|
||||
|
||||
|
||||
/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */
|
||||
void MD4Final (unsigned char digest[16], MD4_CTX *context)
|
||||
{
|
||||
unsigned char bits[8];
|
||||
unsigned int index, padLen;
|
||||
|
||||
/* Save number of bits */
|
||||
Encode (bits, context->count, 8);
|
||||
|
||||
/* Pad out to 56 mod 64.*/
|
||||
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
|
||||
padLen = (index < 56) ? (56 - index) : (120 - index);
|
||||
MD4Update (context, PADDING, padLen);
|
||||
|
||||
/* Append length (before padding) */
|
||||
MD4Update (context, bits, 8);
|
||||
|
||||
/* Store state in digest */
|
||||
Encode (digest, context->state, 16);
|
||||
|
||||
/* Zeroize sensitive information.*/
|
||||
memset ((POINTER)context, 0, sizeof (*context));
|
||||
}
|
||||
|
||||
|
||||
/* MD4 basic transformation. Transforms state based on block. */
|
||||
static void MD4Transform (UINT4 state[4], const unsigned char block[64])
|
||||
{
|
||||
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
|
||||
|
||||
Decode (x, block, 64);
|
||||
|
||||
/* Round 1 */
|
||||
FF (a, b, c, d, x[ 0], S11); /* 1 */
|
||||
FF (d, a, b, c, x[ 1], S12); /* 2 */
|
||||
FF (c, d, a, b, x[ 2], S13); /* 3 */
|
||||
FF (b, c, d, a, x[ 3], S14); /* 4 */
|
||||
FF (a, b, c, d, x[ 4], S11); /* 5 */
|
||||
FF (d, a, b, c, x[ 5], S12); /* 6 */
|
||||
FF (c, d, a, b, x[ 6], S13); /* 7 */
|
||||
FF (b, c, d, a, x[ 7], S14); /* 8 */
|
||||
FF (a, b, c, d, x[ 8], S11); /* 9 */
|
||||
FF (d, a, b, c, x[ 9], S12); /* 10 */
|
||||
FF (c, d, a, b, x[10], S13); /* 11 */
|
||||
FF (b, c, d, a, x[11], S14); /* 12 */
|
||||
FF (a, b, c, d, x[12], S11); /* 13 */
|
||||
FF (d, a, b, c, x[13], S12); /* 14 */
|
||||
FF (c, d, a, b, x[14], S13); /* 15 */
|
||||
FF (b, c, d, a, x[15], S14); /* 16 */
|
||||
|
||||
/* Round 2 */
|
||||
GG (a, b, c, d, x[ 0], S21); /* 17 */
|
||||
GG (d, a, b, c, x[ 4], S22); /* 18 */
|
||||
GG (c, d, a, b, x[ 8], S23); /* 19 */
|
||||
GG (b, c, d, a, x[12], S24); /* 20 */
|
||||
GG (a, b, c, d, x[ 1], S21); /* 21 */
|
||||
GG (d, a, b, c, x[ 5], S22); /* 22 */
|
||||
GG (c, d, a, b, x[ 9], S23); /* 23 */
|
||||
GG (b, c, d, a, x[13], S24); /* 24 */
|
||||
GG (a, b, c, d, x[ 2], S21); /* 25 */
|
||||
GG (d, a, b, c, x[ 6], S22); /* 26 */
|
||||
GG (c, d, a, b, x[10], S23); /* 27 */
|
||||
GG (b, c, d, a, x[14], S24); /* 28 */
|
||||
GG (a, b, c, d, x[ 3], S21); /* 29 */
|
||||
GG (d, a, b, c, x[ 7], S22); /* 30 */
|
||||
GG (c, d, a, b, x[11], S23); /* 31 */
|
||||
GG (b, c, d, a, x[15], S24); /* 32 */
|
||||
|
||||
/* Round 3 */
|
||||
HH (a, b, c, d, x[ 0], S31); /* 33 */
|
||||
HH (d, a, b, c, x[ 8], S32); /* 34 */
|
||||
HH (c, d, a, b, x[ 4], S33); /* 35 */
|
||||
HH (b, c, d, a, x[12], S34); /* 36 */
|
||||
HH (a, b, c, d, x[ 2], S31); /* 37 */
|
||||
HH (d, a, b, c, x[10], S32); /* 38 */
|
||||
HH (c, d, a, b, x[ 6], S33); /* 39 */
|
||||
HH (b, c, d, a, x[14], S34); /* 40 */
|
||||
HH (a, b, c, d, x[ 1], S31); /* 41 */
|
||||
HH (d, a, b, c, x[ 9], S32); /* 42 */
|
||||
HH (c, d, a, b, x[ 5], S33); /* 43 */
|
||||
HH (b, c, d, a, x[13], S34); /* 44 */
|
||||
HH (a, b, c, d, x[ 3], S31); /* 45 */
|
||||
HH (d, a, b, c, x[11], S32); /* 46 */
|
||||
HH (c, d, a, b, x[ 7], S33); /* 47 */
|
||||
HH (b, c, d, a, x[15], S34); /* 48 */
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
|
||||
/* Zeroize sensitive information.*/
|
||||
memset ((POINTER)x, 0, sizeof (x));
|
||||
}
|
||||
|
||||
|
||||
/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */
|
||||
static void Encode (unsigned char *output, UINT4 *input, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0, j = 0; j < len; i++, j += 4) {
|
||||
output[j] = (unsigned char)(input[i] & 0xff);
|
||||
output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
|
||||
output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
|
||||
output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */
|
||||
static void Decode (UINT4 *output, const unsigned char *input, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0, j = 0; j < len; i++, j += 4)
|
||||
output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
unsigned Com_BlockChecksum (void const *buffer, int length)
|
||||
{
|
||||
int digest[4];
|
||||
unsigned val;
|
||||
MD4_CTX ctx;
|
||||
|
||||
MD4Init (&ctx);
|
||||
MD4Update (&ctx, (unsigned char *)buffer, length);
|
||||
MD4Final ( (unsigned char *)digest, &ctx);
|
||||
|
||||
val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];
|
||||
|
||||
return val;
|
||||
}
|
||||
1248
code/qcommon/msg.cpp
Normal file
1248
code/qcommon/msg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
566
code/qcommon/net_chan.cpp
Normal file
566
code/qcommon/net_chan.cpp
Normal file
@@ -0,0 +1,566 @@
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
/*
|
||||
|
||||
packet header
|
||||
-------------
|
||||
4 outgoing sequence. high bit will be set if this is a fragmented message
|
||||
4 acknowledge sequence
|
||||
[2 qport (only for client to server)]
|
||||
[2 fragment start byte]
|
||||
[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment]
|
||||
|
||||
if the sequence number is -1, the packet should be handled as an out-of-band
|
||||
message instead of as part of a netcon.
|
||||
|
||||
All fragments will have the same sequence numbers.
|
||||
|
||||
The qport field is a workaround for bad address translating routers that
|
||||
sometimes remap the client's source port on a packet during gameplay.
|
||||
|
||||
If the base part of the net address matches and the qport matches, then the
|
||||
channel matches even if the IP port differs. The IP port should be updated
|
||||
to the new value before sending out any replies.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#define MAX_PACKETLEN (MAX_MSGLEN) //(1400) // max size of a network packet
|
||||
#define MAX_LOOPDATA 16 * 1024
|
||||
|
||||
#if (MAX_PACKETLEN > MAX_MSGLEN)
|
||||
#error MAX_PACKETLEN must be <= MAX_MSGLEN
|
||||
#endif
|
||||
#if (MAX_LOOPDATA > MAX_MSGLEN)
|
||||
#error MAX_LOOPDATA must be <= MAX_MSGLEN
|
||||
#endif
|
||||
|
||||
#define FRAGMENT_SIZE (MAX_PACKETLEN - 100)
|
||||
#define PACKET_HEADER 10 // two ints and a short
|
||||
|
||||
#define FRAGMENT_BIT (1<<31)
|
||||
|
||||
cvar_t *showpackets;
|
||||
cvar_t *showdrop;
|
||||
cvar_t *qport;
|
||||
|
||||
static char *netsrcString[2] = {
|
||||
"client",
|
||||
"server"
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char loopData[MAX_LOOPDATA];
|
||||
int get, send;
|
||||
} loopback_t;
|
||||
|
||||
static loopback_t *loopbacks = NULL;
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Netchan_Init
|
||||
|
||||
===============
|
||||
*/
|
||||
void Netchan_Init( int port ) {
|
||||
if (!loopbacks)
|
||||
{
|
||||
loopbacks = (loopback_t*) Z_Malloc(sizeof(loopback_t) * 2, TAG_NEWDEL, qtrue);
|
||||
}
|
||||
|
||||
port &= 0xffff;
|
||||
showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP );
|
||||
showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP );
|
||||
qport = Cvar_Get ("qport", va("%i", port), CVAR_INIT );
|
||||
}
|
||||
|
||||
void Netchan_Shutdown()
|
||||
{
|
||||
if (loopbacks)
|
||||
{
|
||||
Z_Free(loopbacks);
|
||||
loopbacks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
Netchan_Setup
|
||||
|
||||
called to open a channel to a remote system
|
||||
==============
|
||||
*/
|
||||
void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) {
|
||||
memset (chan, 0, sizeof(*chan));
|
||||
|
||||
chan->sock = sock;
|
||||
chan->remoteAddress = adr;
|
||||
chan->qport = qport;
|
||||
chan->incomingSequence = 0;
|
||||
chan->outgoingSequence = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
Netchan_Transmit
|
||||
|
||||
Sends a message to a connection, fragmenting if necessary
|
||||
A 0 length will still generate a packet.
|
||||
================
|
||||
*/
|
||||
void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) {
|
||||
msg_t send;
|
||||
byte send_buf[MAX_PACKETLEN];
|
||||
int fragmentStart, fragmentLength;
|
||||
|
||||
fragmentStart = 0; // stop warning message
|
||||
fragmentLength = 0;
|
||||
|
||||
// fragment large reliable messages
|
||||
if ( length >= FRAGMENT_SIZE ) {
|
||||
fragmentStart = 0;
|
||||
do {
|
||||
// write the packet header
|
||||
MSG_Init (&send, send_buf, sizeof(send_buf));
|
||||
|
||||
MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT );
|
||||
MSG_WriteLong( &send, chan->incomingSequence );
|
||||
|
||||
// send the qport if we are a client
|
||||
if ( chan->sock == NS_CLIENT ) {
|
||||
MSG_WriteShort( &send, qport->integer );
|
||||
}
|
||||
|
||||
// copy the reliable message to the packet first
|
||||
fragmentLength = FRAGMENT_SIZE;
|
||||
if ( fragmentStart + fragmentLength > length ) {
|
||||
fragmentLength = length - fragmentStart;
|
||||
}
|
||||
|
||||
MSG_WriteShort( &send, fragmentStart );
|
||||
MSG_WriteShort( &send, fragmentLength );
|
||||
MSG_WriteData( &send, data + fragmentStart, fragmentLength );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
Com_Printf ("%s send %4i : s=%i ack=%i fragment=%i,%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, send.cursize
|
||||
, chan->outgoingSequence - 1
|
||||
, chan->incomingSequence
|
||||
, fragmentStart, fragmentLength);
|
||||
}
|
||||
|
||||
fragmentStart += fragmentLength;
|
||||
// this exit condition is a little tricky, because a packet
|
||||
// that is exactly the fragment length still needs to send
|
||||
// a second packet of zero length so that the other side
|
||||
// can tell there aren't more to follow
|
||||
} while ( fragmentStart != length || fragmentLength == FRAGMENT_SIZE );
|
||||
|
||||
chan->outgoingSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// write the packet header
|
||||
MSG_Init (&send, send_buf, sizeof(send_buf));
|
||||
|
||||
MSG_WriteLong( &send, chan->outgoingSequence );
|
||||
MSG_WriteLong( &send, chan->incomingSequence );
|
||||
chan->outgoingSequence++;
|
||||
|
||||
// send the qport if we are a client
|
||||
if ( chan->sock == NS_CLIENT ) {
|
||||
MSG_WriteShort( &send, qport->integer );
|
||||
}
|
||||
|
||||
MSG_WriteData( &send, data, length );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
Com_Printf( "%s send %4i : s=%i ack=%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, send.cursize
|
||||
, chan->outgoingSequence - 1
|
||||
, chan->incomingSequence );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Netchan_Process
|
||||
|
||||
Returns qfalse if the message should not be processed due to being
|
||||
out of order or a fragment.
|
||||
|
||||
Msg must be large enough to hold MAX_MSGLEN, because if this is the
|
||||
final fragment of a multi-part message, the entire thing will be
|
||||
copied out.
|
||||
=================
|
||||
*/
|
||||
qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) {
|
||||
int sequence, sequence_ack;
|
||||
int qport;
|
||||
int fragmentStart, fragmentLength;
|
||||
qboolean fragmented;
|
||||
|
||||
// get sequence numbers
|
||||
MSG_BeginReading( msg );
|
||||
sequence = MSG_ReadLong( msg );
|
||||
sequence_ack = MSG_ReadLong( msg );
|
||||
|
||||
// check for fragment information
|
||||
if ( sequence & FRAGMENT_BIT ) {
|
||||
sequence &= ~FRAGMENT_BIT;
|
||||
fragmented = qtrue;
|
||||
} else {
|
||||
fragmented = qfalse;
|
||||
}
|
||||
|
||||
// read the qport if we are a server
|
||||
if ( chan->sock == NS_SERVER ) {
|
||||
qport = MSG_ReadShort( msg );
|
||||
}
|
||||
|
||||
// read the fragment information
|
||||
if ( fragmented ) {
|
||||
fragmentStart = MSG_ReadShort( msg );
|
||||
fragmentLength = MSG_ReadShort( msg );
|
||||
} else {
|
||||
fragmentStart = 0; // stop warning message
|
||||
fragmentLength = 0;
|
||||
}
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
if ( fragmented ) {
|
||||
Com_Printf( "%s recv %4i : s=%i ack=%i fragment=%i,%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, msg->cursize
|
||||
, sequence
|
||||
, sequence_ack
|
||||
, fragmentStart, fragmentLength );
|
||||
} else {
|
||||
Com_Printf( "%s recv %4i : s=%i ack=%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, msg->cursize
|
||||
, sequence
|
||||
, sequence_ack );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// discard out of order or duplicated packets
|
||||
//
|
||||
if ( sequence <= chan->incomingSequence ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Out of order packet %i at %i\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, sequence
|
||||
, chan->incomingSequence );
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
//
|
||||
// dropped packets don't keep the message from being used
|
||||
//
|
||||
chan->dropped = sequence - (chan->incomingSequence+1);
|
||||
if ( chan->dropped > 0 ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Dropped %i packets at %i\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, chan->dropped
|
||||
, sequence );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// if this is the final framgent of a reliable message,
|
||||
// bump incoming_reliable_sequence
|
||||
//
|
||||
if ( fragmented ) {
|
||||
// make sure we
|
||||
if ( sequence != chan->fragmentSequence ) {
|
||||
chan->fragmentSequence = sequence;
|
||||
chan->fragmentLength = 0;
|
||||
}
|
||||
|
||||
// if we missed a fragment, dump the message
|
||||
if ( fragmentStart != chan->fragmentLength ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Dropped a message fragment\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, sequence);
|
||||
}
|
||||
// we can still keep the part that we have so far,
|
||||
// so we don't need to clear chan->fragmentLength
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// copy the fragment to the fragment buffer
|
||||
if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize ||
|
||||
chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf ("%s:illegal fragment length\n"
|
||||
, NET_AdrToString (chan->remoteAddress ) );
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
memcpy( chan->fragmentBuffer + chan->fragmentLength,
|
||||
msg->data + msg->readcount, fragmentLength );
|
||||
|
||||
chan->fragmentLength += fragmentLength;
|
||||
|
||||
// if this wasn't the last fragment, don't process anything
|
||||
if ( fragmentLength == FRAGMENT_SIZE ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if ( chan->fragmentLength > msg->maxsize ) {
|
||||
Com_Printf( "%s:fragmentLength %i > msg->maxsize\n"
|
||||
, NET_AdrToString (chan->remoteAddress ),
|
||||
chan->fragmentLength );
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// copy the full message over the partial fragment
|
||||
|
||||
// make sure the sequence number is still there
|
||||
*(int *)msg->data = LittleLong( sequence );
|
||||
|
||||
memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength );
|
||||
msg->cursize = chan->fragmentLength + 4;
|
||||
chan->fragmentLength = 0;
|
||||
msg->readcount = 4; // past the sequence number
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
//
|
||||
// the message can now be read from the current message pointer
|
||||
//
|
||||
chan->incomingSequence = sequence;
|
||||
chan->incomingAcknowledged = sequence_ack;
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
||||
/*
|
||||
===================
|
||||
NET_CompareBaseAdr
|
||||
|
||||
Compares without the port
|
||||
===================
|
||||
*/
|
||||
qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b)
|
||||
{
|
||||
if (a.type != b.type)
|
||||
return qfalse;
|
||||
|
||||
if (a.type == NA_LOOPBACK)
|
||||
return qtrue;
|
||||
|
||||
Com_Printf ("NET_CompareBaseAdr: bad address type\n");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
const char *NET_AdrToString (netadr_t a)
|
||||
{
|
||||
static char s[64];
|
||||
|
||||
if (a.type == NA_LOOPBACK) {
|
||||
Com_sprintf (s, sizeof(s), "loopback");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
qboolean NET_CompareAdr (netadr_t a, netadr_t b)
|
||||
{
|
||||
if (a.type != b.type)
|
||||
return qfalse;
|
||||
|
||||
if (a.type == NA_LOOPBACK)
|
||||
return qtrue;
|
||||
|
||||
Com_Printf ("NET_CompareAdr: bad address type\n");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
qboolean NET_IsLocalAddress( netadr_t adr ) {
|
||||
return adr.type == NA_LOOPBACK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
LOOPBACK BUFFERS FOR LOCAL PLAYER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message)
|
||||
{
|
||||
int i;
|
||||
loopback_t *loop;
|
||||
|
||||
loop = &loopbacks[sock];
|
||||
|
||||
//If read and write positions are the same, nothing left to read.
|
||||
if (loop->get == loop->send)
|
||||
return qfalse;
|
||||
|
||||
//Get read position. Wrap if too close to end.
|
||||
i = loop->get;
|
||||
if(i > MAX_LOOPDATA - 4) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
//Get length of packet.
|
||||
int length = *(int*)(loop->loopData + i);
|
||||
i += 4;
|
||||
|
||||
//See if entire packet is at end of buffer or part is at the beginning.
|
||||
if(i + length <= MAX_LOOPDATA) {
|
||||
//Everything fits, full copy.
|
||||
memcpy (net_message->data, loop->loopData + i, length);
|
||||
net_message->cursize = length;
|
||||
i += length;
|
||||
loop->get = i;
|
||||
} else {
|
||||
//Doesn't all fit, partial copy
|
||||
const int copyToEnd = MAX_LOOPDATA - i;
|
||||
memcpy (net_message->data, loop->loopData + i, copyToEnd);
|
||||
memcpy ((char*)net_message->data + copyToEnd,
|
||||
loop->loopData, length - copyToEnd);
|
||||
net_message->cursize = length;
|
||||
loop->get = length - copyToEnd;
|
||||
}
|
||||
|
||||
memset (net_from, 0, sizeof(*net_from));
|
||||
net_from->type = NA_LOOPBACK;
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to)
|
||||
{
|
||||
int i;
|
||||
loopback_t *loop;
|
||||
|
||||
loop = &loopbacks[sock^1];
|
||||
|
||||
//Make sure there is enough free space in the buffer.
|
||||
int freeSpace;
|
||||
if(loop->send >= loop->get) {
|
||||
freeSpace = MAX_LOOPDATA - (loop->send - loop->get);
|
||||
} else {
|
||||
freeSpace = loop->get - loop->send;
|
||||
}
|
||||
|
||||
assert(freeSpace > length);
|
||||
|
||||
//Get write position. Wrap around if too close to end.
|
||||
i = loop->send;
|
||||
if(i > MAX_LOOPDATA - 4) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
//Write length of packet.
|
||||
*(int*)(loop->loopData + i) = length;
|
||||
i += 4;
|
||||
|
||||
//See if the whole packet will fit on the end of the buffer or if we
|
||||
//need to write part of it back at the beginning.
|
||||
if(i + length <= MAX_LOOPDATA) {
|
||||
//Everything fits, full copy.
|
||||
memcpy (loop->loopData + i, data, length);
|
||||
i += length;
|
||||
loop->send = i;
|
||||
} else {
|
||||
//Doesn't all fit, partial copy
|
||||
int copyToEnd = MAX_LOOPDATA - i;
|
||||
memcpy(loop->loopData + i, data, copyToEnd);
|
||||
memcpy(loop->loopData, (char*)data + copyToEnd, length - copyToEnd);
|
||||
loop->send = length - copyToEnd;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) {
|
||||
|
||||
// sequenced packets are shown in netchan, so just show oob
|
||||
if ( showpackets->integer && *(int *)data == -1 ) {
|
||||
Com_Printf ("send packet %4i\n", length);
|
||||
}
|
||||
|
||||
if ( to.type == NA_LOOPBACK ) {
|
||||
NET_SendLoopPacket (sock, length, data, to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
NET_OutOfBandPrint
|
||||
|
||||
Sends a text message in an out-of-band datagram
|
||||
================
|
||||
*/
|
||||
void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) {
|
||||
va_list argptr;
|
||||
char string[MAX_MSGLEN];
|
||||
|
||||
// set the header
|
||||
string[0] = (char) 0xff;
|
||||
string[1] = (char) 0xff;
|
||||
string[2] = (char) 0xff;
|
||||
string[3] = (char) 0xff;
|
||||
|
||||
va_start( argptr, format );
|
||||
vsprintf( string+4, format, argptr );
|
||||
va_end( argptr );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( sock, strlen( string ), string, adr );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
NET_StringToAdr
|
||||
|
||||
Traps "localhost" for loopback, passes everything else to system
|
||||
=============
|
||||
*/
|
||||
qboolean NET_StringToAdr( const char *s, netadr_t *a ) {
|
||||
if (!strcmp (s, "localhost")) {
|
||||
memset (a, 0, sizeof(*a));
|
||||
a->type = NA_LOOPBACK;
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
a->type = NA_BAD;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
17
code/qcommon/platform.h
Normal file
17
code/qcommon/platform.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Simple header file to dispatch to the relevant platform API headers
|
||||
#ifndef _PLATFORM_H
|
||||
#define _PLATFORM_H
|
||||
|
||||
#if defined(_XBOX)
|
||||
#include <xtl.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#endif
|
||||
|
||||
#if defined(_WINDOWS)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
855
code/qcommon/qcommon.h
Normal file
855
code/qcommon/qcommon.h
Normal file
@@ -0,0 +1,855 @@
|
||||
// qcommon.h -- definitions common between client and server, but not game.or ref modules
|
||||
#ifndef __QCOMMON_H__
|
||||
#define __QCOMMON_H__
|
||||
|
||||
#include "stringed_ingame.h"
|
||||
#include "../qcommon/cm_public.h"
|
||||
|
||||
|
||||
// some zone mem debugging stuff
|
||||
#ifndef FINAL_BUILD
|
||||
#ifdef _DEBUG
|
||||
//
|
||||
// both of these should be REM'd unless you specifically need them...
|
||||
//
|
||||
//#define DEBUG_ZONE_ALLOCS // adds __FILE__ and __LINE__ info to zone blocks, to see who's leaking
|
||||
//#define DETAILED_ZONE_DEBUG_CODE // this slows things down a LOT, and is only for tracking nasty double-freeing Z_Malloc bugs
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
//
|
||||
// msg.c
|
||||
//
|
||||
typedef struct {
|
||||
qboolean allowoverflow; // if false, do a Com_Error
|
||||
qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set)
|
||||
byte *data;
|
||||
int maxsize;
|
||||
int cursize;
|
||||
int readcount;
|
||||
int bit; // for bitwise reads and writes
|
||||
} msg_t;
|
||||
|
||||
void MSG_Init (msg_t *buf, byte *data, int length);
|
||||
void MSG_Clear (msg_t *buf);
|
||||
void *MSG_GetSpace (msg_t *buf, int length);
|
||||
void MSG_WriteData (msg_t *buf, const void *data, int length);
|
||||
|
||||
|
||||
struct usercmd_s;
|
||||
struct entityState_s;
|
||||
struct playerState_s;
|
||||
|
||||
void MSG_WriteBits( msg_t *msg, int value, int bits );
|
||||
|
||||
void MSG_WriteByte (msg_t *sb, int c);
|
||||
void MSG_WriteShort (msg_t *sb, int c);
|
||||
void MSG_WriteLong (msg_t *sb, int c);
|
||||
void MSG_WriteString (msg_t *sb, const char *s);
|
||||
|
||||
void MSG_BeginReading (msg_t *sb);
|
||||
|
||||
int MSG_ReadBits( msg_t *msg, int bits );
|
||||
|
||||
int MSG_ReadByte (msg_t *sb);
|
||||
int MSG_ReadShort (msg_t *sb);
|
||||
int MSG_ReadLong (msg_t *sb);
|
||||
char *MSG_ReadString (msg_t *sb);
|
||||
char *MSG_ReadStringLine (msg_t *sb);
|
||||
void MSG_ReadData (msg_t *sb, void *buffer, int size);
|
||||
|
||||
|
||||
void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
|
||||
void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
|
||||
|
||||
void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to
|
||||
, qboolean force );
|
||||
void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to,
|
||||
int number );
|
||||
void MSG_ReadEntity( msg_t *msg, entityState_t *to);
|
||||
void MSG_WriteEntity( msg_t *msg, entityState_t *to, int removeNum);
|
||||
|
||||
void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
|
||||
void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
#ifdef _M_IX86
|
||||
//
|
||||
// optimised stuff for Intel, since most of our data is in that format anyway...
|
||||
//
|
||||
extern short BigShort (short l);
|
||||
extern int BigLong (int l);
|
||||
extern float BigFloat (float l);
|
||||
#define LittleShort(l) l
|
||||
#define LittleLong(l) l
|
||||
#define LittleFloat(l) l
|
||||
//
|
||||
#else
|
||||
//
|
||||
// standard smart-swap code...
|
||||
//
|
||||
extern short BigShort (short l);
|
||||
extern short LittleShort (short l);
|
||||
extern int BigLong (int l);
|
||||
extern int LittleLong (int l);
|
||||
extern float BigFloat (float l);
|
||||
extern float LittleFloat (float l);
|
||||
//
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
NET
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
#ifdef _XBOX
|
||||
#define PACKET_BACKUP 2
|
||||
#else
|
||||
#define PACKET_BACKUP 16 // number of old messages that must be kept on client and
|
||||
#endif // server for delta comrpession and ping estimation
|
||||
#define PACKET_MASK (PACKET_BACKUP-1)
|
||||
|
||||
#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet
|
||||
|
||||
#define PORT_ANY -1
|
||||
|
||||
#define MAX_RELIABLE_COMMANDS 64 // max string commands buffered for restransmit
|
||||
|
||||
typedef enum {
|
||||
NA_BAD, // an address lookup failed
|
||||
NA_LOOPBACK,
|
||||
} netadrtype_t;
|
||||
|
||||
typedef enum {
|
||||
NS_CLIENT,
|
||||
NS_SERVER
|
||||
} netsrc_t;
|
||||
|
||||
typedef struct {
|
||||
netadrtype_t type;
|
||||
|
||||
unsigned short port;
|
||||
} netadr_t;
|
||||
|
||||
void NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to);
|
||||
void NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...);
|
||||
|
||||
qboolean NET_CompareAdr (netadr_t a, netadr_t b);
|
||||
qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b);
|
||||
qboolean NET_IsLocalAddress (netadr_t adr);
|
||||
qboolean NET_IsLANAddress (netadr_t adr);
|
||||
const char *NET_AdrToString (netadr_t a);
|
||||
qboolean NET_StringToAdr ( const char *s, netadr_t *a);
|
||||
qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message);
|
||||
|
||||
|
||||
#define MAX_MSGLEN (1*17408) // max length of a message, which may
|
||||
//#define MAX_MSGLEN (3*16384) // max length of a message, which may
|
||||
// be fragmented into multiple packets
|
||||
|
||||
|
||||
/*
|
||||
Netchan handles packet fragmentation and out of order / duplicate suppression
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
netsrc_t sock;
|
||||
|
||||
int dropped; // between last packet and previous
|
||||
|
||||
netadr_t remoteAddress;
|
||||
int qport; // qport value to write when transmitting
|
||||
|
||||
// sequencing variables
|
||||
int incomingSequence;
|
||||
int incomingAcknowledged;
|
||||
|
||||
int outgoingSequence;
|
||||
|
||||
// incoming fragment assembly buffer
|
||||
int fragmentSequence;
|
||||
int fragmentLength;
|
||||
byte fragmentBuffer[MAX_MSGLEN];
|
||||
} netchan_t;
|
||||
|
||||
void Netchan_Init( int qport );
|
||||
void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport );
|
||||
|
||||
void Netchan_Transmit( netchan_t *chan, int length, const byte *data );
|
||||
qboolean Netchan_Process( netchan_t *chan, msg_t *msg );
|
||||
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
PROTOCOL
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
#define PROTOCOL_VERSION 40
|
||||
|
||||
#define PORT_SERVER 27960
|
||||
|
||||
// the svc_strings[] array in cl_parse.c should mirror this
|
||||
//
|
||||
// server to client
|
||||
//
|
||||
enum svc_ops_e {
|
||||
svc_bad,
|
||||
svc_nop,
|
||||
svc_gamestate,
|
||||
svc_configstring, // [short] [string] only in gamestate messages
|
||||
svc_baseline, // only in gamestate messages
|
||||
svc_serverCommand, // [string] to be executed by client game module
|
||||
svc_download, // [short] size [size bytes]
|
||||
svc_snapshot
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// client to server
|
||||
//
|
||||
enum clc_ops_e {
|
||||
clc_bad,
|
||||
clc_nop,
|
||||
clc_move, // [[usercmd_t]
|
||||
clc_clientCommand // [string] message
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
CMD
|
||||
|
||||
Command text buffering and command execution
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Any number of commands can be added in a frame, from several different sources.
|
||||
Most commands come from either keybindings or console line input, but entire text
|
||||
files can be execed.
|
||||
|
||||
*/
|
||||
|
||||
void Cbuf_Init (void);
|
||||
// allocates an initial text buffer that will grow as needed
|
||||
|
||||
void Cbuf_AddText( const char *text );
|
||||
// Adds command text at the end of the buffer, does NOT add a final \n
|
||||
|
||||
void Cbuf_ExecuteText( int exec_when, const char *text );
|
||||
// this can be used in place of either Cbuf_AddText or Cbuf_InsertText
|
||||
|
||||
void Cbuf_Execute (void);
|
||||
// Pulls off \n terminated lines of text from the command buffer and sends
|
||||
// them through Cmd_ExecuteString. Stops when the buffer is empty.
|
||||
// Normally called once per frame, but may be explicitly invoked.
|
||||
// Do not call inside a command function, or current args will be destroyed.
|
||||
|
||||
//===========================================================================
|
||||
|
||||
/*
|
||||
|
||||
Command execution takes a null terminated string, breaks it into tokens,
|
||||
then searches for a command or variable that matches the first token.
|
||||
|
||||
*/
|
||||
|
||||
typedef void (*xcommand_t) (void);
|
||||
|
||||
void Cmd_Init (void);
|
||||
|
||||
void Cmd_AddCommand( const char *cmd_name, xcommand_t function );
|
||||
// called by the init functions of other parts of the program to
|
||||
// register commands and functions to call for them.
|
||||
// The cmd_name is referenced later, so it should not be in temp memory
|
||||
// if function is NULL, the command will be forwarded to the server
|
||||
// as a clc_clientCommand instead of executed locally
|
||||
|
||||
void Cmd_RemoveCommand( const char *cmd_name );
|
||||
|
||||
char *Cmd_CompleteCommand( const char *partial );
|
||||
// attempts to match a partial command for automatic command line completion
|
||||
// returns NULL if nothing fits
|
||||
|
||||
int Cmd_Argc (void);
|
||||
char *Cmd_Argv (int arg);
|
||||
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength );
|
||||
char *Cmd_Args (void);
|
||||
void Cmd_ArgsBuffer( char *buffer, int bufferLength );
|
||||
// The functions that execute commands get their parameters with these
|
||||
// functions. Cmd_Argv () will return an empty string, not a NULL
|
||||
// if arg > argc, so string operations are allways safe.
|
||||
|
||||
void Cmd_TokenizeString( const char *text );
|
||||
// Takes a null terminated string. Does not need to be /n terminated.
|
||||
// breaks the string up into arg tokens.
|
||||
|
||||
void Cmd_ExecuteString( const char *text );
|
||||
// Parses a single line of text into arguments and tries to execute it
|
||||
// as if it was typed at the console
|
||||
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
CVAR
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
cvar_t variables are used to hold scalar or string variables that can be changed
|
||||
or displayed at the console or prog code as well as accessed directly
|
||||
in C code.
|
||||
|
||||
The user can access cvars from the console in three ways:
|
||||
r_draworder prints the current value
|
||||
r_draworder 0 sets the current value to 0
|
||||
set r_draworder 0 as above, but creates the cvar if not present
|
||||
|
||||
Cvars are restricted from having the same names as commands to keep this
|
||||
interface from being ambiguous.
|
||||
|
||||
The are also occasionally used to communicated information between different
|
||||
modules of the program.
|
||||
|
||||
*/
|
||||
|
||||
cvar_t *Cvar_Get( const char *var_name, const char *value, int flags );
|
||||
// creates the variable if it doesn't exist, or returns the existing one
|
||||
// if it exists, the value will not be changed, but flags will be ORed in
|
||||
// that allows variables to be unarchived without needing bitflags
|
||||
// if value is "", the value will not override a previously set value.
|
||||
|
||||
void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
|
||||
// basically a slightly modified Cvar_Get for the interpreted modules
|
||||
|
||||
void Cvar_Update( vmCvar_t *vmCvar );
|
||||
// updates an interpreted modules' version of a cvar
|
||||
|
||||
void Cvar_Set( const char *var_name, const char *value );
|
||||
// will create the variable with no flags if it doesn't exist
|
||||
|
||||
void Cvar_SetValue( const char *var_name, float value );
|
||||
// expands value to a string and calls Cvar_Set
|
||||
|
||||
float Cvar_VariableValue( const char *var_name );
|
||||
int Cvar_VariableIntegerValue( const char *var_name );
|
||||
// returns 0 if not defined or non numeric
|
||||
|
||||
char *Cvar_VariableString( const char *var_name );
|
||||
void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
|
||||
// returns an empty string if not defined
|
||||
|
||||
char *Cvar_CompleteVariable( const char *partial );
|
||||
// attempts to match a partial variable name for command line completion
|
||||
// returns NULL if nothing fits
|
||||
|
||||
void Cvar_Reset( const char *var_name );
|
||||
|
||||
void Cvar_SetCheatState( void );
|
||||
// reset all testing vars to a safe value
|
||||
|
||||
qboolean Cvar_Command( void );
|
||||
// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known
|
||||
// command. Returns true if the command was a variable reference that
|
||||
// was handled. (print or change)
|
||||
|
||||
void Cvar_WriteVariables( fileHandle_t f );
|
||||
// writes lines containing "set variable value" for all variables
|
||||
// with the archive flag set to true.
|
||||
|
||||
void Cvar_Init( void );
|
||||
|
||||
char *Cvar_InfoString( int bit );
|
||||
// returns an info string containing all the cvars that have the given bit set
|
||||
// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc )
|
||||
void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize );
|
||||
|
||||
void Cvar_Restart_f( void );
|
||||
|
||||
extern int cvar_modifiedFlags;
|
||||
// whenever a cvar is modifed, its flags will be OR'd into this, so
|
||||
// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO,
|
||||
// etc, variables have been modified since the last check. The bit
|
||||
// can then be cleared to allow another change detection.
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
FILESYSTEM
|
||||
|
||||
No stdio calls should be used by any part of the game, because
|
||||
we need to deal with all sorts of directory and seperator char
|
||||
issues.
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
qboolean FS_Initialized();
|
||||
|
||||
void FS_InitFilesystem (void);
|
||||
|
||||
char **FS_ListFiles( const char *directory, const char *extension, int *numfiles );
|
||||
// directory should not have either a leading or trailing /
|
||||
// if extension is "/", only subdirectories will be returned
|
||||
// the returned files will not include any directories or /
|
||||
|
||||
void FS_FreeFileList( char **filelist );
|
||||
|
||||
int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
|
||||
|
||||
fileHandle_t FS_FOpenFileWrite( const char *qpath );
|
||||
// will properly create any needed paths and deal with seperater character issues
|
||||
|
||||
fileHandle_t FS_FOpenFileAppend( const char *filename ); // this was present already, but no public proto
|
||||
|
||||
qboolean FS_GetExtendedInfo_FOpenFileRead(const char *filename, char **ppsFilename, int *piOffset);
|
||||
//return value is success of opening file, then ppsFilename and piOffset are valid
|
||||
|
||||
int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE );
|
||||
// if uniqueFILE is true, then a new FILE will be fopened even if the file
|
||||
// is found in an already open pak file. If uniqueFILE is false, you must call
|
||||
// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed
|
||||
// It is generally safe to always set uniqueFILE to true, because the majority of
|
||||
// file IO goes through FS_ReadFile, which Does The Right Thing already.
|
||||
|
||||
int FS_FileIsInPAK(const char *filename );
|
||||
// returns 1 if a file is in the PAK file, otherwise -1
|
||||
|
||||
int FS_Write( const void *buffer, int len, fileHandle_t f );
|
||||
|
||||
int FS_Read( void *buffer, int len, fileHandle_t f );
|
||||
// properly handles partial reads and reads from other dlls
|
||||
|
||||
void FS_FCloseFile( fileHandle_t f );
|
||||
// note: you can't just fclose from another DLL, due to MS libc issues
|
||||
|
||||
int FS_ReadFile( const char *qpath, void **buffer );
|
||||
// returns the length of the file
|
||||
// a null buffer will just return the file length without loading
|
||||
// as a quick check for existance. -1 length == not present
|
||||
// A 0 byte will always be appended at the end, so string ops are safe.
|
||||
// the buffer should be considered read-only, because it may be cached
|
||||
// for other uses.
|
||||
|
||||
void FS_ForceFlush( fileHandle_t f );
|
||||
// forces flush on files we're writing to.
|
||||
|
||||
void FS_FreeFile( void *buffer );
|
||||
// frees the memory returned by FS_ReadFile
|
||||
|
||||
void FS_WriteFile( const char *qpath, const void *buffer, int size );
|
||||
// writes a complete file, creating any subdirectories needed
|
||||
|
||||
int FS_filelength( fileHandle_t f );
|
||||
// doesn't work for files that are opened from a pack file
|
||||
|
||||
int FS_FTell( fileHandle_t f );
|
||||
// where are we?
|
||||
|
||||
void FS_Flush( fileHandle_t f );
|
||||
|
||||
void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... );
|
||||
// like fprintf
|
||||
|
||||
int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode );
|
||||
// opens a file for reading, writing, or appending depending on the value of mode
|
||||
|
||||
int FS_Seek( fileHandle_t f, long offset, int origin );
|
||||
// seek on a file (doesn't work for zip files!!!!!!!!)
|
||||
|
||||
|
||||
// These 2 are generally only used by the save games, filenames are local (eg "saves/blah.sav")
|
||||
//
|
||||
void FS_DeleteUserGenFile( const char *filename );
|
||||
qboolean FS_MoveUserGenFile ( const char *filename_src, const char *filename_dst );
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
MISC
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
//==========================================================
|
||||
//
|
||||
// NOTE NOTE NOTE!!!!!!!!!!!!!
|
||||
//
|
||||
// Any CPUID_XXXX defined as higher than CPUID_INTEL_MMX *must* have MMX support (eg like CPUID_AMD_3DNOW (0x30) has),
|
||||
// this allows convenient MMX capability checking. If you for some reason want to support some new processor that does
|
||||
// *NOT* have MMX (yeah, right), then define it as a lower number. -slc
|
||||
//
|
||||
// ( These values are returned by Sys_GetProcessorId )
|
||||
//
|
||||
#define CPUID_GENERIC 0 // any unrecognized processor
|
||||
|
||||
#define CPUID_AXP 0x10
|
||||
|
||||
#define CPUID_INTEL_UNSUPPORTED 0x20 // Intel 386/486
|
||||
#define CPUID_INTEL_PENTIUM 0x21 // Intel Pentium or PPro
|
||||
#define CPUID_INTEL_MMX 0x22 // Intel Pentium/MMX or P2/MMX
|
||||
#define CPUID_INTEL_KATMAI 0x23 // Intel Katmai
|
||||
#define CPUID_INTEL_WILLIAMETTE 0x24 // Intel Williamette
|
||||
|
||||
#define CPUID_AMD_3DNOW 0x30 // AMD K6 3DNOW!
|
||||
//
|
||||
//==========================================================
|
||||
|
||||
#define RoundUp(N, M) ((N) + ((unsigned int)(M)) - (((unsigned int)(N)) % ((unsigned int)(M))))
|
||||
#define RoundDown(N, M) ((N) - (((unsigned int)(N)) % ((unsigned int)(M))))
|
||||
|
||||
char *CopyString( const char *in );
|
||||
void Info_Print( const char *s );
|
||||
|
||||
void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *));
|
||||
void Com_EndRedirect( void );
|
||||
void QDECL Com_Printf( const char *fmt, ... );
|
||||
void QDECL Com_PrintfAlways( const char *fmt, ... );
|
||||
void QDECL Com_DPrintf( const char *fmt, ... );
|
||||
void QDECL Com_Error( int code, const char *fmt, ... );
|
||||
void Com_Quit_f( void );
|
||||
int Com_EventLoop( void );
|
||||
int Com_Milliseconds( void ); // will be journaled properly
|
||||
unsigned Com_BlockChecksum( const void *buffer, int length );
|
||||
int Com_Filter(char *filter, char *name, int casesensitive);
|
||||
|
||||
void Com_StartupVariable( const char *match );
|
||||
// checks for and removes command line "+set var arg" constructs
|
||||
// if match is NULL, all set commands will be executed, otherwise
|
||||
// only a set with the exact name. Only used during startup.
|
||||
|
||||
|
||||
extern cvar_t *com_developer;
|
||||
extern cvar_t *com_speeds;
|
||||
extern cvar_t *com_timescale;
|
||||
extern cvar_t *com_sv_running;
|
||||
extern cvar_t *com_cl_running;
|
||||
extern cvar_t *com_viewlog; // 0 = hidden, 1 = visible, 2 = minimized
|
||||
extern cvar_t *com_version;
|
||||
|
||||
// both client and server must agree to pause
|
||||
extern cvar_t *cl_paused;
|
||||
extern cvar_t *sv_paused;
|
||||
|
||||
// com_speeds times
|
||||
extern int time_game;
|
||||
extern int time_frontend;
|
||||
extern int time_backend; // renderer backend time
|
||||
|
||||
extern int timeInTrace;
|
||||
extern int timeInPVSCheck;
|
||||
extern int numTraces;
|
||||
|
||||
extern int com_frameTime;
|
||||
extern int com_frameMsec;
|
||||
|
||||
extern qboolean com_errorEntered;
|
||||
|
||||
|
||||
#ifndef _XBOX
|
||||
extern fileHandle_t com_journalFile;
|
||||
extern fileHandle_t com_journalDataFile;
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
--- low memory ----
|
||||
server vm
|
||||
server clipmap
|
||||
---mark---
|
||||
renderer initialization (shaders, etc)
|
||||
UI vm
|
||||
cgame vm
|
||||
renderer map
|
||||
renderer models
|
||||
|
||||
---free---
|
||||
|
||||
temp file loading
|
||||
--- high memory ---
|
||||
|
||||
*/
|
||||
int Z_Validate( void ); // also used to insure all of these are paged in
|
||||
int Z_MemSize ( memtag_t eTag );
|
||||
void Z_TagFree ( memtag_t eTag );
|
||||
int Z_Free ( void *ptr ); //returns bytes freed
|
||||
int Z_Size ( void *pvAddress);
|
||||
void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag );
|
||||
qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag);
|
||||
qboolean Z_IsFromTempPool(void *pvAddress);
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
|
||||
void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine );
|
||||
void *_D_S_Malloc ( int iSize, const char *psFile, int iLine );
|
||||
void _D_Z_Label ( const void *pvAddress, const char *pslabel );
|
||||
|
||||
#define Z_Malloc(_iSize, _eTag, _bZeroit) _D_Z_Malloc (_iSize, _eTag, _bZeroit, __FILE__, __LINE__)
|
||||
#define S_Malloc(_iSize) _D_S_Malloc (_iSize, __FILE__, __LINE__) // NOT 0 filled memory only for small allocations
|
||||
|
||||
#define Z_Label(_ptr, _label) _D_Z_Label (_ptr, _label)
|
||||
|
||||
#else
|
||||
|
||||
void *Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, int iAlign = 4); // return memory NOT zero-filled by default
|
||||
void *S_Malloc ( int iSize ); // NOT 0 filled memory only for small allocations
|
||||
|
||||
#define Z_Label(_ptr, _label) /* */
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void Hunk_Clear( void );
|
||||
void Hunk_ClearToMark( void );
|
||||
void Hunk_SetMark( void );
|
||||
// note the opposite default for 'bZeroIt' in Hunk_Alloc to Z_Malloc, since Hunk_Alloc always used to memset(0)...
|
||||
//
|
||||
inline void *Hunk_Alloc( int size, qboolean bZeroIt = qtrue)
|
||||
{
|
||||
return Z_Malloc(size, TAG_HUNKALLOC, bZeroIt);
|
||||
}
|
||||
|
||||
// Used to re-tag new/delete allocations:
|
||||
void Z_PushNewDeleteTag( memtag_t eTag );
|
||||
void Z_PopNewDeleteTag( void );
|
||||
|
||||
void Com_TouchMemory( void );
|
||||
|
||||
// commandLine should not include the executable name (argv[0])
|
||||
void Com_SetOrgAngles(vec3_t org,vec3_t angles);
|
||||
void Com_Init( char *commandLine );
|
||||
void Com_Frame( void );
|
||||
void Com_Shutdown( void );
|
||||
void Com_ShutdownZoneMemory(void);
|
||||
void Com_ShutdownHunkMemory(void);
|
||||
|
||||
bool Com_ParseTextFile(const char *file, class CGenericParser2 &parser, bool cleanFirst = true);
|
||||
CGenericParser2 *Com_ParseTextFile(const char *file, bool cleanFirst, bool writeable);
|
||||
void Com_ParseTextFileDestroy(class CGenericParser2 &parser);
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
CLIENT / SERVER SYSTEMS
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
//
|
||||
// client interface
|
||||
//
|
||||
void CL_InitKeyCommands( void );
|
||||
// the keyboard binding interface must be setup before execing
|
||||
// config files, but the rest of client startup will happen later
|
||||
|
||||
void CL_Init( void );
|
||||
void CL_Disconnect( void );
|
||||
void CL_Shutdown( void );
|
||||
void CL_Frame( int msec,float fractionMsec );
|
||||
qboolean CL_GameCommand( void );
|
||||
void CL_KeyEvent (int key, qboolean down, unsigned time);
|
||||
|
||||
void CL_CharEvent( int key );
|
||||
// char events are for field typing, not game control
|
||||
|
||||
void CL_MouseEvent( int dx, int dy, int time );
|
||||
|
||||
void CL_JoystickEvent( int axis, int value, int time );
|
||||
|
||||
void CL_PacketEvent( netadr_t from, msg_t *msg );
|
||||
|
||||
void CL_ConsolePrint( char *text );
|
||||
|
||||
void CL_MapLoading( void );
|
||||
// do a screen update before starting to load a map
|
||||
// when the server is going to load a new map, the entire hunk
|
||||
// will be cleared, so the client must shutdown cgame, ui, and
|
||||
// the renderer
|
||||
|
||||
void CL_ForwardCommandToServer( void );
|
||||
// adds the current command line as a clc_clientCommand to the client message.
|
||||
// things like godmode, noclip, etc, are commands directed to the server,
|
||||
// so when they are typed in at the console, they will need to be forwarded.
|
||||
|
||||
void CL_FlushMemory( void );
|
||||
// dump all memory on an error
|
||||
|
||||
void CL_StartHunkUsers( void );
|
||||
|
||||
void Key_WriteBindings( fileHandle_t f );
|
||||
// for writing the config files
|
||||
|
||||
void S_ClearSoundBuffer( void );
|
||||
// call before filesystem access
|
||||
|
||||
void SCR_DebugGraph (float value, int color); // FIXME: move logging to common?
|
||||
|
||||
|
||||
//
|
||||
// server interface
|
||||
//
|
||||
void SV_Init( void );
|
||||
void SV_Shutdown( char *finalmsg );
|
||||
void SV_Frame( int msec,float fractionMsec);
|
||||
void SV_PacketEvent( netadr_t from, msg_t *msg );
|
||||
qboolean SV_GameCommand( void );
|
||||
|
||||
|
||||
//
|
||||
// UI interface
|
||||
//
|
||||
qboolean UI_GameCommand( void );
|
||||
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
||||
NON-PORTABLE SYSTEM SERVICES
|
||||
|
||||
==============================================================
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
AXIS_SIDE,
|
||||
AXIS_FORWARD,
|
||||
AXIS_UP,
|
||||
AXIS_ROLL,
|
||||
AXIS_YAW,
|
||||
AXIS_PITCH,
|
||||
MAX_JOYSTICK_AXIS
|
||||
} joystickAxis_t;
|
||||
|
||||
typedef enum {
|
||||
SE_NONE, // evTime is still valid
|
||||
SE_KEY, // evValue is a key code, evValue2 is the down flag
|
||||
SE_CHAR, // evValue is an ascii char
|
||||
SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves
|
||||
SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127)
|
||||
SE_CONSOLE, // evPtr is a char*
|
||||
SE_PACKET // evPtr is a netadr_t followed by data bytes to evPtrLength
|
||||
} sysEventType_t;
|
||||
|
||||
typedef struct {
|
||||
int evTime;
|
||||
sysEventType_t evType;
|
||||
int evValue, evValue2;
|
||||
int evPtrLength; // bytes of data pointed to by evPtr, for journaling
|
||||
void *evPtr; // this must be manually freed if not NULL
|
||||
} sysEvent_t;
|
||||
|
||||
sysEvent_t Sys_GetEvent( void );
|
||||
|
||||
void Sys_Init (void);
|
||||
|
||||
char *Sys_GetCurrentUser( void );
|
||||
|
||||
void QDECL Sys_Error( const char *error, ...);
|
||||
void Sys_Quit (void);
|
||||
char *Sys_GetClipboardData( void ); // note that this isn't journaled...
|
||||
|
||||
void Sys_Print( const char *msg );
|
||||
#ifdef _XBOX
|
||||
void Sys_Log( const char *file, const char *msg );
|
||||
void Sys_Log( const char *file, const void *buffer, int size, bool flush );
|
||||
#endif
|
||||
|
||||
// Sys_Milliseconds should only be used for profiling purposes,
|
||||
// any game related timing information should come from event timestamps
|
||||
int Sys_Milliseconds (void);
|
||||
|
||||
|
||||
// the system console is shown when a dedicated server is running
|
||||
void Sys_DisplaySystemConsole( qboolean show );
|
||||
|
||||
int Sys_GetProcessorId( void );
|
||||
|
||||
void Sys_BeginStreamedFile( fileHandle_t f, int readahead );
|
||||
void Sys_EndStreamedFile( fileHandle_t f );
|
||||
int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f );
|
||||
void Sys_StreamSeek( fileHandle_t f, int offset, int origin );
|
||||
|
||||
void Sys_ShowConsole( int level, qboolean quitOnClose );
|
||||
void Sys_SetErrorText( const char *text );
|
||||
|
||||
qboolean Sys_CheckCD( void );
|
||||
|
||||
void Sys_Mkdir( const char *path );
|
||||
char *Sys_Cwd( void );
|
||||
char *Sys_DefaultCDPath(void);
|
||||
char *Sys_DefaultBasePath(void);
|
||||
|
||||
char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs );
|
||||
void Sys_FreeFileList( char **filelist );
|
||||
|
||||
void Sys_BeginProfiling( void );
|
||||
void Sys_EndProfiling( void );
|
||||
|
||||
qboolean Sys_LowPhysicalMemory();
|
||||
qboolean Sys_FileOutOfDate( LPCSTR psFinalFileName /* dest */, LPCSTR psDataFileName /* src */ );
|
||||
qboolean Sys_CopyFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, qboolean bOverwrite);
|
||||
|
||||
|
||||
//byte* SCR_GetScreenshot(qboolean *qValid);
|
||||
//void SCR_SetScreenshot(const byte *pbData, int w, int h);
|
||||
//byte* SCR_TempRawImage_ReadFromFile(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip);
|
||||
//void SCR_TempRawImage_CleanUp();
|
||||
|
||||
inline int Round(float value)
|
||||
{
|
||||
return((int)floorf(value + 0.5f));
|
||||
}
|
||||
|
||||
|
||||
#ifdef _XBOX
|
||||
//////////////////////////////
|
||||
//
|
||||
// Map Lump Loader
|
||||
//
|
||||
struct Lump
|
||||
{
|
||||
void* data;
|
||||
int len;
|
||||
|
||||
Lump() : data(NULL), len(0) {}
|
||||
~Lump() { clear(); }
|
||||
|
||||
void load(const char* map, const char* lump)
|
||||
{
|
||||
clear();
|
||||
|
||||
char path[MAX_QPATH];
|
||||
Com_sprintf(path, MAX_QPATH, "%s/%s.mle", map, lump);
|
||||
|
||||
len = FS_ReadFile(path, &data);
|
||||
if (len < 0) len = 0;
|
||||
}
|
||||
|
||||
void clear(void)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
FS_FreeFile(data);
|
||||
data = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif _XBOX
|
||||
|
||||
#endif //__QCOMMON_H__
|
||||
634
code/qcommon/qfiles.h
Normal file
634
code/qcommon/qfiles.h
Normal file
@@ -0,0 +1,634 @@
|
||||
#ifndef __QFILES_H__
|
||||
#define __QFILES_H__
|
||||
|
||||
//
|
||||
// qfiles.h: quake file formats
|
||||
// This file must be identical in the quake and utils directories
|
||||
//
|
||||
|
||||
// surface geometry should not exceed these limits
|
||||
#define SHADER_MAX_VERTEXES 1000
|
||||
#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
|
||||
|
||||
|
||||
// the maximum size of game reletive pathnames
|
||||
#define MAX_QPATH 64
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
QVM files
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#define VM_MAGIC 0x12721444
|
||||
typedef struct {
|
||||
int vmMagic;
|
||||
|
||||
int instructionCount;
|
||||
|
||||
int codeOffset;
|
||||
int codeLength;
|
||||
|
||||
int dataOffset;
|
||||
int dataLength;
|
||||
int litLength; // ( dataLength - litLength ) should be byteswapped on load
|
||||
int bssLength; // zero filled memory appended to datalength
|
||||
} vmHeader_t;
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
PCX files are used for 8 bit images
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char manufacturer;
|
||||
char version;
|
||||
char encoding;
|
||||
char bits_per_pixel;
|
||||
unsigned short xmin,ymin,xmax,ymax;
|
||||
unsigned short hres,vres;
|
||||
unsigned char palette[48];
|
||||
char reserved;
|
||||
char color_planes;
|
||||
unsigned short bytes_per_line;
|
||||
unsigned short palette_type;
|
||||
char filler[58];
|
||||
unsigned char data; // unbounded
|
||||
} pcx_t;
|
||||
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
TGA files are used for 24/32 bit images
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
typedef struct _TargaHeader {
|
||||
unsigned char id_length, colormap_type, image_type;
|
||||
unsigned short colormap_index, colormap_length;
|
||||
unsigned char colormap_size;
|
||||
unsigned short x_origin, y_origin, width, height;
|
||||
unsigned char pixel_size, attributes;
|
||||
} TargaHeader;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
.MD3 triangle model file format
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
|
||||
#define MD3_VERSION 15
|
||||
|
||||
// limits
|
||||
#define MD3_MAX_LODS 3
|
||||
#define MD3_MAX_TRIANGLES 8192 // per surface
|
||||
#define MD3_MAX_VERTS 4096 // per surface
|
||||
#define MD3_MAX_SHADERS 256 // per surface
|
||||
#define MD3_MAX_FRAMES 1024 // per model
|
||||
#define MD3_MAX_SURFACES 32 + 32 // per model
|
||||
#define MD3_MAX_TAGS 16 // per frame
|
||||
|
||||
// vertex scales
|
||||
#define MD3_XYZ_SCALE (1.0/64)
|
||||
|
||||
typedef struct md3Frame_s {
|
||||
vec3_t bounds[2];
|
||||
vec3_t localOrigin;
|
||||
float radius;
|
||||
char name[16];
|
||||
} md3Frame_t;
|
||||
|
||||
typedef struct md3Tag_s {
|
||||
char name[MAX_QPATH]; // tag name
|
||||
vec3_t origin;
|
||||
vec3_t axis[3];
|
||||
} md3Tag_t;
|
||||
|
||||
/*
|
||||
** md3Surface_t
|
||||
**
|
||||
** CHUNK SIZE
|
||||
** header sizeof( md3Surface_t )
|
||||
** shaders sizeof( md3Shader_t ) * numShaders
|
||||
** triangles[0] sizeof( md3Triangle_t ) * numTriangles
|
||||
** st sizeof( md3St_t ) * numVerts
|
||||
** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
|
||||
*/
|
||||
typedef struct {
|
||||
int ident; //
|
||||
|
||||
char name[MAX_QPATH]; // polyset name
|
||||
|
||||
int flags;
|
||||
int numFrames; // all surfaces in a model should have the same
|
||||
|
||||
int numShaders; // all surfaces in a model should have the same
|
||||
int numVerts;
|
||||
|
||||
int numTriangles;
|
||||
int ofsTriangles;
|
||||
|
||||
int ofsShaders; // offset from start of md3Surface_t
|
||||
int ofsSt; // texture coords are common for all frames
|
||||
int ofsXyzNormals; // numVerts * numFrames
|
||||
|
||||
int ofsEnd; // next surface follows
|
||||
} md3Surface_t;
|
||||
|
||||
typedef struct {
|
||||
char name[MAX_QPATH];
|
||||
int shaderIndex; // for in-game use
|
||||
} md3Shader_t;
|
||||
|
||||
typedef struct {
|
||||
int indexes[3];
|
||||
} md3Triangle_t;
|
||||
|
||||
typedef struct {
|
||||
float st[2];
|
||||
} md3St_t;
|
||||
|
||||
typedef struct {
|
||||
short xyz[3];
|
||||
short normal;
|
||||
} md3XyzNormal_t;
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
int version;
|
||||
|
||||
char name[MAX_QPATH]; // model name
|
||||
|
||||
int flags;
|
||||
|
||||
int numFrames;
|
||||
int numTags;
|
||||
int numSurfaces;
|
||||
|
||||
int numSkins;
|
||||
|
||||
int ofsFrames; // offset for first frame
|
||||
int ofsTags; // numFrames * numTags
|
||||
int ofsSurfaces; // first surface, others follow
|
||||
|
||||
int ofsEnd; // end of file
|
||||
} md3Header_t;
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
.BSP file format
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'R')
|
||||
// little-endian "IBSP"
|
||||
|
||||
#define BSP_VERSION 1
|
||||
|
||||
|
||||
// there shouldn't be any problem with increasing these values at the
|
||||
// expense of more memory allocation in the utilities
|
||||
#define MAX_MAP_MODELS 0x400
|
||||
#define MAX_MAP_BRUSHES 0x8000
|
||||
#define MAX_MAP_ENTITIES 0x800
|
||||
#define MAX_MAP_ENTSTRING 0x40000
|
||||
#define MAX_MAP_SHADERS 0x400
|
||||
|
||||
#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match!
|
||||
#define MAX_MAP_FOGS 0x100
|
||||
#define MAX_MAP_PLANES 0x20000
|
||||
#define MAX_MAP_NODES 0x20000
|
||||
#define MAX_MAP_BRUSHSIDES 0x20000
|
||||
#define MAX_MAP_LEAFS 0x20000
|
||||
#define MAX_MAP_LEAFFACES 0x20000
|
||||
#define MAX_MAP_LEAFBRUSHES 0x40000
|
||||
#define MAX_MAP_PORTALS 0x20000
|
||||
#define MAX_MAP_LIGHTING 0x800000
|
||||
#define MAX_MAP_LIGHTGRID 65535
|
||||
#define MAX_MAP_LIGHTGRID_ARRAY 0x100000
|
||||
|
||||
#define MAX_MAP_VISIBILITY 0x400000
|
||||
|
||||
#define MAX_MAP_DRAW_SURFS 0x20000
|
||||
#define MAX_MAP_DRAW_VERTS 0x80000
|
||||
#define MAX_MAP_DRAW_INDEXES 0x80000
|
||||
|
||||
|
||||
// key / value pair sizes in the entities lump
|
||||
#define MAX_KEY 32
|
||||
#define MAX_VALUE 1024
|
||||
|
||||
// the editor uses these predefined yaw angles to orient entities up or down
|
||||
#define ANGLE_UP -1
|
||||
#define ANGLE_DOWN -2
|
||||
|
||||
#define LIGHTMAP_WIDTH 128
|
||||
#define LIGHTMAP_HEIGHT 128
|
||||
|
||||
//=============================================================================
|
||||
|
||||
#ifdef _XBOX
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct {
|
||||
float mins[3], maxs[3];
|
||||
int firstSurface;
|
||||
unsigned short numSurfaces;
|
||||
int firstBrush;
|
||||
unsigned short numBrushes;
|
||||
} dmodel_t;
|
||||
|
||||
typedef struct {
|
||||
char shader[MAX_QPATH];
|
||||
int surfaceFlags;
|
||||
int contentFlags;
|
||||
} dshader_t;
|
||||
|
||||
// planes x^1 is allways the opposite of plane x
|
||||
|
||||
typedef struct {
|
||||
float normal[3];
|
||||
float dist;
|
||||
} dplane_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum;
|
||||
short children[2]; // negative numbers are -(leafs+1), not nodes
|
||||
short mins[3]; // for frustom culling
|
||||
short maxs[3];
|
||||
} dnode_t;
|
||||
|
||||
typedef struct {
|
||||
short cluster; // -1 = opaque cluster (do I still store these?)
|
||||
signed char area;
|
||||
|
||||
short mins[3]; // for frustum culling
|
||||
short maxs[3];
|
||||
|
||||
unsigned short firstLeafSurface;
|
||||
unsigned short numLeafSurfaces;
|
||||
|
||||
unsigned short firstLeafBrush;
|
||||
unsigned short numLeafBrushes;
|
||||
} dleaf_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum; // positive plane side faces out of the leaf
|
||||
byte shaderNum;
|
||||
} dbrushside_t;
|
||||
|
||||
typedef struct {
|
||||
int firstSide;
|
||||
byte numSides;
|
||||
unsigned short shaderNum; // the shader that determines the contents flags
|
||||
} dbrush_t;
|
||||
|
||||
typedef struct {
|
||||
char shader[MAX_QPATH];
|
||||
int brushNum;
|
||||
int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
|
||||
} dfog_t;
|
||||
|
||||
// Light Style Constants
|
||||
#define MAXLIGHTMAPS 4
|
||||
#define LS_NORMAL 0x00
|
||||
#define LS_UNUSED 0xfe
|
||||
#define LS_NONE 0xff
|
||||
#define MAX_LIGHT_STYLES 64
|
||||
|
||||
typedef struct {
|
||||
float lightmap[MAXLIGHTMAPS][2];
|
||||
float st[2];
|
||||
short xyz[3];
|
||||
short normal[3];
|
||||
byte color[MAXLIGHTMAPS][4];
|
||||
} mapVert_t;
|
||||
|
||||
#define DRAWVERT_LIGHTMAP_SCALE 32768.0f
|
||||
// Change texture coordinates for TriSurfs to be even more fine grain.
|
||||
// See below for note about keeping MIN_ST and MAX_ST up to date with
|
||||
// ST_SCALE. These are in 4.12. Okay, how about 5.11? Gah. 9.7!
|
||||
//#define DRAWVERT_ST_SCALE 4096.0f
|
||||
//#define DRAWVERT_ST_SCALE 2048.0f
|
||||
#define DRAWVERT_ST_SCALE 128.0f
|
||||
|
||||
// We use a slightly different format for the fixed point texture
|
||||
// coords in Grid/Mesh drawverts: 10.6 rather than 12.4
|
||||
// To be sure that this is ok, keep the max and min values equal to
|
||||
// the largest and smallest whole numbers that can be stored using the
|
||||
// format. (ie: Don't change GRID_DRAWVERT_ST_SCALE without changing
|
||||
// the other two!) (And don't forget that we're using a bit for sign.)
|
||||
#define GRID_DRAWVERT_ST_SCALE 64.0f
|
||||
|
||||
// This master switch controls whether we use compressed (4-bit per channel)
|
||||
// vertex colors in draw and surface verts. It saves memory, but I'm switching
|
||||
// it off, because we end up with that nasty green/purple streaking effect.
|
||||
// If we ever figure out how to do it better... (1555? 565?)
|
||||
//#define COMPRESS_VERTEX_COLORS
|
||||
|
||||
typedef struct {
|
||||
short xyz[3];
|
||||
short dvst[2];
|
||||
short dvlightmap[MAXLIGHTMAPS][2];
|
||||
short normal[3];
|
||||
#ifdef _XBOX
|
||||
vec3_t tangent;
|
||||
#endif
|
||||
#ifdef COMPRESS_VERTEX_COLORS
|
||||
byte dvcolor[MAXLIGHTMAPS][1];
|
||||
#else
|
||||
byte dvcolor[MAXLIGHTMAPS][4];
|
||||
#endif
|
||||
} drawVert_t;
|
||||
|
||||
typedef struct {
|
||||
byte flags;
|
||||
byte latLong[2];
|
||||
} dgrid_t;
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
byte shaderNum;
|
||||
signed char fogNum;
|
||||
|
||||
unsigned int verts; // high 20 bits are first vert, low 12 are num verts
|
||||
unsigned int indexes; // high 20 bits are first index, low 12 are num indices
|
||||
|
||||
byte lightmapStyles[MAXLIGHTMAPS];
|
||||
byte lightmapNum[MAXLIGHTMAPS];
|
||||
|
||||
short lightmapVecs[3];
|
||||
} dface_t;
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
byte shaderNum;
|
||||
signed char fogNum;
|
||||
|
||||
unsigned int verts; // high 20 bits are first vert, low 12 are num verts
|
||||
|
||||
byte lightmapStyles[MAXLIGHTMAPS];
|
||||
byte lightmapNum[MAXLIGHTMAPS];
|
||||
|
||||
short lightmapVecs[2][3]; // for patches, [0] and [1] are lodbounds
|
||||
|
||||
byte patchWidth;
|
||||
byte patchHeight;
|
||||
} dpatch_t;
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
byte shaderNum;
|
||||
signed char fogNum;
|
||||
|
||||
unsigned int verts; // high 20 bits are first vert, low 12 are num verts
|
||||
unsigned int indexes; // high 20 bits are first index, low 12 are num indices
|
||||
|
||||
byte lightmapStyles[MAXLIGHTMAPS];
|
||||
} dtrisurf_t;
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
byte shaderNum;
|
||||
signed char fogNum;
|
||||
|
||||
short origin[3];
|
||||
short normal[3];
|
||||
byte color[3];
|
||||
} dflare_t;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#else // _XBOX
|
||||
|
||||
typedef struct {
|
||||
int fileofs, filelen;
|
||||
} lump_t;
|
||||
|
||||
#define LUMP_ENTITIES 0
|
||||
#define LUMP_SHADERS 1
|
||||
#define LUMP_PLANES 2
|
||||
#define LUMP_NODES 3
|
||||
#define LUMP_LEAFS 4
|
||||
#define LUMP_LEAFSURFACES 5
|
||||
#define LUMP_LEAFBRUSHES 6
|
||||
#define LUMP_MODELS 7
|
||||
#define LUMP_BRUSHES 8
|
||||
#define LUMP_BRUSHSIDES 9
|
||||
#define LUMP_DRAWVERTS 10
|
||||
#define LUMP_DRAWINDEXES 11
|
||||
#define LUMP_FOGS 12
|
||||
#define LUMP_SURFACES 13
|
||||
#define LUMP_LIGHTMAPS 14
|
||||
#define LUMP_LIGHTGRID 15
|
||||
#define LUMP_VISIBILITY 16
|
||||
#define LUMP_LIGHTARRAY 17
|
||||
#define HEADER_LUMPS 18
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
int version;
|
||||
|
||||
lump_t lumps[HEADER_LUMPS];
|
||||
} dheader_t;
|
||||
|
||||
typedef struct {
|
||||
float mins[3], maxs[3];
|
||||
int firstSurface, numSurfaces;
|
||||
int firstBrush, numBrushes;
|
||||
} dmodel_t;
|
||||
|
||||
typedef struct dshader_s {
|
||||
char shader[MAX_QPATH];
|
||||
int surfaceFlags;
|
||||
int contentFlags;
|
||||
} dshader_t;
|
||||
|
||||
// planes x^1 is allways the opposite of plane x
|
||||
|
||||
typedef struct {
|
||||
float normal[3];
|
||||
float dist;
|
||||
} dplane_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum;
|
||||
int children[2]; // negative numbers are -(leafs+1), not nodes
|
||||
int mins[3]; // for frustom culling
|
||||
int maxs[3];
|
||||
} dnode_t;
|
||||
|
||||
typedef struct {
|
||||
int cluster; // -1 = opaque cluster (do I still store these?)
|
||||
int area;
|
||||
|
||||
int mins[3]; // for frustum culling
|
||||
int maxs[3];
|
||||
|
||||
int firstLeafSurface;
|
||||
int numLeafSurfaces;
|
||||
|
||||
int firstLeafBrush;
|
||||
int numLeafBrushes;
|
||||
} dleaf_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum; // positive plane side faces out of the leaf
|
||||
int shaderNum;
|
||||
int drawSurfNum;
|
||||
} dbrushside_t;
|
||||
|
||||
typedef struct {
|
||||
int firstSide;
|
||||
int numSides;
|
||||
int shaderNum; // the shader that determines the contents flags
|
||||
} dbrush_t;
|
||||
|
||||
typedef struct {
|
||||
char shader[MAX_QPATH];
|
||||
int brushNum;
|
||||
int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
|
||||
} dfog_t;
|
||||
|
||||
// Light Style Constants
|
||||
#define MAXLIGHTMAPS 4
|
||||
#define LS_NORMAL 0x00
|
||||
#define LS_UNUSED 0xfe
|
||||
#define LS_NONE 0xff
|
||||
#define MAX_LIGHT_STYLES 64
|
||||
|
||||
typedef struct {
|
||||
vec3_t xyz;
|
||||
float st[2];
|
||||
float lightmap[MAXLIGHTMAPS][2];
|
||||
vec3_t normal;
|
||||
byte color[MAXLIGHTMAPS][4];
|
||||
} mapVert_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t xyz;
|
||||
float st[2];
|
||||
float lightmap[MAXLIGHTMAPS][2];
|
||||
vec3_t normal;
|
||||
byte color[MAXLIGHTMAPS][4];
|
||||
} drawVert_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
byte ambientLight[MAXLIGHTMAPS][3];
|
||||
byte directLight[MAXLIGHTMAPS][3];
|
||||
byte styles[MAXLIGHTMAPS];
|
||||
byte latLong[2];
|
||||
} dgrid_t;
|
||||
|
||||
typedef enum {
|
||||
MST_BAD,
|
||||
MST_PLANAR,
|
||||
MST_PATCH,
|
||||
MST_TRIANGLE_SOUP,
|
||||
MST_FLARE
|
||||
} mapSurfaceType_t;
|
||||
|
||||
typedef struct {
|
||||
int shaderNum;
|
||||
int fogNum;
|
||||
int surfaceType;
|
||||
|
||||
int firstVert;
|
||||
int numVerts;
|
||||
|
||||
int firstIndex;
|
||||
int numIndexes;
|
||||
|
||||
byte lightmapStyles[MAXLIGHTMAPS], vertexStyles[MAXLIGHTMAPS];
|
||||
int lightmapNum[MAXLIGHTMAPS];
|
||||
int lightmapX[MAXLIGHTMAPS], lightmapY[MAXLIGHTMAPS];
|
||||
int lightmapWidth, lightmapHeight;
|
||||
|
||||
vec3_t lightmapOrigin;
|
||||
vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
|
||||
|
||||
int patchWidth;
|
||||
int patchHeight;
|
||||
} dsurface_t;
|
||||
|
||||
#endif _XBOX
|
||||
|
||||
typedef enum //# hunkAllocType_e
|
||||
{
|
||||
HA_MISC,
|
||||
HA_MAP,
|
||||
HA_SHADERS,
|
||||
HA_LIGHTING,
|
||||
HA_FOG,
|
||||
HA_PATCHES,
|
||||
HA_VIS,
|
||||
HA_SUBMODELS,
|
||||
HA_MODELS,
|
||||
MAX_HA_TYPES
|
||||
} hunkAllocType_t;
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Defines and structures required for fonts
|
||||
|
||||
#define GLYPH_COUNT 256
|
||||
|
||||
// Must match define in stmparse.h
|
||||
#define STYLE_DROPSHADOW 0x80000000
|
||||
#define STYLE_BLINK 0x40000000
|
||||
#define SET_MASK 0x00ffffff
|
||||
|
||||
typedef struct
|
||||
{
|
||||
short width; // number of pixels wide
|
||||
short height; // number of scan lines
|
||||
short horizAdvance; // number of pixels to advance to the next char
|
||||
short horizOffset; // x offset into space to render glyph
|
||||
int baseline; // y offset
|
||||
float s; // x start tex coord
|
||||
float t; // y start tex coord
|
||||
float s2; // x end tex coord
|
||||
float t2; // y end tex coord
|
||||
} glyphInfo_t;
|
||||
|
||||
|
||||
// this file corresponds 1:1 with the "*.fontdat" files, so don't change it unless you're going to
|
||||
// recompile the fontgen util and regenerate all the fonts!
|
||||
//
|
||||
typedef struct dfontdat_s
|
||||
{
|
||||
glyphInfo_t mGlyphs[GLYPH_COUNT];
|
||||
|
||||
short mPointSize;
|
||||
short mHeight; // max height of font
|
||||
short mAscender;
|
||||
short mDescender;
|
||||
|
||||
short mKoreanHack; // unused field, written out by John's fontgen program but we have to leave it there for disk structs <sigh>
|
||||
} dfontdat_t;
|
||||
|
||||
/////////////////// fonts end ////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
725
code/qcommon/sparc.h
Normal file
725
code/qcommon/sparc.h
Normal file
@@ -0,0 +1,725 @@
|
||||
|
||||
/*
|
||||
* UNPUBLISHED -- Rights reserved under the copyright laws of the
|
||||
* United States. Use of a copyright notice is precautionary only and
|
||||
* does not imply publication or disclosure.
|
||||
*
|
||||
* THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION
|
||||
* OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION,
|
||||
* DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR
|
||||
* EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
AUTHOR: Dave Calvin
|
||||
CREATED: 2002-05-07
|
||||
|
||||
SParse ARray Compressor. Given an array, this class reduces the memory
|
||||
needed to store the array by eliminating the most-frequently used element.
|
||||
The remaining elements are increased in size by one integer.
|
||||
|
||||
If the compressed data would be larger than the original data, the
|
||||
original data is stored as is.
|
||||
|
||||
Compression is O(2N) where N is the number of elements to compress.
|
||||
|
||||
Decompression is O(log M + N) where M is the number of elements after
|
||||
compression (CompressedLength()) and N is the number of elements to decompress.
|
||||
Decompression is O(1) when the same or smaller amount of data is requested as
|
||||
the last decompression.
|
||||
|
||||
The pointer returned by Decompress() is valid until the class is destroyed
|
||||
or a new call is made to Compress() or Decompress().
|
||||
|
||||
Elements must define operator==, operator!=, and sizeof.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __SPARC_H
|
||||
#define __SPARC_H
|
||||
|
||||
#ifdef _GAMECUBE
|
||||
#define SPARC_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
//Bigger than a short, smaller than an int.
|
||||
#pragma pack(push, 1)
|
||||
struct NotSoShort
|
||||
{
|
||||
unsigned char bytes[3];
|
||||
|
||||
NotSoShort(void) {}
|
||||
|
||||
NotSoShort(unsigned int source) {
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
bytes[2] = source & 0xFF;
|
||||
bytes[1] = (source >> 8) & 0xFF;
|
||||
bytes[0] = (source >> 16) & 0xFF;
|
||||
#else
|
||||
bytes[0] = source & 0xFF;
|
||||
bytes[1] = (source >> 8) & 0xFF;
|
||||
bytes[2] = (source >> 16) & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned int GetValue(void) {
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2];
|
||||
#else
|
||||
return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool operator==(unsigned int cmp) {
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
return cmp == ((*(unsigned int*)bytes) >> 8);
|
||||
#else
|
||||
return cmp == ((*(unsigned int*)bytes) & 0x00FFFFFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool operator<(unsigned int cmp) {
|
||||
unsigned int tmp = *(unsigned int*)bytes;
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
tmp >>= 8;
|
||||
#else
|
||||
tmp &= 0x00FFFFFF;
|
||||
#endif
|
||||
return tmp < cmp;
|
||||
}
|
||||
|
||||
bool operator<=(unsigned int cmp) {
|
||||
unsigned int tmp = *(unsigned int*)bytes;
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
tmp >>= 8;
|
||||
#else
|
||||
tmp &= 0x00FFFFFF;
|
||||
#endif
|
||||
return tmp <= cmp;
|
||||
}
|
||||
|
||||
bool operator>(unsigned int cmp) {
|
||||
unsigned int tmp = *(unsigned int*)bytes;
|
||||
#ifdef SPARC_BIG_ENDIAN
|
||||
tmp >>= 8;
|
||||
#else
|
||||
tmp &= 0x00FFFFFF;
|
||||
#endif
|
||||
return tmp > cmp;
|
||||
}
|
||||
};
|
||||
|
||||
//Compressed data is made up of these elements.
|
||||
template <class T, class U>
|
||||
struct SPARCElement
|
||||
{
|
||||
T data;
|
||||
U offset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
inline unsigned int SPARC_SWAP32(unsigned int x, bool doSwap) {
|
||||
if (doSwap) {
|
||||
return ((unsigned int)( ( (x & 0xff000000) >> 24)
|
||||
+ ( (x & 0x00ff0000) >> 8 )
|
||||
+ ( (x & 0x0000ff00) << 8 )
|
||||
+ ( (x & 0x000000ff) << 24 ) ));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
inline NotSoShort SPARC_SWAP24(NotSoShort x, bool doSwap) {
|
||||
if (doSwap) {
|
||||
x.bytes[0] ^= x.bytes[2];
|
||||
x.bytes[2] ^= x.bytes[0];
|
||||
x.bytes[0] ^= x.bytes[2];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned short SPARC_SWAP16(unsigned short x, bool doSwap) {
|
||||
if (doSwap) {
|
||||
return ((unsigned short)( ( (x & 0xff00) >> 8)
|
||||
+ ( (x & 0x00ff) << 8 ) ));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
//The core of the SPARC system. T is the data type to be compressed.
|
||||
//U is the data type needed to store offsets information in the compressed
|
||||
//data. Smaller U makes for better compression but bigger data requires
|
||||
//larger U.
|
||||
template <class T, class U>
|
||||
class SPARCCore
|
||||
{
|
||||
private:
|
||||
//Using compression or just storing clear data?
|
||||
bool compressionUsed;
|
||||
|
||||
//Compressed data and its length.
|
||||
SPARCElement<T, U> *compressedData;
|
||||
unsigned int compressedLength;
|
||||
|
||||
//Decompression cache.
|
||||
T *decompressedData;
|
||||
unsigned int decompressedOffset;
|
||||
unsigned int decompressedLength;
|
||||
|
||||
//Element which was removed to compress.
|
||||
T removedElement;
|
||||
|
||||
//Length of original data before compression.
|
||||
unsigned int originalLength;
|
||||
|
||||
//Memory allocators.
|
||||
void* (*Allocator)(unsigned int size);
|
||||
void (*Deallocator)(void *ptr);
|
||||
|
||||
//Destroy all allocated memory.
|
||||
void Cleanup(void) {
|
||||
if(compressedData) {
|
||||
if(Deallocator) {
|
||||
Deallocator(compressedData);
|
||||
} else {
|
||||
delete [] compressedData;
|
||||
}
|
||||
compressedData = NULL;
|
||||
}
|
||||
|
||||
if(decompressedData) {
|
||||
if(Deallocator) {
|
||||
Deallocator(decompressedData);
|
||||
} else {
|
||||
delete [] decompressedData;
|
||||
}
|
||||
decompressedData = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Init(void) {
|
||||
compressionUsed = false;
|
||||
compressedData = NULL;
|
||||
originalLength = 0;
|
||||
compressedLength = 0;
|
||||
decompressedData = NULL;
|
||||
decompressedOffset = 0;
|
||||
decompressedLength = 0;
|
||||
}
|
||||
|
||||
|
||||
//Binary search for the compressed element most closely matching 'offset'.
|
||||
SPARCElement<T, U> *FindDecompStart(unsigned int offset)
|
||||
{
|
||||
unsigned int startPoint = compressedLength / 2;
|
||||
unsigned int divisor = 4;
|
||||
unsigned int leap;
|
||||
while(1) {
|
||||
if(compressedData[startPoint].offset <= offset &&
|
||||
compressedData[startPoint+1].offset > offset) {
|
||||
if(compressedData[startPoint].offset == offset) {
|
||||
return &compressedData[startPoint];
|
||||
} else {
|
||||
return &compressedData[startPoint+1];
|
||||
}
|
||||
}
|
||||
|
||||
leap = compressedLength / divisor;
|
||||
if(leap < 1) {
|
||||
leap = 1;
|
||||
} else {
|
||||
divisor *= 2;
|
||||
}
|
||||
if(compressedData[startPoint].offset > offset) {
|
||||
startPoint -= leap;
|
||||
} else {
|
||||
startPoint += leap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
SPARCCore(void) {
|
||||
Init();
|
||||
Allocator = NULL;
|
||||
Deallocator = NULL;
|
||||
}
|
||||
|
||||
~SPARCCore(void) {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void SetAllocator(void* (*alloc)(unsigned int size),
|
||||
void (*dealloc)(void *ptr)) {
|
||||
Allocator = alloc;
|
||||
Deallocator = dealloc;
|
||||
}
|
||||
|
||||
//Just store the array without compression.
|
||||
unsigned int Store(const T *array, unsigned int length) {
|
||||
//Destroy old data.
|
||||
Cleanup();
|
||||
Init();
|
||||
|
||||
//Allocate memory and copy array.
|
||||
if(Allocator) {
|
||||
decompressedData = (T*)Allocator(length * sizeof(T));
|
||||
} else {
|
||||
decompressedData = new T[length];
|
||||
}
|
||||
compressedLength = length;
|
||||
memcpy(decompressedData, array, sizeof(T) * length);
|
||||
|
||||
//Set length.
|
||||
originalLength = length;
|
||||
|
||||
return CompressedSize();
|
||||
}
|
||||
|
||||
//Load compressed data directly.
|
||||
unsigned int Load(const char *array, unsigned int length) {
|
||||
//Destroy old data.
|
||||
Cleanup();
|
||||
Init();
|
||||
|
||||
//Restore some attributes.
|
||||
compressionUsed = (bool)*array++;
|
||||
|
||||
assert(sizeof(T) == 1); //For now only support characters.
|
||||
removedElement = *(T*)array;
|
||||
array += sizeof(T);
|
||||
|
||||
originalLength = *(unsigned int*)array;
|
||||
array += sizeof(unsigned int);
|
||||
|
||||
compressedLength = *(unsigned int*)array;
|
||||
array += sizeof(unsigned int);
|
||||
|
||||
//Allocate memory and copy array.
|
||||
if (compressionUsed) {
|
||||
if(Allocator) {
|
||||
compressedData = (SPARCElement<T, U>*)
|
||||
Allocator(compressedLength * sizeof(SPARCElement<T, U>));
|
||||
} else {
|
||||
compressedData = new SPARCElement<T, U>[compressedLength];
|
||||
}
|
||||
memcpy(compressedData, array,
|
||||
compressedLength * sizeof(SPARCElement<T, U>));
|
||||
}
|
||||
else {
|
||||
if(Allocator) {
|
||||
decompressedData = (T*)Allocator(
|
||||
compressedLength * sizeof(T));
|
||||
} else {
|
||||
decompressedData = new T[compressedLength];
|
||||
}
|
||||
memcpy(decompressedData, array, compressedLength * sizeof(T));
|
||||
}
|
||||
|
||||
return CompressedSize();
|
||||
}
|
||||
|
||||
//Save state for later restoration.
|
||||
unsigned int Save(char *array, unsigned int length, bool doSwap) {
|
||||
//Figure out how much space is needed.
|
||||
unsigned int size = sizeof(char) + sizeof(T) +
|
||||
sizeof(unsigned int) + sizeof(unsigned int);
|
||||
|
||||
if (compressionUsed) {
|
||||
size += compressedLength * sizeof(SPARCElement<T, U>);
|
||||
}
|
||||
else {
|
||||
size += compressedLength * sizeof(T);
|
||||
}
|
||||
|
||||
assert(length >= size);
|
||||
|
||||
//Save some attributes.
|
||||
*array++ = (char)compressionUsed;
|
||||
|
||||
assert(sizeof(T) == 1); //For now only support characters.
|
||||
*(T*)array = removedElement;
|
||||
array += sizeof(T);
|
||||
|
||||
*(unsigned int*)array = SPARC_SWAP32(originalLength, doSwap);
|
||||
array += sizeof(unsigned int);
|
||||
|
||||
*(unsigned int*)array = SPARC_SWAP32(compressedLength, doSwap);
|
||||
array += sizeof(unsigned int);
|
||||
|
||||
//Store compressed data (or uncompressed data if none exists)
|
||||
if (compressionUsed) {
|
||||
for (unsigned int i = 0; i < compressedLength; ++i) {
|
||||
//Copy the data element. For now only support characters.
|
||||
((SPARCElement<T, U> *)array)[i].data = compressedData[i].data;
|
||||
|
||||
//Copy the offset to the next unique element.
|
||||
if (sizeof(U) == 1) {
|
||||
((SPARCElement<T, U> *)array)[i].offset =
|
||||
compressedData[i].offset;
|
||||
}
|
||||
else if (sizeof(U) == 2) {
|
||||
((SPARCElement<T, unsigned short> *)array)[i].offset =
|
||||
SPARC_SWAP16(*(unsigned short*)&compressedData[i].offset,
|
||||
doSwap);
|
||||
}
|
||||
else if (sizeof(U) == 3) {
|
||||
((SPARCElement<T, NotSoShort> *)array)[i].offset =
|
||||
SPARC_SWAP24(*(NotSoShort*)&compressedData[i].offset,
|
||||
doSwap);
|
||||
}
|
||||
else if (sizeof(U) == 4) {
|
||||
((SPARCElement<T, unsigned int> *)array)[i].offset =
|
||||
SPARC_SWAP32(*(unsigned int*)&compressedData[i].offset,
|
||||
doSwap);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
memcpy(array, decompressedData, compressedLength * sizeof(T));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
//Compresses this array, returns the compressed size. Compresses
|
||||
//by eliminating the given element.
|
||||
unsigned int Compress(const T *array, unsigned int length, T removal) {
|
||||
|
||||
unsigned int i;
|
||||
unsigned int numRemove = 0;
|
||||
SPARCElement<T, U> *compress;
|
||||
|
||||
//Destroy old data.
|
||||
Cleanup();
|
||||
Init();
|
||||
|
||||
//Count number of elements to remove. Can't remove first or
|
||||
//last element (prevents boundary conditions).
|
||||
for(i=1; i<length-1; i++) {
|
||||
if(array[i] == removal) {
|
||||
numRemove++;
|
||||
}
|
||||
}
|
||||
|
||||
compressedLength = length - numRemove;
|
||||
originalLength = length;
|
||||
|
||||
//If we're going to allocate more memory than was originally used,
|
||||
//just store the data.
|
||||
if(sizeof(SPARCElement<T, U>) * compressedLength >=
|
||||
sizeof(T) * length) {
|
||||
Store(array, length);
|
||||
return CompressedSize();
|
||||
}
|
||||
|
||||
//Allocate memory for compressed elements.
|
||||
if(Allocator) {
|
||||
compressedData = (SPARCElement<T, U>*)
|
||||
Allocator(compressedLength * sizeof(SPARCElement<T, U>));
|
||||
} else {
|
||||
compressedData = new SPARCElement<T, U>[compressedLength];
|
||||
}
|
||||
compressionUsed = true;
|
||||
|
||||
//Fill compressed array. First and last elements go in no matter
|
||||
//what.
|
||||
compressedData[0].data = array[0];
|
||||
compressedData[0].offset = 0;
|
||||
compress = &compressedData[1];
|
||||
for(i=1; i<length-1; i++) {
|
||||
if(array[i] != removal) {
|
||||
compress->data = array[i];
|
||||
compress->offset = i;
|
||||
compress++;
|
||||
}
|
||||
}
|
||||
compress->data = array[i];
|
||||
compress->offset = i;
|
||||
|
||||
//Store removal value for decompression purposes.
|
||||
removedElement = removal;
|
||||
|
||||
//Store original length for bounds checking.
|
||||
originalLength = length;
|
||||
|
||||
//Return the compressed size.
|
||||
return CompressedSize();
|
||||
}
|
||||
|
||||
|
||||
//Get the compressed data size in bytes, or 0 if nothing stored.
|
||||
unsigned int CompressedSize(void) {
|
||||
return compressedLength * sizeof(SPARCElement<T, U>);
|
||||
}
|
||||
|
||||
//Get the decompressed data starting at offset and ending at
|
||||
//offset + length. Returns NULL on error.
|
||||
const T *Decompress(unsigned int offset, unsigned int length) {
|
||||
|
||||
SPARCElement<T, U> *decomp = NULL;
|
||||
unsigned int i;
|
||||
|
||||
//If data isn't compressed, just return a pointers.
|
||||
if(!compressionUsed) {
|
||||
return decompressedData + offset;
|
||||
}
|
||||
|
||||
//If last decompression falls within offset and length, just return
|
||||
//a pointer.
|
||||
if(decompressedData && decompressedOffset <= offset &&
|
||||
decompressedOffset + decompressedLength >= offset + length) {
|
||||
return decompressedData + offset - decompressedOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Allocate new space for decompression if length has changed.
|
||||
if(length != decompressedLength) {
|
||||
//Destroy old data first.
|
||||
if(decompressedData) {
|
||||
if(Deallocator) {
|
||||
Deallocator(decompressedData);
|
||||
} else {
|
||||
delete [] decompressedData;
|
||||
}
|
||||
}
|
||||
|
||||
if(Allocator) {
|
||||
decompressedData = (T*)Allocator(length * sizeof(T));
|
||||
} else {
|
||||
decompressedData = new T[length];
|
||||
}
|
||||
}
|
||||
decompressedOffset = offset;
|
||||
decompressedLength = length;
|
||||
|
||||
//Find position to start decompressing from.
|
||||
decomp = FindDecompStart(offset);
|
||||
|
||||
if(!decomp) { //should never happen
|
||||
assert(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//Decompress the data.
|
||||
for(i=0; i < length; i++) {
|
||||
if(decomp->offset == i + offset) {
|
||||
decompressedData[i] = decomp->data;
|
||||
decomp++;
|
||||
} else {
|
||||
decompressedData[i] = removedElement;
|
||||
}
|
||||
}
|
||||
|
||||
return decompressedData;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//The user-interface to SPARC. Automatically selects the best core based
|
||||
//on data size.
|
||||
template <class T>
|
||||
class SPARC
|
||||
{
|
||||
private:
|
||||
void *core;
|
||||
unsigned char offsetBytes;
|
||||
|
||||
//Memory allocators.
|
||||
void* (*Allocator)(unsigned int size);
|
||||
void (*Deallocator)(void *ptr);
|
||||
|
||||
public:
|
||||
SPARC(void) {
|
||||
core = NULL;
|
||||
offsetBytes = 0;
|
||||
Allocator = NULL;
|
||||
Deallocator = NULL;
|
||||
}
|
||||
|
||||
~SPARC(void) {
|
||||
Release();
|
||||
};
|
||||
|
||||
void SetAllocator(void* (*alloc)(unsigned int size),
|
||||
void (*dealloc)(void *ptr)) {
|
||||
Allocator = alloc;
|
||||
Deallocator = dealloc;
|
||||
}
|
||||
|
||||
//Select a core, cast it to the right type and return the size.
|
||||
unsigned int CompressedSize(void) {
|
||||
if(!core) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(offsetBytes) {
|
||||
case 1:
|
||||
return ((SPARCCore<T, unsigned char>*)core)->CompressedSize();
|
||||
case 2:
|
||||
return ((SPARCCore<T, unsigned short>*)core)->CompressedSize();
|
||||
case 3:
|
||||
return ((SPARCCore<T, NotSoShort>*)core)->CompressedSize();
|
||||
case 4:
|
||||
return ((SPARCCore<T, unsigned int>*)core)->CompressedSize();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Always use the same core type since we won't be compressing.
|
||||
unsigned int Store(const T *array, unsigned int length)
|
||||
{
|
||||
Release();
|
||||
offsetBytes = 1;
|
||||
core = new SPARCCore<T, unsigned char>;
|
||||
((SPARCCore<T, unsigned char>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned char>*)core)-> Store(array, length);
|
||||
}
|
||||
|
||||
//Load compressed data directly.
|
||||
unsigned int Load(const char *array, unsigned int length) {
|
||||
Release();
|
||||
|
||||
offsetBytes = *array++;
|
||||
|
||||
switch (offsetBytes) {
|
||||
case 1:
|
||||
core = new SPARCCore<T, unsigned char>;
|
||||
((SPARCCore<T, unsigned char>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned char>*)core)->
|
||||
Load(array, length-1);
|
||||
case 2:
|
||||
core = new SPARCCore<T, unsigned short>;
|
||||
((SPARCCore<T, unsigned short>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned short>*)core)->
|
||||
Load(array, length-1);
|
||||
case 3:
|
||||
core = new SPARCCore<T, NotSoShort>;
|
||||
((SPARCCore<T, NotSoShort>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, NotSoShort>*)core)->
|
||||
Load(array, length-1);
|
||||
case 4:
|
||||
core = new SPARCCore<T, unsigned int>;
|
||||
((SPARCCore<T, unsigned int>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned int>*)core)->
|
||||
Load(array, length-1);
|
||||
default:
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//Save compressed data into array.
|
||||
unsigned int Save(char *array, unsigned int length, bool doSwap) {
|
||||
*array++ = offsetBytes;
|
||||
|
||||
switch (offsetBytes) {
|
||||
case 1:
|
||||
return ((SPARCCore<T, unsigned char>*)core)->
|
||||
Save(array, length-1, doSwap);
|
||||
case 2:
|
||||
return ((SPARCCore<T, unsigned short>*)core)->
|
||||
Save(array, length-1, doSwap);
|
||||
case 3:
|
||||
return ((SPARCCore<T, NotSoShort>*)core)->
|
||||
Save(array, length-1, doSwap);
|
||||
case 4:
|
||||
return ((SPARCCore<T, unsigned int>*)core)->
|
||||
Save(array, length-1, doSwap);
|
||||
default:
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//Create the smallest core possible for the given data.
|
||||
unsigned int Compress(const T *array, unsigned int length, T removal) {
|
||||
Release();
|
||||
|
||||
if(length < 256) {
|
||||
offsetBytes = 1;
|
||||
core = new SPARCCore<T, unsigned char>;
|
||||
((SPARCCore<T, unsigned char>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned char>*)core)->
|
||||
Compress(array, length, removal);
|
||||
} else if(length < 65536) {
|
||||
offsetBytes = 2;
|
||||
core = new SPARCCore<T, unsigned short>;
|
||||
((SPARCCore<T, unsigned short>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned short>*)core)->
|
||||
Compress(array, length, removal);
|
||||
} else if(length < 16777216) {
|
||||
offsetBytes = 3;
|
||||
core = new SPARCCore<T, NotSoShort>;
|
||||
((SPARCCore<T, NotSoShort>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, NotSoShort>*)core)->
|
||||
Compress(array, length, removal);
|
||||
} else {
|
||||
offsetBytes = 4;
|
||||
core = new SPARCCore<T, unsigned int>;
|
||||
((SPARCCore<T, unsigned int>*)core)->
|
||||
SetAllocator(Allocator, Deallocator);
|
||||
return ((SPARCCore<T, unsigned int>*)core)->
|
||||
Compress(array, length, removal);
|
||||
}
|
||||
}
|
||||
|
||||
//Cast to the correct core type and decompress.
|
||||
const T *Decompress(unsigned int offset, unsigned int length) {
|
||||
if(!core) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch(offsetBytes) {
|
||||
case 1:
|
||||
return ((SPARCCore<T, unsigned char>*)core)->
|
||||
Decompress(offset, length);
|
||||
case 2:
|
||||
return ((SPARCCore<T, unsigned short>*)core)->
|
||||
Decompress(offset, length);
|
||||
case 3:
|
||||
return ((SPARCCore<T, NotSoShort>*)core)->
|
||||
Decompress(offset, length);
|
||||
case 4:
|
||||
return ((SPARCCore<T, unsigned int>*)core)->
|
||||
Decompress(offset, length);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//Destroy all compressed data and the current decompressed buffer.
|
||||
void Release(void) {
|
||||
if(core) {
|
||||
switch(offsetBytes) {
|
||||
case 1:
|
||||
delete (SPARCCore<T, unsigned char>*)core;
|
||||
break;
|
||||
case 2:
|
||||
delete (SPARCCore<T, unsigned short>*)core;
|
||||
break;
|
||||
case 3:
|
||||
delete (SPARCCore<T, NotSoShort>*)core;
|
||||
break;
|
||||
case 4:
|
||||
delete (SPARCCore<T, unsigned int>*)core;
|
||||
break;
|
||||
}
|
||||
core = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
120
code/qcommon/sstring.h
Normal file
120
code/qcommon/sstring.h
Normal file
@@ -0,0 +1,120 @@
|
||||
// Filename:- sstring.h
|
||||
//
|
||||
// Gil's string template, used to replace Microsoft's <string> vrsion which doesn't compile under certain stl map<>
|
||||
// conditions...
|
||||
|
||||
|
||||
#ifndef SSTRING_H
|
||||
#define SSTRING_H
|
||||
|
||||
|
||||
template<int MaxSize>
|
||||
class sstring
|
||||
{
|
||||
struct SStorage
|
||||
{
|
||||
char data[MaxSize];
|
||||
};
|
||||
SStorage mStorage;
|
||||
public:
|
||||
/* don't figure we need this
|
||||
template<int oMaxSize>
|
||||
sstring(const sstring<oMaxSize> &o)
|
||||
{
|
||||
assert(strlen(o.mStorage.data)<MaxSize);
|
||||
strcpy(mStorage.data,o.mStorage.data);
|
||||
}
|
||||
*/
|
||||
sstring(const sstring<MaxSize> &o)
|
||||
{
|
||||
//strcpy(mStorage.data,o.mStorage.data);
|
||||
Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data),qtrue);
|
||||
}
|
||||
sstring(const char *s)
|
||||
{
|
||||
//assert(strlen(s)<MaxSize);
|
||||
//strcpy(mStorage.data,s);
|
||||
Q_strncpyz(mStorage.data,s,sizeof(mStorage.data),qtrue);
|
||||
}
|
||||
sstring()
|
||||
{
|
||||
mStorage.data[0]=0;
|
||||
}
|
||||
/* don't figure we need this
|
||||
template<int oMaxSize>
|
||||
sstring<oMaxSize> & operator =(const sstring<oMaxSize> &o)
|
||||
{
|
||||
assert(strlen(o.mStorage.data)<MaxSize);
|
||||
strcpy(mStorage.data,o.mStorage.data);
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
sstring<MaxSize> & operator=(const sstring<MaxSize> &o)
|
||||
{
|
||||
//strcpy(mStorage.data,o.mStorage.data);
|
||||
Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data),qtrue);
|
||||
return *this;
|
||||
}
|
||||
sstring<MaxSize> & operator=(const char *s)
|
||||
{
|
||||
assert(strlen(s)<MaxSize);
|
||||
//strcpy(mStorage.data,s);
|
||||
Q_strncpyz(mStorage.data,s,sizeof(mStorage.data),qtrue);
|
||||
return *this;
|
||||
}
|
||||
char *c_str()
|
||||
{
|
||||
return mStorage.data;
|
||||
}
|
||||
const char *c_str() const
|
||||
{
|
||||
return mStorage.data;
|
||||
}
|
||||
int capacity() const
|
||||
{
|
||||
return MaxSize; // not sure if this should be MaxSize-1? depends if talking bytes or strlen space I guess
|
||||
}
|
||||
int length() const
|
||||
{
|
||||
return strlen(mStorage.data);
|
||||
}
|
||||
bool operator==(const sstring<MaxSize> &o) const
|
||||
{
|
||||
if (!strcmpi(mStorage.data,o.mStorage.data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool operator!=(const sstring<MaxSize> &o) const
|
||||
{
|
||||
if (strcmpi(mStorage.data,o.mStorage.data)!=0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool operator<(const sstring<MaxSize> &o) const
|
||||
{
|
||||
if (strcmpi(mStorage.data,o.mStorage.data)<0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool operator>(const sstring<MaxSize> &o) const
|
||||
{
|
||||
if (strcmpi(mStorage.data,o.mStorage.data)>0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef sstring<MAX_QPATH> sstring_t;
|
||||
|
||||
#endif // #ifndef SSTRING_H
|
||||
|
||||
/////////////////// eof ////////////////////
|
||||
|
||||
985
code/qcommon/stringed_ingame.cpp
Normal file
985
code/qcommon/stringed_ingame.cpp
Normal file
@@ -0,0 +1,985 @@
|
||||
// Filename:- stringed_ingame.cpp
|
||||
//
|
||||
// This file is designed to be pasted into each game project that uses the StringEd package's files.
|
||||
// You can alter the way it does things by (eg) replacing STL with RATL, BUT LEAVE THE OVERALL
|
||||
// FUNCTIONALITY THE SAME, or if I ever make any funadamental changes to the way the package works
|
||||
// then you're going to be SOOL (shit out of luck ;-)...
|
||||
//
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// stuff common to all qcommon files...
|
||||
#include "../server/server.h"
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
#include "../qcommon/fixedmap.h"
|
||||
#include "../zlib/zlib.h"
|
||||
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
|
||||
#pragma warning ( disable : 4511 ) // copy constructor could not be generated
|
||||
#pragma warning ( disable : 4512 ) // assignment operator could not be generated
|
||||
#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah
|
||||
#include "stringed_ingame.h"
|
||||
#include "stringed_interface.h"
|
||||
|
||||
// Needed for DWORD and XC_LANGUAGE defines:
|
||||
#include <xtl.h>
|
||||
|
||||
///////////////////////////////////////////////
|
||||
|
||||
// some STL stuff...
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
///////////////////////////////////////////////
|
||||
|
||||
cvar_t *se_language = NULL;
|
||||
|
||||
// Yeah, it's hardcoded. I don't give a shit.
|
||||
#define MAX_STRING_ENTRIES 4096
|
||||
|
||||
|
||||
typedef struct SE_Entry_s
|
||||
{
|
||||
string m_strString;
|
||||
} SE_Entry_t;
|
||||
|
||||
|
||||
//typedef map <string, SE_Entry_t> mapStringEntries_t;
|
||||
|
||||
class CStringEdPackage
|
||||
{
|
||||
private:
|
||||
|
||||
SE_BOOL m_bEndMarkerFound_ParseOnly;
|
||||
string m_strCurrentEntryRef_ParseOnly;
|
||||
string m_strCurrentEntryEnglish_ParseOnly;
|
||||
string m_strCurrentFileRef_ParseOnly;
|
||||
string m_strLoadingLanguage_ParseOnly; // eg "german"
|
||||
SE_BOOL m_bLoadingEnglish_ParseOnly;
|
||||
|
||||
public:
|
||||
|
||||
CStringEdPackage()
|
||||
{
|
||||
Z_PushNewDeleteTag( TAG_STRINGED );
|
||||
m_Strings = new VVFixedMap< char *, unsigned long >( MAX_STRING_ENTRIES );
|
||||
Z_PopNewDeleteTag();
|
||||
|
||||
Clear( SE_FALSE );
|
||||
}
|
||||
|
||||
~CStringEdPackage()
|
||||
{
|
||||
Clear( SE_FALSE );
|
||||
}
|
||||
|
||||
// Text entries, indexed by crc32 of reference:
|
||||
VVFixedMap< char *, unsigned long > *m_Strings;
|
||||
|
||||
// mapStringEntries_t m_StringEntries; // needs to be in public space now
|
||||
|
||||
void Clear( SE_BOOL bChangingLanguages );
|
||||
void SetupNewFileParse( LPCSTR psFileName );
|
||||
SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest );
|
||||
LPCSTR ParseLine( LPCSTR psLine );
|
||||
LPCSTR ExtractLanguageFromPath( LPCSTR psFileName );
|
||||
SE_BOOL EndMarkerFoundDuringParse( void )
|
||||
{
|
||||
return m_bEndMarkerFound_ParseOnly;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void AddEntry( LPCSTR psLocalReference );
|
||||
int GetNumStrings(void);
|
||||
void SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug );
|
||||
SE_BOOL SetReference( int iIndex, LPCSTR psNewString );
|
||||
LPCSTR GetCurrentFileName(void);
|
||||
LPCSTR GetCurrentReference_ParseOnly( void );
|
||||
SE_BOOL CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine);
|
||||
LPCSTR InsideQuotes( LPCSTR psLine );
|
||||
LPCSTR ConvertCRLiterals_Read( LPCSTR psString );
|
||||
void REMKill( char *psBuffer );
|
||||
char *Filename_PathOnly( LPCSTR psFilename );
|
||||
char *Filename_WithoutPath(LPCSTR psFilename);
|
||||
char *Filename_WithoutExt(LPCSTR psFilename);
|
||||
};
|
||||
|
||||
CStringEdPackage TheStringPackage;
|
||||
|
||||
|
||||
void CStringEdPackage::Clear( SE_BOOL bChangingLanguages )
|
||||
{
|
||||
// m_StringEntries.clear();
|
||||
|
||||
m_bEndMarkerFound_ParseOnly = SE_FALSE;
|
||||
m_strCurrentEntryRef_ParseOnly = "";
|
||||
m_strCurrentEntryEnglish_ParseOnly = "";
|
||||
//
|
||||
// the other vars are cleared in SetupNewFileParse(), and are ok to not do here.
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
|
||||
// loses anything after the path (if any), (eg) "dir/name.bmp" becomes "dir"
|
||||
// (copes with either slash-scheme for names)
|
||||
//
|
||||
// (normally I'd call another function for this, but this is supposed to be engine-independant,
|
||||
// so a certain amount of re-invention of the wheel is to be expected...)
|
||||
//
|
||||
char *CStringEdPackage::Filename_PathOnly(LPCSTR psFilename)
|
||||
{
|
||||
static char sString[ iSE_MAX_FILENAME_LENGTH ];
|
||||
|
||||
strcpy(sString,psFilename);
|
||||
|
||||
char *p1= strrchr(sString,'\\');
|
||||
char *p2= strrchr(sString,'/');
|
||||
char *p = (p1>p2)?p1:p2;
|
||||
if (p)
|
||||
*p=0;
|
||||
|
||||
return sString;
|
||||
}
|
||||
|
||||
|
||||
// returns (eg) "dir/name" for "dir/name.bmp"
|
||||
// (copes with either slash-scheme for names)
|
||||
//
|
||||
// (normally I'd call another function for this, but this is supposed to be engine-independant,
|
||||
// so a certain amount of re-invention of the wheel is to be expected...)
|
||||
//
|
||||
char *CStringEdPackage::Filename_WithoutExt(LPCSTR psFilename)
|
||||
{
|
||||
static char sString[ iSE_MAX_FILENAME_LENGTH ];
|
||||
|
||||
strcpy(sString,psFilename);
|
||||
|
||||
char *p = strrchr(sString,'.');
|
||||
char *p2= strrchr(sString,'\\');
|
||||
char *p3= strrchr(sString,'/');
|
||||
|
||||
// special check, make sure the first suffix we found from the end wasn't just a directory suffix (eg on a path'd filename with no extension anyway)
|
||||
//
|
||||
if (p &&
|
||||
(p2==0 || (p2 && p>p2)) &&
|
||||
(p3==0 || (p3 && p>p3))
|
||||
)
|
||||
*p=0;
|
||||
|
||||
return sString;
|
||||
}
|
||||
|
||||
// returns actual filename only, no path
|
||||
// (copes with either slash-scheme for names)
|
||||
//
|
||||
// (normally I'd call another function for this, but this is supposed to be engine-independant,
|
||||
// so a certain amount of re-invention of the wheel is to be expected...)
|
||||
//
|
||||
char *CStringEdPackage::Filename_WithoutPath(LPCSTR psFilename)
|
||||
{
|
||||
static char sString[ iSE_MAX_FILENAME_LENGTH ];
|
||||
|
||||
LPCSTR psCopyPos = psFilename;
|
||||
|
||||
while (*psFilename)
|
||||
{
|
||||
if (*psFilename == '/' || *psFilename == '\\')
|
||||
psCopyPos = psFilename+1;
|
||||
psFilename++;
|
||||
}
|
||||
|
||||
strcpy(sString,psCopyPos);
|
||||
|
||||
return sString;
|
||||
}
|
||||
|
||||
|
||||
LPCSTR CStringEdPackage::ExtractLanguageFromPath( LPCSTR psFileName )
|
||||
{
|
||||
return Filename_WithoutPath( Filename_PathOnly( psFileName ) );
|
||||
}
|
||||
|
||||
|
||||
void CStringEdPackage::SetupNewFileParse( LPCSTR psFileName )
|
||||
{
|
||||
char sString[ iSE_MAX_FILENAME_LENGTH ];
|
||||
|
||||
strcpy(sString, Filename_WithoutPath( Filename_WithoutExt( psFileName ) ));
|
||||
Q_strupr(sString);
|
||||
|
||||
m_strCurrentFileRef_ParseOnly = sString; // eg "OBJECTIVES"
|
||||
m_strLoadingLanguage_ParseOnly = ExtractLanguageFromPath( psFileName );
|
||||
m_bLoadingEnglish_ParseOnly = (!stricmp( m_strLoadingLanguage_ParseOnly.c_str(), "english" )) ? SE_TRUE : SE_FALSE;
|
||||
}
|
||||
|
||||
|
||||
// returns SE_TRUE if supplied keyword found at line start (and advances supplied ptr past any whitespace to next arg (or line end if none),
|
||||
//
|
||||
// else returns SE_FALSE...
|
||||
//
|
||||
SE_BOOL CStringEdPackage::CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine)
|
||||
{
|
||||
if (!Q_stricmpn(psKeyword, psLine, strlen(psKeyword)) )
|
||||
{
|
||||
psLine += strlen(psKeyword);
|
||||
|
||||
// skip whitespace to arrive at next item...
|
||||
//
|
||||
while ( *psLine == '\t' || *psLine == ' ' )
|
||||
{
|
||||
psLine++;
|
||||
}
|
||||
return SE_TRUE;
|
||||
}
|
||||
|
||||
return SE_FALSE;
|
||||
}
|
||||
|
||||
// change "\n" to '\n' (i.e. 2-byte char-string to 1-byte ctrl-code)...
|
||||
// (or "\r\n" in editor)
|
||||
//
|
||||
LPCSTR CStringEdPackage::ConvertCRLiterals_Read( LPCSTR psString )
|
||||
{
|
||||
static string str;
|
||||
str = psString;
|
||||
int iLoc;
|
||||
while ( (iLoc = str.find("\\n")) != -1 )
|
||||
{
|
||||
str[iLoc ] = '\n';
|
||||
str.erase( iLoc+1,1 );
|
||||
}
|
||||
|
||||
return str.c_str();
|
||||
}
|
||||
|
||||
|
||||
// kill off any "//" onwards part in the line, but NOT if it's inside a quoted string...
|
||||
//
|
||||
void CStringEdPackage::REMKill( char *psBuffer )
|
||||
{
|
||||
char *psScanPos = psBuffer;
|
||||
char *p;
|
||||
int iDoubleQuotesSoFar = 0;
|
||||
|
||||
// scan forwards in case there are more than one (and the first is inside quotes)...
|
||||
//
|
||||
while ( (p=strstr(psScanPos,"//")) != NULL)
|
||||
{
|
||||
// count the number of double quotes before this point, if odd number, then we're inside quotes...
|
||||
//
|
||||
int iDoubleQuoteCount = iDoubleQuotesSoFar;
|
||||
|
||||
for (int i=0; i<p-psScanPos; i++)
|
||||
{
|
||||
if (psScanPos[i] == '"')
|
||||
{
|
||||
iDoubleQuoteCount++;
|
||||
}
|
||||
}
|
||||
if (!(iDoubleQuoteCount&1))
|
||||
{
|
||||
// not inside quotes, so kill line here...
|
||||
//
|
||||
*p='\0';
|
||||
//
|
||||
// and remove any trailing whitespace...
|
||||
//
|
||||
if (psScanPos[0]) // any strlen? (else access violation with -1 below)
|
||||
{
|
||||
int iWhiteSpaceScanPos = strlen(psScanPos)-1;
|
||||
while (iWhiteSpaceScanPos>=0 && isspace(psScanPos[iWhiteSpaceScanPos]))
|
||||
{
|
||||
psScanPos[iWhiteSpaceScanPos--] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// inside quotes (blast), oh well, skip past and keep scanning...
|
||||
//
|
||||
psScanPos = p+1;
|
||||
iDoubleQuotesSoFar = iDoubleQuoteCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns true while new lines available to be read...
|
||||
//
|
||||
SE_BOOL CStringEdPackage::ReadLine( LPCSTR &psParsePos, char *psDest )
|
||||
{
|
||||
if (psParsePos[0])
|
||||
{
|
||||
LPCSTR psLineEnd = strchr(psParsePos, '\n');
|
||||
if (psLineEnd)
|
||||
{
|
||||
int iCharsToCopy = (psLineEnd - psParsePos);
|
||||
strncpy(psDest, psParsePos, iCharsToCopy);
|
||||
psDest[iCharsToCopy] = '\0';
|
||||
psParsePos += iCharsToCopy;
|
||||
while (*psParsePos && strchr("\r\n",*psParsePos))
|
||||
{
|
||||
psParsePos++; // skip over CR or CR/LF pairs
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// last line...
|
||||
//
|
||||
strcpy(psDest, psParsePos);
|
||||
psParsePos += strlen(psParsePos);
|
||||
}
|
||||
|
||||
// clean up the line...
|
||||
//
|
||||
if (psDest[0])
|
||||
{
|
||||
int iWhiteSpaceScanPos = strlen(psDest)-1;
|
||||
while (iWhiteSpaceScanPos>=0 && isspace(psDest[iWhiteSpaceScanPos]))
|
||||
{
|
||||
psDest[iWhiteSpaceScanPos--] = '\0';
|
||||
}
|
||||
|
||||
REMKill( psDest );
|
||||
}
|
||||
return SE_TRUE;
|
||||
}
|
||||
|
||||
return SE_FALSE;
|
||||
}
|
||||
|
||||
// remove any outside quotes from this supplied line, plus any leading or trailing whitespace...
|
||||
//
|
||||
LPCSTR CStringEdPackage::InsideQuotes( LPCSTR psLine )
|
||||
{
|
||||
// I *could* replace this string object with a declared array, but wasn't sure how big to leave it, and it'd have to
|
||||
// be static as well, hence permanent. (problem on consoles?)
|
||||
//
|
||||
static string str;
|
||||
str = ""; // do NOT join to above line
|
||||
|
||||
// skip any leading whitespace...
|
||||
//
|
||||
while (*psLine == ' ' || *psLine == '\t')
|
||||
{
|
||||
psLine++;
|
||||
}
|
||||
|
||||
// skip any leading quote...
|
||||
//
|
||||
if (*psLine == '"')
|
||||
{
|
||||
psLine++;
|
||||
}
|
||||
|
||||
// assign it...
|
||||
//
|
||||
str = psLine;
|
||||
|
||||
if (psLine[0])
|
||||
{
|
||||
// lose any trailing whitespace...
|
||||
//
|
||||
while ( str.c_str()[ strlen(str.c_str()) -1 ] == ' ' ||
|
||||
str.c_str()[ strlen(str.c_str()) -1 ] == '\t'
|
||||
)
|
||||
{
|
||||
str.erase( strlen(str.c_str()) -1, 1);
|
||||
}
|
||||
|
||||
// lose any trailing quote...
|
||||
//
|
||||
if (str.c_str()[ strlen(str.c_str()) -1 ] == '"')
|
||||
{
|
||||
str.erase( strlen(str.c_str()) -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// and return it...
|
||||
//
|
||||
return str.c_str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this copes with both foreigners using hi-char values (eg the french using 0x92 instead of 0x27
|
||||
// for a "'" char), as well as the fact that our buggy fontgen program writes out zeroed glyph info for
|
||||
// some fonts anyway (though not all, just as a gotcha).
|
||||
//
|
||||
// New bit, instead of static buffer (since XBox guys are desperately short of mem) I return a malloc'd buffer now,
|
||||
// so remember to free it!
|
||||
//
|
||||
static char *CopeWithDumbStringData( LPCSTR psSentence, LPCSTR psThisLanguage )
|
||||
{
|
||||
const int iBufferSize = strlen(psSentence)*3; // *3 to allow for expansion of anything even stupid string consisting entirely of elipsis chars
|
||||
char *psNewString = (char *) Z_Malloc(iBufferSize, TAG_TEMP_WORKSPACE, qfalse);
|
||||
Q_strncpyz(psNewString, psSentence, iBufferSize);
|
||||
|
||||
// this is annoying, I have to just guess at which languages to do it for (ie NOT ASIAN/MBCS!!!) since the
|
||||
// string system was deliberately (and correctly) designed to not know or care whether it was doing SBCS
|
||||
// or MBCS languages, because it was never envisioned that I'd have to clean up other people's mess.
|
||||
//
|
||||
// Ok, bollocks to it, this will have to do. Any other languages that come later and have bugs in their text can
|
||||
// get fixed by them typing it in properly in the first place...
|
||||
//
|
||||
if (!stricmp(psThisLanguage,"ENGLISH") ||
|
||||
!stricmp(psThisLanguage,"FRENCH") ||
|
||||
!stricmp(psThisLanguage,"GERMAN") ||
|
||||
!stricmp(psThisLanguage,"ITALIAN") ||
|
||||
!stricmp(psThisLanguage,"SPANISH") ||
|
||||
!stricmp(psThisLanguage,"POLISH") ||
|
||||
!stricmp(psThisLanguage,"RUSSIAN")
|
||||
)
|
||||
{
|
||||
char *p;
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x92),va("%c",0x27)); // "'"
|
||||
while ((p=strchr(psNewString,0x92))!=NULL) // "rich" (and illegal) apostrophe
|
||||
{
|
||||
*p = 0x27;
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x93),"\""); // smart quotes -> '"'
|
||||
while ((p=strchr(psNewString,0x93))!=NULL)
|
||||
{
|
||||
*p = '"';
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x94),"\""); // smart quotes -> '"'
|
||||
while ((p=strchr(psNewString,0x94))!=NULL)
|
||||
{
|
||||
*p = '"';
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x0B),"."); // full stop
|
||||
while ((p=strchr(psNewString,0x0B))!=NULL)
|
||||
{
|
||||
*p = '.';
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x85),"..."); // "..."-char -> 3-char "..."
|
||||
while ((p=strchr(psNewString,0x85))!=NULL) // "rich" (and illegal) apostrophe
|
||||
{
|
||||
memmove(p+2,p,strlen(p));
|
||||
*p++ = '.';
|
||||
*p++ = '.';
|
||||
*p = '.';
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x91),va("%c",0x27)); // "'"
|
||||
while ((p=strchr(psNewString,0x91))!=NULL)
|
||||
{
|
||||
*p = 0x27;
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x96),va("%c",0x2D)); // "-"
|
||||
while ((p=strchr(psNewString,0x96))!=NULL)
|
||||
{
|
||||
*p = 0x2D;
|
||||
}
|
||||
|
||||
// strXLS_Speech.Replace(va("%c",0x97),va("%c",0x2D)); // "-"
|
||||
while ((p=strchr(psNewString,0x97))!=NULL)
|
||||
{
|
||||
*p = 0x2D;
|
||||
}
|
||||
|
||||
// bug fix for picky grammatical errors, replace "?." with "? "
|
||||
//
|
||||
while ((p=strstr(psNewString,"?."))!=NULL)
|
||||
{
|
||||
p[1] = ' ';
|
||||
}
|
||||
|
||||
// StripEd and our print code don't support tabs...
|
||||
//
|
||||
while ((p=strchr(psNewString,0x09))!=NULL)
|
||||
{
|
||||
*p = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return psNewString;
|
||||
}
|
||||
|
||||
// return is either NULL for good else error message to display...
|
||||
//
|
||||
LPCSTR CStringEdPackage::ParseLine( LPCSTR psLine )
|
||||
{
|
||||
LPCSTR psErrorMessage = NULL;
|
||||
|
||||
if (psLine)
|
||||
{
|
||||
if (CheckLineForKeyword( sSE_KEYWORD_VERSION, psLine ))
|
||||
{
|
||||
// VERSION "1"
|
||||
//
|
||||
LPCSTR psVersionNumber = InsideQuotes( psLine );
|
||||
int iVersionNumber = atoi( psVersionNumber );
|
||||
|
||||
if (iVersionNumber != iSE_VERSION)
|
||||
{
|
||||
psErrorMessage = va("Unexpected version number %d, expecting %d!\n", iVersionNumber, iSE_VERSION);
|
||||
}
|
||||
}
|
||||
else
|
||||
if ( CheckLineForKeyword(sSE_KEYWORD_CONFIG, psLine)
|
||||
|| CheckLineForKeyword(sSE_KEYWORD_FILENOTES, psLine)
|
||||
|| CheckLineForKeyword(sSE_KEYWORD_NOTES, psLine)
|
||||
)
|
||||
{
|
||||
// not used ingame, but need to absorb the token
|
||||
}
|
||||
else
|
||||
if (CheckLineForKeyword(sSE_KEYWORD_REFERENCE, psLine))
|
||||
{
|
||||
// REFERENCE GUARD_GOOD_TO_SEE_YOU
|
||||
//
|
||||
LPCSTR psLocalReference = InsideQuotes( psLine );
|
||||
AddEntry( psLocalReference );
|
||||
}
|
||||
else
|
||||
if (CheckLineForKeyword(sSE_KEYWORD_ENDMARKER, psLine))
|
||||
{
|
||||
// ENDMARKER
|
||||
//
|
||||
m_bEndMarkerFound_ParseOnly = SE_TRUE; // the only major error checking I bother to do (for file truncation)
|
||||
}
|
||||
else
|
||||
if (!Q_stricmpn(sSE_KEYWORD_LANG, psLine, strlen(sSE_KEYWORD_LANG)))
|
||||
{
|
||||
// LANG_ENGLISH "GUARD: Good to see you, sir. Taylor is waiting for you in the clean tent. We need to get you suited up. "
|
||||
//
|
||||
LPCSTR psReference = GetCurrentReference_ParseOnly();
|
||||
if ( psReference[0] )
|
||||
{
|
||||
psLine += strlen(sSE_KEYWORD_LANG);
|
||||
|
||||
// what language is this?...
|
||||
//
|
||||
LPCSTR psWordEnd = psLine;
|
||||
while (*psWordEnd && *psWordEnd != ' ' && *psWordEnd != '\t')
|
||||
{
|
||||
psWordEnd++;
|
||||
}
|
||||
char sThisLanguage[1024]={0};
|
||||
int iCharsToCopy = psWordEnd - psLine;
|
||||
if (iCharsToCopy > sizeof(sThisLanguage)-1)
|
||||
{
|
||||
iCharsToCopy = sizeof(sThisLanguage)-1;
|
||||
}
|
||||
strncpy(sThisLanguage, psLine, iCharsToCopy); // already declared as {0} so no need to zero-cap dest buffer
|
||||
|
||||
psLine += strlen(sThisLanguage);
|
||||
LPCSTR _psSentence = ConvertCRLiterals_Read( InsideQuotes( psLine ) );
|
||||
|
||||
// Dammit, I hate having to do crap like this just because other people mess up and put
|
||||
// stupid data in their text, so I have to cope with it.
|
||||
//
|
||||
// note hackery with _psSentence and psSentence because of const-ness. bleurgh. Just don't ask.
|
||||
//
|
||||
char *psSentence = CopeWithDumbStringData( _psSentence, sThisLanguage );
|
||||
|
||||
if ( m_bLoadingEnglish_ParseOnly )
|
||||
{
|
||||
// if loading just "english", then go ahead and store it...
|
||||
//
|
||||
SetString( psReference, psSentence, SE_FALSE );
|
||||
}
|
||||
else
|
||||
{
|
||||
// if loading a foreign language...
|
||||
//
|
||||
SE_BOOL bSentenceIsEnglish = (!stricmp(sThisLanguage,"english")) ? SE_TRUE: SE_FALSE; // see whether this is the english master or not
|
||||
|
||||
// this check can be omitted, I'm just being extra careful here...
|
||||
//
|
||||
if ( !bSentenceIsEnglish )
|
||||
{
|
||||
// basically this is just checking that an .STE file override is the same language as the .STR...
|
||||
//
|
||||
if (stricmp( m_strLoadingLanguage_ParseOnly.c_str(), sThisLanguage ))
|
||||
{
|
||||
psErrorMessage = va("Language \"%s\" found when expecting \"%s\"!\n", sThisLanguage, m_strLoadingLanguage_ParseOnly.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!psErrorMessage)
|
||||
{
|
||||
SetString( psReference, psSentence, bSentenceIsEnglish );
|
||||
}
|
||||
}
|
||||
|
||||
Z_Free( psSentence );
|
||||
}
|
||||
else
|
||||
{
|
||||
psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_LANG "\"\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
psErrorMessage = va("Unknown keyword at linestart: \"%s\"\n", psLine);
|
||||
}
|
||||
}
|
||||
|
||||
return psErrorMessage;
|
||||
}
|
||||
|
||||
// returns reference of string being parsed, else "" for none.
|
||||
//
|
||||
LPCSTR CStringEdPackage::GetCurrentReference_ParseOnly( void )
|
||||
{
|
||||
return m_strCurrentEntryRef_ParseOnly.c_str();
|
||||
}
|
||||
|
||||
// add new string entry (during parse)
|
||||
//
|
||||
void CStringEdPackage::AddEntry( LPCSTR psLocalReference )
|
||||
{
|
||||
// the reason I don't just assign it anyway is because the optional .STE override files don't contain flags,
|
||||
// and therefore would wipe out the parsed flags of the .STR file...
|
||||
//
|
||||
/*
|
||||
mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) );
|
||||
if (itEntry == m_StringEntries.end())
|
||||
{
|
||||
SE_Entry_t SE_Entry;
|
||||
m_StringEntries[ va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ] = SE_Entry;
|
||||
}
|
||||
*/
|
||||
m_strCurrentEntryRef_ParseOnly = psLocalReference;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void CStringEdPackage::SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug )
|
||||
{
|
||||
const char *ref = va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference);
|
||||
unsigned long refCrc = crc32( 0, (const Bytef *)ref, strlen(ref) );
|
||||
|
||||
if ( bEnglishDebug )
|
||||
{
|
||||
// This is the leading english text of a foreign sentence pair (so it's the debug-key text):
|
||||
// don't store, just make a note in-case #same shows up:
|
||||
m_strCurrentEntryEnglish_ParseOnly = psNewString;
|
||||
}
|
||||
else if ( m_bLoadingEnglish_ParseOnly )
|
||||
{
|
||||
// It's the english text, and we're loading english. Add it!
|
||||
int len = strlen( psNewString );
|
||||
char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
|
||||
strcpy( strData, psNewString );
|
||||
|
||||
m_Strings->Insert( strData, refCrc );
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's foreign text, we're going to add it, but we need to check for #same
|
||||
if (!stricmp(psNewString, sSE_EXPORT_SAME))
|
||||
{
|
||||
// If it's #same, then copy the stored english version:
|
||||
int len = m_strCurrentEntryEnglish_ParseOnly.length();
|
||||
char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
|
||||
strcpy( strData, m_strCurrentEntryEnglish_ParseOnly.c_str() );
|
||||
|
||||
m_Strings->Insert( strData, refCrc );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Explicit foreign text. Add it!
|
||||
int len = strlen( psNewString );
|
||||
char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
|
||||
strcpy( strData, psNewString );
|
||||
|
||||
m_Strings->Insert( strData, refCrc );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// filename is local here, eg: "strings/german/obj.str"
|
||||
//
|
||||
// return is either NULL for good else error message to display...
|
||||
//
|
||||
static LPCSTR SE_Load_Actual( LPCSTR psFileName, SE_BOOL bSpeculativeLoad )
|
||||
{
|
||||
LPCSTR psErrorMessage = NULL;
|
||||
|
||||
unsigned char *psLoadedData = SE_LoadFileData( psFileName );
|
||||
if ( psLoadedData )
|
||||
{
|
||||
// now parse the data...
|
||||
//
|
||||
char *psParsePos = (char *) psLoadedData;
|
||||
|
||||
TheStringPackage.SetupNewFileParse( psFileName );
|
||||
|
||||
char sLineBuffer[16384]; // should be enough for one line of text (some of them can be BIG though)
|
||||
while ( !psErrorMessage && TheStringPackage.ReadLine((LPCSTR &) psParsePos, sLineBuffer ) )
|
||||
{
|
||||
if (strlen(sLineBuffer))
|
||||
{
|
||||
psErrorMessage = TheStringPackage.ParseLine( sLineBuffer );
|
||||
}
|
||||
}
|
||||
|
||||
SE_FreeFileDataAfterLoad( psLoadedData);
|
||||
|
||||
if (!psErrorMessage && !TheStringPackage.EndMarkerFoundDuringParse())
|
||||
{
|
||||
psErrorMessage = va("Truncated file, failed to find \"%s\" at file end!", sSE_KEYWORD_ENDMARKER);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( bSpeculativeLoad )
|
||||
{
|
||||
// then it's ok to not find the file, so do nothing...
|
||||
}
|
||||
else
|
||||
{
|
||||
psErrorMessage = va("Unable to load \"%s\"!", psFileName);
|
||||
}
|
||||
}
|
||||
|
||||
return psErrorMessage;
|
||||
}
|
||||
|
||||
static LPCSTR SE_GetFoundFile( string &strResult )
|
||||
{
|
||||
static char sTemp[1024/*MAX_PATH*/];
|
||||
|
||||
if (!strlen(strResult.c_str()))
|
||||
return NULL;
|
||||
|
||||
strncpy(sTemp,strResult.c_str(),sizeof(sTemp)-1);
|
||||
sTemp[sizeof(sTemp)-1]='\0';
|
||||
|
||||
char *psSemiColon = strchr(sTemp,';');
|
||||
if ( psSemiColon)
|
||||
{
|
||||
*psSemiColon = '\0';
|
||||
|
||||
strResult.erase(0,(psSemiColon-sTemp)+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no semicolon found, probably last entry? (though i think even those have them on, oh well)
|
||||
//
|
||||
strResult.erase();
|
||||
}
|
||||
|
||||
// strlwr(sTemp); // just for consistancy and set<> -> set<> erasure checking etc
|
||||
|
||||
return sTemp;
|
||||
}
|
||||
|
||||
//////////// API entry points from rest of game.... //////////////////////////////
|
||||
|
||||
// filename is local here, eg: "strings/german/obj.str"
|
||||
//
|
||||
// return is either NULL for good else error message to display...
|
||||
//
|
||||
LPCSTR SE_Load( LPCSTR psFileName, SE_BOOL bFailIsCritical = SE_TRUE )
|
||||
{
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// ingame here tends to pass in names without paths, but I expect them when doing a language load, so...
|
||||
//
|
||||
char sTemp[1000]={0};
|
||||
if (!strchr(psFileName,'/'))
|
||||
{
|
||||
strcpy(sTemp,sSE_STRINGS_DIR);
|
||||
strcat(sTemp,"/");
|
||||
if (se_language)
|
||||
{
|
||||
strcat(sTemp,se_language->string);
|
||||
strcat(sTemp,"/");
|
||||
}
|
||||
}
|
||||
strcat(sTemp,psFileName);
|
||||
COM_DefaultExtension( sTemp, sizeof(sTemp), sSE_INGAME_FILE_EXTENSION);
|
||||
psFileName = &sTemp[0];
|
||||
//
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
|
||||
LPCSTR psErrorMessage = SE_Load_Actual( psFileName, SE_FALSE );
|
||||
|
||||
// check for any corresponding / overriding .STE files and load them afterwards...
|
||||
//
|
||||
if ( !psErrorMessage )
|
||||
{
|
||||
char sFileName[ iSE_MAX_FILENAME_LENGTH ];
|
||||
strncpy( sFileName, psFileName, sizeof(sFileName)-1 );
|
||||
sFileName[ sizeof(sFileName)-1 ] = '\0';
|
||||
char *p = strrchr( sFileName, '.' );
|
||||
if (p && strlen(p) == strlen(sSE_EXPORT_FILE_EXTENSION))
|
||||
{
|
||||
strcpy( p, sSE_EXPORT_FILE_EXTENSION );
|
||||
|
||||
psErrorMessage = SE_Load_Actual( sFileName, SE_TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
if (psErrorMessage)
|
||||
{
|
||||
if (bFailIsCritical)
|
||||
{
|
||||
// TheStringPackage.Clear(TRUE); // Will we want to do this? Any errors that arise should be fixed immediately
|
||||
Com_Error( ERR_DROP, "SE_Load(): Couldn't load \"%s\"!\n\nError: \"%s\"\n", psFileName, psErrorMessage );
|
||||
}
|
||||
else
|
||||
{
|
||||
Com_DPrintf(S_COLOR_YELLOW "SE_Load(): Couldn't load \"%s\"!\n", psFileName );
|
||||
}
|
||||
}
|
||||
|
||||
return psErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
// convenience-function for the main GetString call...
|
||||
//
|
||||
LPCSTR SE_GetString( LPCSTR psPackageReference, LPCSTR psStringReference)
|
||||
{
|
||||
char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long
|
||||
|
||||
sprintf(sReference,"%s_%s", psPackageReference, psStringReference);
|
||||
|
||||
return SE_GetString( Q_strupr(sReference) );
|
||||
}
|
||||
|
||||
|
||||
LPCSTR SE_GetString( LPCSTR psPackageAndStringReference )
|
||||
{
|
||||
int len = strlen( psPackageAndStringReference );
|
||||
char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long
|
||||
assert( len < sizeof(sReference) );
|
||||
|
||||
Q_strncpyz( sReference, psPackageAndStringReference, sizeof(sReference) );
|
||||
Q_strupr( sReference );
|
||||
|
||||
unsigned long refCrc = crc32( 0, (const Bytef *)sReference, len );
|
||||
char **strData = TheStringPackage.m_Strings->Find( refCrc );
|
||||
|
||||
if( !strData )
|
||||
return "";
|
||||
else
|
||||
return *strData;
|
||||
}
|
||||
|
||||
|
||||
void SE_NewLanguage(void)
|
||||
{
|
||||
TheStringPackage.Clear( SE_TRUE );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// these two functions aren't needed other than to make Quake-type games happy and/or stop memory managers
|
||||
// complaining about leaks if they report them before the global StringEd package object calls it's own dtor.
|
||||
//
|
||||
// but here they are for completeness's sake I guess...
|
||||
//
|
||||
void SE_Init(void)
|
||||
{
|
||||
Z_PushNewDeleteTag( TAG_STRINGED );
|
||||
|
||||
TheStringPackage.Clear( SE_FALSE );
|
||||
|
||||
// se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART);
|
||||
extern DWORD g_dwLanguage;
|
||||
switch( g_dwLanguage )
|
||||
{
|
||||
case XC_LANGUAGE_FRENCH:
|
||||
se_language = Cvar_Get("se_language", "french", CVAR_NORESTART);
|
||||
break;
|
||||
case XC_LANGUAGE_GERMAN:
|
||||
se_language = Cvar_Get("se_language", "german", CVAR_NORESTART);
|
||||
break;
|
||||
case XC_LANGUAGE_ENGLISH:
|
||||
default:
|
||||
se_language = Cvar_Get("se_language", "english", CVAR_NORESTART);
|
||||
break;
|
||||
}
|
||||
|
||||
// Rather than calling SE_LoadLanguage directly, do this. Otherwise,
|
||||
// se_langauge->modified doesn't get cleared, and we parse the string files
|
||||
// twice. Gah.
|
||||
SE_CheckForLanguageUpdates();
|
||||
|
||||
Z_PopNewDeleteTag();
|
||||
}
|
||||
|
||||
// returns error message else NULL for ok.
|
||||
//
|
||||
// Any errors that result from this should probably be treated as game-fatal, since an asset file is fuxored.
|
||||
//
|
||||
LPCSTR SE_LoadLanguage( LPCSTR psLanguage )
|
||||
{
|
||||
LPCSTR psErrorMessage = NULL;
|
||||
|
||||
if (psLanguage && psLanguage[0])
|
||||
{
|
||||
SE_NewLanguage();
|
||||
|
||||
string strResults;
|
||||
/*int iFilesFound = */SE_BuildFileList(
|
||||
#ifdef _STRINGED
|
||||
va("C:\\Source\\Tools\\StringEd\\test_data\\%s",sSE_STRINGS_DIR)
|
||||
#else
|
||||
sSE_STRINGS_DIR
|
||||
#endif
|
||||
, strResults
|
||||
);
|
||||
|
||||
LPCSTR p;
|
||||
while ( (p=SE_GetFoundFile (strResults)) != NULL && !psErrorMessage )
|
||||
{
|
||||
LPCSTR psThisLang = TheStringPackage.ExtractLanguageFromPath( p );
|
||||
|
||||
if ( !stricmp( psLanguage, psThisLang ) )
|
||||
{
|
||||
psErrorMessage = SE_Load( p );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( 0 && "SE_LoadLanguage(): Bad language name!" );
|
||||
}
|
||||
|
||||
return psErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
// called in Com_Frame, so don't take up any time! (can also be called during dedicated)
|
||||
//
|
||||
// instead of re-loading just the files we've already loaded I'm going to load the whole language (simpler)
|
||||
//
|
||||
void SE_CheckForLanguageUpdates(void)
|
||||
{
|
||||
if (se_language && se_language->modified)
|
||||
{
|
||||
LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string );
|
||||
if ( psErrorMessage )
|
||||
{
|
||||
Com_Error( ERR_DROP, psErrorMessage );
|
||||
}
|
||||
TheStringPackage.m_Strings->Sort();
|
||||
se_language->modified = SE_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////// eof //////////////////////////
|
||||
53
code/qcommon/stringed_ingame.h
Normal file
53
code/qcommon/stringed_ingame.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// Filename:- stringed_ingame.h
|
||||
//
|
||||
|
||||
#ifndef STRINGED_INGAME_H
|
||||
#define STRINGED_INGAME_H
|
||||
|
||||
|
||||
// alter these to suit your own game...
|
||||
//
|
||||
#define SE_BOOL qboolean
|
||||
#define SE_TRUE qtrue
|
||||
#define SE_FALSE qfalse
|
||||
#define iSE_MAX_FILENAME_LENGTH MAX_QPATH
|
||||
#define sSE_STRINGS_DIR "strings"
|
||||
|
||||
extern cvar_t *se_language;
|
||||
|
||||
// some needed text-equates, do not alter these under any circumstances !!!! (unless you're me. Which you're not)
|
||||
//
|
||||
#define iSE_VERSION 1
|
||||
#define sSE_KEYWORD_VERSION "VERSION"
|
||||
#define sSE_KEYWORD_CONFIG "CONFIG"
|
||||
#define sSE_KEYWORD_FILENOTES "FILENOTES"
|
||||
#define sSE_KEYWORD_REFERENCE "REFERENCE"
|
||||
#define sSE_KEYWORD_NOTES "NOTES"
|
||||
#define sSE_KEYWORD_LANG "LANG_"
|
||||
#define sSE_KEYWORD_ENDMARKER "ENDMARKER"
|
||||
#define sSE_FILE_EXTENSION ".st" // editor-only NEVER used ingame, but I wanted all extensions together
|
||||
#define sSE_EXPORT_FILE_EXTENSION ".ste"
|
||||
#define sSE_INGAME_FILE_EXTENSION ".str"
|
||||
#define sSE_EXPORT_SAME "#same"
|
||||
|
||||
|
||||
|
||||
// available API calls...
|
||||
//
|
||||
typedef const char *LPCSTR;
|
||||
|
||||
void SE_Init ( void );
|
||||
void SE_CheckForLanguageUpdates(void);
|
||||
LPCSTR SE_LoadLanguage ( LPCSTR psLanguage );
|
||||
void SE_NewLanguage ( void );
|
||||
//
|
||||
// for convenience, two ways of getting at the same data...
|
||||
//
|
||||
LPCSTR SE_GetString ( LPCSTR psPackageReference, LPCSTR psStringReference);
|
||||
LPCSTR SE_GetString ( LPCSTR psPackageAndStringReference);
|
||||
|
||||
|
||||
#endif // #ifndef STRINGED_INGAME_H
|
||||
|
||||
/////////////////// eof ////////////////
|
||||
|
||||
215
code/qcommon/stringed_interface.cpp
Normal file
215
code/qcommon/stringed_interface.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// Filename:- stringed_interface.cpp
|
||||
//
|
||||
// This file contains functions that StringEd wants to call to do things like load/save, they can be modified
|
||||
// for use ingame, but must remain functionally the same...
|
||||
//
|
||||
// Please try and put modifications for whichever games this is used for inside #defines, so I can copy the same file
|
||||
// into each project.
|
||||
//
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// stuff common to all qcommon files...
|
||||
#include "../server/server.h"
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
|
||||
#pragma warning ( disable : 4511 ) // copy constructor could not be generated
|
||||
#pragma warning ( disable : 4512 ) // assignment operator could not be generated
|
||||
#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah
|
||||
#include "stringed_interface.h"
|
||||
#include "stringed_ingame.h"
|
||||
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
#ifdef _STRINGED
|
||||
#include <stdlib.h>
|
||||
#include <memory.h>
|
||||
#include "generic.h"
|
||||
#endif
|
||||
|
||||
|
||||
// this just gets the binary of the file into memory, so I can parse it. Called by main SGE loader
|
||||
//
|
||||
// returns either char * of loaded file, else NULL for failed-to-open...
|
||||
//
|
||||
unsigned char *SE_LoadFileData( const char *psFileName, int *piLoadedLength /* = 0 */)
|
||||
{
|
||||
unsigned char *psReturn = NULL;
|
||||
if ( piLoadedLength )
|
||||
{
|
||||
*piLoadedLength = 0;
|
||||
}
|
||||
|
||||
#ifdef _STRINGED
|
||||
if (psFileName[1] == ':')
|
||||
{
|
||||
// full-path filename...
|
||||
//
|
||||
FILE *fh = fopen( psFileName, "rb" );
|
||||
if (fh)
|
||||
{
|
||||
long lLength = filesize(fh);
|
||||
|
||||
if (lLength > 0)
|
||||
{
|
||||
psReturn = (unsigned char *) malloc( lLength + 1);
|
||||
if (psReturn)
|
||||
{
|
||||
int iBytesRead = fread( psReturn, 1, lLength, fh );
|
||||
if (iBytesRead != lLength)
|
||||
{
|
||||
// error reading file!!!...
|
||||
//
|
||||
free(psReturn);
|
||||
psReturn = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
psReturn[ lLength ] = '\0';
|
||||
if ( piLoadedLength )
|
||||
{
|
||||
*piLoadedLength = iBytesRead;
|
||||
}
|
||||
}
|
||||
fclose(fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// local filename, so prepend the base dir etc according to game and load it however (from PAK?)
|
||||
//
|
||||
unsigned char *pvLoadedData;
|
||||
int iLen = FS_ReadFile( psFileName, (void **)&pvLoadedData );
|
||||
|
||||
if (iLen>0)
|
||||
{
|
||||
psReturn = pvLoadedData;
|
||||
if ( piLoadedLength )
|
||||
{
|
||||
*piLoadedLength = iLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return psReturn;
|
||||
}
|
||||
|
||||
|
||||
// called by main SGE code after loaded data has been parsedinto internal structures...
|
||||
//
|
||||
void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile )
|
||||
{
|
||||
#ifdef _STRINGED
|
||||
if ( psLoadedFile )
|
||||
{
|
||||
free( psLoadedFile );
|
||||
}
|
||||
#else
|
||||
if ( psLoadedFile )
|
||||
{
|
||||
FS_FreeFile( psLoadedFile );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifndef _STRINGED
|
||||
// quake-style method of doing things since their file-list code doesn't have a 'recursive' flag...
|
||||
//
|
||||
int giFilesFound;
|
||||
static void SE_R_ListFiles( const char *psExtension, const char *psDir, string &strResults )
|
||||
{
|
||||
// Com_Printf(va("Scanning Dir: %s\n",psDir));
|
||||
|
||||
char **sysFiles, **dirFiles;
|
||||
int numSysFiles, i, numdirs;
|
||||
|
||||
dirFiles = FS_ListFiles( psDir, "/", &numdirs);
|
||||
for (i=0;i<numdirs;i++)
|
||||
{
|
||||
if (dirFiles[i][0] && dirFiles[i][0] != '.') // skip blanks, plus ".", ".." etc
|
||||
{
|
||||
char sDirName[MAX_QPATH];
|
||||
sprintf(sDirName, "%s/%s", psDir, dirFiles[i]);
|
||||
//
|
||||
// for some reason the quake filesystem in this game now returns an extra slash on the end,
|
||||
// didn't used to. Sigh...
|
||||
//
|
||||
if (sDirName[strlen(sDirName)-1] == '/')
|
||||
{
|
||||
sDirName[strlen(sDirName)-1] = '\0';
|
||||
}
|
||||
SE_R_ListFiles( psExtension, sDirName, strResults );
|
||||
}
|
||||
}
|
||||
|
||||
sysFiles = FS_ListFiles( psDir, psExtension, &numSysFiles );
|
||||
for(i=0; i<numSysFiles; i++)
|
||||
{
|
||||
char sFilename[MAX_QPATH];
|
||||
sprintf(sFilename,"%s/%s", psDir, sysFiles[i]);
|
||||
|
||||
// Com_Printf("%sFound file: %s",!i?"\n":"",sFilename);
|
||||
|
||||
strResults += sFilename;
|
||||
strResults += ';';
|
||||
giFilesFound++;
|
||||
|
||||
// read it in...
|
||||
//
|
||||
/* byte *pbData = NULL;
|
||||
int iSize = FS_ReadFile( sFilename, (void **)&pbData);
|
||||
|
||||
if (pbData)
|
||||
{
|
||||
|
||||
FS_FreeFile( pbData );
|
||||
}
|
||||
*/
|
||||
}
|
||||
FS_FreeFileList( sysFiles );
|
||||
FS_FreeFileList( dirFiles );
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// replace this with a call to whatever your own code equivalent is.
|
||||
//
|
||||
// expected result is a ';'-delineated string (including last one) containing file-list search results
|
||||
//
|
||||
int SE_BuildFileList( const char *psStartDir, string &strResults )
|
||||
{
|
||||
#ifndef _STRINGED
|
||||
giFilesFound = 0;
|
||||
strResults = "";
|
||||
|
||||
SE_R_ListFiles( sSE_INGAME_FILE_EXTENSION, psStartDir, strResults );
|
||||
|
||||
return giFilesFound;
|
||||
#else
|
||||
// .ST files...
|
||||
//
|
||||
int iFilesFound = BuildFileList( va("%s\\*%s",psStartDir, sSE_INGAME_FILE_EXTENSION), // LPCSTR psPathAndFilter,
|
||||
true // bool bRecurseSubDirs
|
||||
);
|
||||
|
||||
extern string strResult;
|
||||
strResults = strResult;
|
||||
return iFilesFound;
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////// eof ///////////////////////
|
||||
|
||||
21
code/qcommon/stringed_interface.h
Normal file
21
code/qcommon/stringed_interface.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Filename:- stringed_interface.h
|
||||
//
|
||||
// These are the functions that get replaced by game-specific ones (or StringEd code) so SGE can access files etc
|
||||
//
|
||||
|
||||
#ifndef STRINGED_INTERFACE_H
|
||||
#define STRINGED_INTERFACE_H
|
||||
|
||||
#pragma warning ( disable : 4786 ) // disable the usual stupid and pointless STL warning
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
unsigned char * SE_LoadFileData ( const char *psFileName, int *piLoadedLength = 0);
|
||||
void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile );
|
||||
int SE_BuildFileList ( const char *psStartDir, string &strResults );
|
||||
|
||||
#endif // #ifndef STRINGED_INTERFACE_H
|
||||
|
||||
|
||||
////////////////// eof ///////////////////
|
||||
|
||||
13
code/qcommon/stv_version.h
Normal file
13
code/qcommon/stv_version.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Current version of the single player game
|
||||
#include "../win32/autoversion.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define Q3_VERSION "(debug)JA: v"VERSION_STRING_DOTTED
|
||||
#elif defined FINAL_BUILD
|
||||
#define Q3_VERSION "JA: v"VERSION_STRING_DOTTED
|
||||
#else
|
||||
#define Q3_VERSION "(internal)JA: v"VERSION_STRING_DOTTED
|
||||
#endif
|
||||
// end
|
||||
|
||||
|
||||
42
code/qcommon/tags.h
Normal file
42
code/qcommon/tags.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Filename:- tags.h
|
||||
|
||||
// do NOT include-protect this file, or add any fields or labels, because it's included within enums and tables
|
||||
//
|
||||
// these macro args get "TAG_" prepended on them for enum purposes, and appear as literal strings for "meminfo" command
|
||||
|
||||
TAGDEF(ALL),
|
||||
TAGDEF(HUNKALLOC), // mem that was formerly from the hunk AFTER the SetMark (ie discarded during vid_reset)
|
||||
TAGDEF(HUNKMISCMODELS), // sub-hunk alloc to track misc models
|
||||
TAGDEF(FILESYS), // general filesystem usage
|
||||
TAGDEF(LISTFILES), // for "*.blah" lists
|
||||
TAGDEF(AMBIENTSET),
|
||||
TAGDEF(G_ALLOC), // used by G_Alloc()
|
||||
TAGDEF(CLIENTS), // Memory used for client info
|
||||
TAGDEF(STATIC), // special usage for 1-byte allocations from 0..9 to avoid CopyString() slowdowns during cvar value copies
|
||||
TAGDEF(SMALL), // used by S_Malloc, but probably more of a hint now. Will be dumped later
|
||||
TAGDEF(MODEL_MD3), // specific model types' disk images
|
||||
TAGDEF(MODEL_GLM), // "
|
||||
TAGDEF(MODEL_GLA), // "
|
||||
TAGDEF(ICARUS), // Memory used internally by the Icarus scripting system
|
||||
TAGDEF(IMAGE_T), // an image_t struct (no longer on the hunk because of cached texture stuff)
|
||||
TAGDEF(TEMP_WORKSPACE), // anything like file loading or image workspace that's only temporary
|
||||
TAGDEF(SND_RAWDATA), // raw sound data, either MP3 or WAV
|
||||
TAGDEF(GHOUL2), // Ghoul2 stuff
|
||||
TAGDEF(BSP), // guess.
|
||||
TAGDEF(GP2), // generic parser 2
|
||||
TAGDEF(ANIMATION_CFG), // may as well keep this seperate / readable
|
||||
TAGDEF(SAVEGAME), // used for allocating chunks during savegame file read
|
||||
// TAGDEF(INFLATE), // Temp memory used by zlib32
|
||||
// TAGDEF(DEFLATE), // Temp memory used by zlib32
|
||||
TAGDEF(POINTCACHE), // weather effects
|
||||
TAGDEF(NEWDEL),
|
||||
TAGDEF(UI_ALLOC),
|
||||
TAGDEF(LIPSYNC),
|
||||
TAGDEF(FILELIST),
|
||||
TAGDEF(BINK),
|
||||
TAGDEF(STRINGED),
|
||||
|
||||
TAGDEF(COUNT)
|
||||
|
||||
//////////////// eof //////////////
|
||||
|
||||
62
code/qcommon/timing.h
Normal file
62
code/qcommon/timing.h
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
class timing_c
|
||||
{
|
||||
private:
|
||||
__int64 start;
|
||||
__int64 end;
|
||||
|
||||
int reset;
|
||||
public:
|
||||
timing_c(void)
|
||||
{
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
const __int64 *s = &start;
|
||||
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
push ebx
|
||||
push edx
|
||||
|
||||
rdtsc
|
||||
mov ebx, s
|
||||
mov [ebx], eax
|
||||
mov [ebx + 4], edx
|
||||
|
||||
pop edx
|
||||
pop ebx
|
||||
pop eax
|
||||
}
|
||||
}
|
||||
int End()
|
||||
{
|
||||
const __int64 *e = &end;
|
||||
__int64 time;
|
||||
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
push ebx
|
||||
push edx
|
||||
|
||||
rdtsc
|
||||
mov ebx, e
|
||||
mov [ebx], eax
|
||||
mov [ebx + 4], edx
|
||||
|
||||
pop edx
|
||||
pop ebx
|
||||
pop eax
|
||||
}
|
||||
time = end - start;
|
||||
if (time < 0)
|
||||
{
|
||||
time = 0;
|
||||
}
|
||||
return((int)time);
|
||||
}
|
||||
};
|
||||
|
||||
// end
|
||||
506
code/qcommon/tri_coll_test.cpp
Normal file
506
code/qcommon/tri_coll_test.cpp
Normal file
@@ -0,0 +1,506 @@
|
||||
/* Triangle/triangle intersection test routine,
|
||||
* by Tomas Moller, 1997.
|
||||
* See article "A Fast Triangle-Triangle Intersection Test",
|
||||
* Journal of Graphics Tools, 2(2), 1997
|
||||
*
|
||||
* int tri_tri_intersect(float V0[3],float V1[3],float V2[3],
|
||||
* float U0[3],float U1[3],float U2[3])
|
||||
*
|
||||
* parameters: vertices of triangle 1: V0,V1,V2
|
||||
* vertices of triangle 2: U0,U1,U2
|
||||
* result : returns 1 if the triangles intersect, otherwise 0
|
||||
*
|
||||
*/
|
||||
|
||||
// leave this at the top for PCH reasons...
|
||||
#include "common_headers.h"
|
||||
|
||||
|
||||
|
||||
|
||||
#include <math.h>
|
||||
#include "../game/q_shared.h"
|
||||
#include "../game/g_local.h"
|
||||
|
||||
/* if USE_EPSILON_TEST is true then we do a check:
|
||||
if |dv|<EPSILON then dv=0.0;
|
||||
else no check is done (which is less robust)
|
||||
*/
|
||||
#define USE_EPSILON_TEST 1
|
||||
#define EPSILON 0.000001
|
||||
|
||||
|
||||
/* some macros */
|
||||
#define CROSS(dest,v1,v2) \
|
||||
dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
|
||||
dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
|
||||
dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
|
||||
|
||||
#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
|
||||
|
||||
#define SUB(dest,v1,v2) \
|
||||
dest[0]=v1[0]-v2[0]; \
|
||||
dest[1]=v1[1]-v2[1]; \
|
||||
dest[2]=v1[2]-v2[2];
|
||||
|
||||
/* sort so that a<=b */
|
||||
#define SORT(a,b) \
|
||||
if(a>b) \
|
||||
{ \
|
||||
float c; \
|
||||
c=a; \
|
||||
a=b; \
|
||||
b=c; \
|
||||
}
|
||||
|
||||
#define ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1) \
|
||||
isect0=VV0+(VV1-VV0)*D0/(D0-D1); \
|
||||
isect1=VV0+(VV2-VV0)*D0/(D0-D2);
|
||||
|
||||
|
||||
#define COMPUTE_INTERVALS(VV0,VV1,VV2,D0,D1,D2,D0D1,D0D2,isect0,isect1) \
|
||||
if(D0D1>0.0f) \
|
||||
{ \
|
||||
/* here we know that D0D2<=0.0 */ \
|
||||
/* that is D0, D1 are on the same side, D2 on the other or on the plane */ \
|
||||
ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \
|
||||
} \
|
||||
else if(D0D2>0.0f) \
|
||||
{ \
|
||||
/* here we know that d0d1<=0.0 */ \
|
||||
ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \
|
||||
} \
|
||||
else if(D1*D2>0.0f || D0!=0.0f) \
|
||||
{ \
|
||||
/* here we know that d0d1<=0.0 or that D0!=0.0 */ \
|
||||
ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1); \
|
||||
} \
|
||||
else if(D1!=0.0f) \
|
||||
{ \
|
||||
ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \
|
||||
} \
|
||||
else if(D2!=0.0f) \
|
||||
{ \
|
||||
ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* triangles are coplanar */ \
|
||||
return coplanar_tri_tri(N1,V0,V1,V2,U0,U1,U2); \
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* this edge to edge test is based on Franlin Antonio's gem:
|
||||
"Faster Line Segment Intersection", in Graphics Gems III,
|
||||
pp. 199-202 */
|
||||
#define EDGE_EDGE_TEST(V0,U0,U1) \
|
||||
Bx=U0[i0]-U1[i0]; \
|
||||
By=U0[i1]-U1[i1]; \
|
||||
Cx=V0[i0]-U0[i0]; \
|
||||
Cy=V0[i1]-U0[i1]; \
|
||||
f=Ay*Bx-Ax*By; \
|
||||
d=By*Cx-Bx*Cy; \
|
||||
if((f>0 && d>=0 && d<=f) || (f<0 && d<=0 && d>=f)) \
|
||||
{ \
|
||||
e=Ax*Cy-Ay*Cx; \
|
||||
if(f>0) \
|
||||
{ \
|
||||
if(e>=0 && e<=f) return 1; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
if(e<=0 && e>=f) return 1; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2) \
|
||||
{ \
|
||||
float Ax,Ay,Bx,By,Cx,Cy,e,d,f; \
|
||||
Ax=V1[i0]-V0[i0]; \
|
||||
Ay=V1[i1]-V0[i1]; \
|
||||
/* test edge U0,U1 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0,U0,U1); \
|
||||
/* test edge U1,U2 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0,U1,U2); \
|
||||
/* test edge U2,U1 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0,U2,U0); \
|
||||
}
|
||||
|
||||
#define POINT_IN_TRI(V0,U0,U1,U2) \
|
||||
{ \
|
||||
float a,b,c,d0,d1,d2; \
|
||||
/* is T1 completly inside T2? */ \
|
||||
/* check if V0 is inside tri(U0,U1,U2) */ \
|
||||
a=U1[i1]-U0[i1]; \
|
||||
b=-(U1[i0]-U0[i0]); \
|
||||
c=-a*U0[i0]-b*U0[i1]; \
|
||||
d0=a*V0[i0]+b*V0[i1]+c; \
|
||||
\
|
||||
a=U2[i1]-U1[i1]; \
|
||||
b=-(U2[i0]-U1[i0]); \
|
||||
c=-a*U1[i0]-b*U1[i1]; \
|
||||
d1=a*V0[i0]+b*V0[i1]+c; \
|
||||
\
|
||||
a=U0[i1]-U2[i1]; \
|
||||
b=-(U0[i0]-U2[i0]); \
|
||||
c=-a*U2[i0]-b*U2[i1]; \
|
||||
d2=a*V0[i0]+b*V0[i1]+c; \
|
||||
if(d0*d1>0.0) \
|
||||
{ \
|
||||
if(d0*d2>0.0) return 1; \
|
||||
} \
|
||||
}
|
||||
|
||||
qboolean coplanar_tri_tri(vec3_t N,vec3_t V0,vec3_t V1,vec3_t V2,
|
||||
vec3_t U0,vec3_t U1,vec3_t U2)
|
||||
{
|
||||
vec3_t A;
|
||||
short i0,i1;
|
||||
/* first project onto an axis-aligned plane, that maximizes the area */
|
||||
/* of the triangles, compute indices: i0,i1. */
|
||||
A[0]=fabs(N[0]);
|
||||
A[1]=fabs(N[1]);
|
||||
A[2]=fabs(N[2]);
|
||||
if(A[0]>A[1])
|
||||
{
|
||||
if(A[0]>A[2])
|
||||
{
|
||||
i0=1; /* A[0] is greatest */
|
||||
i1=2;
|
||||
}
|
||||
else
|
||||
{
|
||||
i0=0; /* A[2] is greatest */
|
||||
i1=1;
|
||||
}
|
||||
}
|
||||
else /* A[0]<=A[1] */
|
||||
{
|
||||
if(A[2]>A[1])
|
||||
{
|
||||
i0=0; /* A[2] is greatest */
|
||||
i1=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
i0=0; /* A[1] is greatest */
|
||||
i1=2;
|
||||
}
|
||||
}
|
||||
|
||||
/* test all edges of triangle 1 against the edges of triangle 2 */
|
||||
EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2);
|
||||
EDGE_AGAINST_TRI_EDGES(V1,V2,U0,U1,U2);
|
||||
EDGE_AGAINST_TRI_EDGES(V2,V0,U0,U1,U2);
|
||||
|
||||
/* finally, test if tri1 is totally contained in tri2 or vice versa */
|
||||
POINT_IN_TRI(V0,U0,U1,U2);
|
||||
POINT_IN_TRI(U0,V0,V1,V2);
|
||||
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,
|
||||
vec3_t U0,vec3_t U1,vec3_t U2)
|
||||
{
|
||||
vec3_t E1,E2;
|
||||
vec3_t N1,N2;
|
||||
float d1,d2;
|
||||
float du0,du1,du2,dv0,dv1,dv2;
|
||||
vec3_t D;
|
||||
float isect1[2], isect2[2];
|
||||
float du0du1,du0du2,dv0dv1,dv0dv2;
|
||||
short index;
|
||||
float vp0,vp1,vp2;
|
||||
float up0,up1,up2;
|
||||
float b,c,max;
|
||||
|
||||
/* compute plane equation of triangle(V0,V1,V2) */
|
||||
SUB(E1,V1,V0);
|
||||
SUB(E2,V2,V0);
|
||||
CROSS(N1,E1,E2);
|
||||
d1=-DOT(N1,V0);
|
||||
/* plane equation 1: N1.X+d1=0 */
|
||||
|
||||
/* put U0,U1,U2 into plane equation 1 to compute signed distances to the plane*/
|
||||
du0=DOT(N1,U0)+d1;
|
||||
du1=DOT(N1,U1)+d1;
|
||||
du2=DOT(N1,U2)+d1;
|
||||
|
||||
/* coplanarity robustness check */
|
||||
#if USE_EPSILON_TEST
|
||||
if(fabs(du0)<EPSILON) du0=0.0;
|
||||
if(fabs(du1)<EPSILON) du1=0.0;
|
||||
if(fabs(du2)<EPSILON) du2=0.0;
|
||||
#endif
|
||||
du0du1=du0*du1;
|
||||
du0du2=du0*du2;
|
||||
|
||||
if(du0du1>0.0f && du0du2>0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute plane of triangle (U0,U1,U2) */
|
||||
SUB(E1,U1,U0);
|
||||
SUB(E2,U2,U0);
|
||||
CROSS(N2,E1,E2);
|
||||
d2=-DOT(N2,U0);
|
||||
/* plane equation 2: N2.X+d2=0 */
|
||||
|
||||
/* put V0,V1,V2 into plane equation 2 */
|
||||
dv0=DOT(N2,V0)+d2;
|
||||
dv1=DOT(N2,V1)+d2;
|
||||
dv2=DOT(N2,V2)+d2;
|
||||
|
||||
#if USE_EPSILON_TEST
|
||||
if(fabs(dv0)<EPSILON) dv0=0.0;
|
||||
if(fabs(dv1)<EPSILON) dv1=0.0;
|
||||
if(fabs(dv2)<EPSILON) dv2=0.0;
|
||||
#endif
|
||||
|
||||
dv0dv1=dv0*dv1;
|
||||
dv0dv2=dv0*dv2;
|
||||
|
||||
if(dv0dv1>0.0f && dv0dv2>0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute direction of intersection line */
|
||||
CROSS(D,N1,N2);
|
||||
|
||||
/* compute and index to the largest component of D */
|
||||
max=fabs(D[0]);
|
||||
index=0;
|
||||
b=fabs(D[1]);
|
||||
c=fabs(D[2]);
|
||||
if(b>max) max=b,index=1;
|
||||
if(c>max) max=c,index=2;
|
||||
|
||||
/* this is the simplified projection onto L*/
|
||||
vp0=V0[index];
|
||||
vp1=V1[index];
|
||||
vp2=V2[index];
|
||||
|
||||
up0=U0[index];
|
||||
up1=U1[index];
|
||||
up2=U2[index];
|
||||
|
||||
/* compute interval for triangle 1 */
|
||||
COMPUTE_INTERVALS(vp0,vp1,vp2,dv0,dv1,dv2,dv0dv1,dv0dv2,isect1[0],isect1[1]);
|
||||
|
||||
/* compute interval for triangle 2 */
|
||||
COMPUTE_INTERVALS(up0,up1,up2,du0,du1,du2,du0du1,du0du2,isect2[0],isect2[1]);
|
||||
|
||||
SORT(isect1[0],isect1[1]);
|
||||
SORT(isect2[0],isect2[1]);
|
||||
|
||||
if(isect1[1]<isect2[0] || isect2[1]<isect1[0]) return qtrue;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
float LineSegmentDistance( vec3_t a, vec3_t b, vec3_t c, vec3_t d )
|
||||
{
|
||||
vec3_t v1, v2, v3, cross;
|
||||
//FIXME: what if parallel or intersect?
|
||||
//FIXME: this doesn't take into account the endpoints...
|
||||
|
||||
//get the two lines
|
||||
VectorSubtract( b, a, v1 );
|
||||
VectorSubtract( c, d, v2 );
|
||||
|
||||
//get their normalized cross product
|
||||
CrossProduct( v1, v2, cross );
|
||||
/*
|
||||
float crossLength = VectorLength( cross );
|
||||
if ( crossLength == 0 )
|
||||
{//intersect! Or... parallel?
|
||||
return 0;
|
||||
}
|
||||
VectorScale( cross, 1/crossLength, cross );
|
||||
*/
|
||||
VectorNormalize( cross );
|
||||
|
||||
//now get a vector from v1 to v2
|
||||
VectorSubtract( d, a, v3 );
|
||||
|
||||
//distance is dot product of that new vector and the normalized cross product
|
||||
float dist = fabs( DotProduct( v3, cross ) );
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
|
||||
float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 )
|
||||
{
|
||||
float current_dist, new_dist;
|
||||
vec3_t new_pnt;
|
||||
//start1, end1 : the first segment
|
||||
//start2, end2 : the second segment
|
||||
|
||||
//output, one point on each segment, the closest two points on the segments.
|
||||
|
||||
//compute some temporaries:
|
||||
//vec start_dif = start2 - start1
|
||||
vec3_t start_dif;
|
||||
VectorSubtract( start2, start1, start_dif );
|
||||
//vec v1 = end1 - start1
|
||||
vec3_t v1;
|
||||
VectorSubtract( end1, start1, v1 );
|
||||
//vec v2 = end2 - start2
|
||||
vec3_t v2;
|
||||
VectorSubtract( end2, start2, v2 );
|
||||
//
|
||||
float v1v1 = DotProduct( v1, v1 );
|
||||
float v2v2 = DotProduct( v2, v2 );
|
||||
float v1v2 = DotProduct( v1, v2 );
|
||||
|
||||
//the main computation
|
||||
|
||||
float denom = (v1v2 * v1v2) - (v1v1 * v2v2);
|
||||
|
||||
//if denom is small, then skip all this and jump to the section marked below
|
||||
if ( fabs(denom) > 0.001f )
|
||||
{
|
||||
float s = -( (v2v2*DotProduct( v1, start_dif )) - (v1v2*DotProduct( v2, start_dif )) ) / denom;
|
||||
float t = ( (v1v1*DotProduct( v2, start_dif )) - (v1v2*DotProduct( v1, start_dif )) ) / denom;
|
||||
qboolean done = qtrue;
|
||||
|
||||
if ( s < 0 )
|
||||
{
|
||||
done = qfalse;
|
||||
s = 0;// and see note below
|
||||
}
|
||||
|
||||
if ( s > 1 )
|
||||
{
|
||||
done = qfalse;
|
||||
s = 1;// and see note below
|
||||
}
|
||||
|
||||
if ( t < 0 )
|
||||
{
|
||||
done = qfalse;
|
||||
t = 0;// and see note below
|
||||
}
|
||||
|
||||
if ( t > 1 )
|
||||
{
|
||||
done = qfalse;
|
||||
t = 1;// and see note below
|
||||
}
|
||||
|
||||
//vec close_pnt1 = start1 + s * v1
|
||||
VectorMA( start1, s, v1, close_pnt1 );
|
||||
//vec close_pnt2 = start2 + t * v2
|
||||
VectorMA( start2, t, v2, close_pnt2 );
|
||||
|
||||
current_dist = Distance( close_pnt1, close_pnt2 );
|
||||
//now, if none of those if's fired, you are done.
|
||||
if ( done )
|
||||
{
|
||||
return current_dist;
|
||||
}
|
||||
//If they did fire, then we need to do some additional tests.
|
||||
|
||||
//What we are gonna do is see if we can find a shorter distance than the above
|
||||
//involving the endpoints.
|
||||
}
|
||||
else
|
||||
{
|
||||
//******start here for paralell lines with current_dist = infinity****
|
||||
current_dist = Q3_INFINITE;
|
||||
}
|
||||
|
||||
//test 2 close_pnts first
|
||||
/*
|
||||
G_FindClosestPointOnLineSegment( start1, end1, close_pnt2, new_pnt );
|
||||
new_dist = Distance( close_pnt2, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( new_pnt, close_pnt1 );
|
||||
VectorCopy( close_pnt2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
G_FindClosestPointOnLineSegment( start2, end2, close_pnt1, new_pnt );
|
||||
new_dist = Distance( close_pnt1, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( close_pnt1, close_pnt1 );
|
||||
VectorCopy( new_pnt, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
*/
|
||||
//test all the endpoints
|
||||
new_dist = Distance( start1, start2 );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( start1, close_pnt1 );
|
||||
VectorCopy( start2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
new_dist = Distance( start1, end2 );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( start1, close_pnt1 );
|
||||
VectorCopy( end2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
new_dist = Distance( end1, start2 );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( end1, close_pnt1 );
|
||||
VectorCopy( start2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
new_dist = Distance( end1, end2 );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( end1, close_pnt1 );
|
||||
VectorCopy( end2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
//Then we have 4 more point / segment tests
|
||||
|
||||
G_FindClosestPointOnLineSegment( start2, end2, start1, new_pnt );
|
||||
new_dist = Distance( start1, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( start1, close_pnt1 );
|
||||
VectorCopy( new_pnt, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
G_FindClosestPointOnLineSegment( start2, end2, end1, new_pnt );
|
||||
new_dist = Distance( end1, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( end1, close_pnt1 );
|
||||
VectorCopy( new_pnt, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
G_FindClosestPointOnLineSegment( start1, end1, start2, new_pnt );
|
||||
new_dist = Distance( start2, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( new_pnt, close_pnt1 );
|
||||
VectorCopy( start2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
G_FindClosestPointOnLineSegment( start1, end1, end2, new_pnt );
|
||||
new_dist = Distance( end2, new_pnt );
|
||||
if ( new_dist < current_dist )
|
||||
{//then update close_pnt1 close_pnt2 and current_dist
|
||||
VectorCopy( new_pnt, close_pnt1 );
|
||||
VectorCopy( end2, close_pnt2 );
|
||||
current_dist = new_dist;
|
||||
}
|
||||
|
||||
return current_dist;
|
||||
}
|
||||
1348
code/qcommon/unzip.cpp
Normal file
1348
code/qcommon/unzip.cpp
Normal file
File diff suppressed because it is too large
Load Diff
286
code/qcommon/unzip.h
Normal file
286
code/qcommon/unzip.h
Normal file
@@ -0,0 +1,286 @@
|
||||
|
||||
#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
|
||||
/* like the STRICT of WIN32, we define a pointer that cannot be converted
|
||||
from (void*) without cast */
|
||||
typedef struct TagunzFile__ { int unused; } unzFile__;
|
||||
typedef unzFile__ *unzFile;
|
||||
#else
|
||||
typedef void* unzFile;
|
||||
#endif
|
||||
|
||||
#define ZIP_FILE FILE
|
||||
|
||||
/* tm_unz contain date/time info */
|
||||
typedef struct tm_unz_s
|
||||
{
|
||||
unsigned int tm_sec; /* seconds after the minute - [0,59] */
|
||||
unsigned int tm_min; /* minutes after the hour - [0,59] */
|
||||
unsigned int tm_hour; /* hours since midnight - [0,23] */
|
||||
unsigned int tm_mday; /* day of the month - [1,31] */
|
||||
unsigned int tm_mon; /* months since January - [0,11] */
|
||||
unsigned int tm_year; /* years - [1980..2044] */
|
||||
} tm_unz;
|
||||
|
||||
/* unz_global_info structure contain global data about the ZIPfile
|
||||
These data comes from the end of central dir */
|
||||
typedef struct unz_global_info_s
|
||||
{
|
||||
unsigned long number_entry; /* total number of entries in the central dir on this disk */
|
||||
unsigned long size_comment; /* size of the global comment of the zipfile */
|
||||
} unz_global_info;
|
||||
|
||||
|
||||
/* unz_file_info contain information about a file in the zipfile */
|
||||
typedef struct unz_file_info_s
|
||||
{
|
||||
unsigned long version; /* version made by 2 unsigned chars */
|
||||
unsigned long version_needed; /* version needed to extract 2 unsigned chars */
|
||||
unsigned long flag; /* general purpose bit flag 2 unsigned chars */
|
||||
unsigned long compression_method; /* compression method 2 unsigned chars */
|
||||
unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */
|
||||
unsigned long crc; /* crc-32 4 unsigned chars */
|
||||
unsigned long compressed_size; /* compressed size 4 unsigned chars */
|
||||
unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */
|
||||
unsigned long size_filename; /* filename length 2 unsigned chars */
|
||||
unsigned long size_file_extra; /* extra field length 2 unsigned chars */
|
||||
unsigned long size_file_comment; /* file comment length 2 unsigned chars */
|
||||
|
||||
unsigned long disk_num_start; /* disk number start 2 unsigned chars */
|
||||
unsigned long internal_fa; /* internal file attributes 2 unsigned chars */
|
||||
unsigned long external_fa; /* external file attributes 4 unsigned chars */
|
||||
|
||||
tm_unz tmu_date;
|
||||
} unz_file_info;
|
||||
|
||||
/* unz_file_info_interntal contain internal info about a file in zipfile*/
|
||||
typedef struct unz_file_info_internal_s
|
||||
{
|
||||
unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */
|
||||
} unz_file_info_internal;
|
||||
|
||||
/* file_in_zip_read_info_s contain internal information about a file in zipfile,
|
||||
when reading and decompress it */
|
||||
typedef struct
|
||||
{
|
||||
char *read_buffer; /* internal buffer for compressed data */
|
||||
z_stream stream; /* zLib stream structure for inflate */
|
||||
|
||||
unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/
|
||||
unsigned long stream_initialised; /* flag set if stream structure is initialised*/
|
||||
|
||||
unsigned long offset_local_extrafield;/* offset of the static extra field */
|
||||
unsigned int size_local_extrafield;/* size of the static extra field */
|
||||
unsigned long pos_local_extrafield; /* position in the static extra field in read*/
|
||||
|
||||
unsigned long crc32; /* crc32 of all data uncompressed */
|
||||
unsigned long crc32_wait; /* crc32 we must obtain after decompress all */
|
||||
unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */
|
||||
unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/
|
||||
ZIP_FILE *file; /* io structore of the zipfile */
|
||||
unsigned long compression_method; /* compression method (0==store) */
|
||||
unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
|
||||
} file_in_zip_read_info_s;
|
||||
|
||||
|
||||
/* unz_s contain internal information about the zipfile
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
ZIP_FILE* file; /* io structore of the zipfile */
|
||||
unz_global_info gi; /* public global information */
|
||||
unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
|
||||
unsigned long num_file; /* number of the current file in the zipfile*/
|
||||
unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/
|
||||
unsigned long current_file_ok; /* flag about the usability of the current file*/
|
||||
unsigned long central_pos; /* position of the beginning of the central dir*/
|
||||
|
||||
unsigned long size_central_dir; /* size of the central directory */
|
||||
unsigned long offset_central_dir; /* offset of start of central directory with
|
||||
respect to the starting disk number */
|
||||
|
||||
unz_file_info cur_file_info; /* public info about the current file in zip*/
|
||||
unz_file_info_internal cur_file_info_internal; /* private info about it*/
|
||||
file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current
|
||||
file if we are decompressing it */
|
||||
} unz_s;
|
||||
|
||||
#define UNZ_OK (0)
|
||||
#define UNZ_END_OF_LIST_OF_FILE (-100)
|
||||
#define UNZ_ERRNO (Z_DATA_ERROR)
|
||||
#define UNZ_EOF (0)
|
||||
#define UNZ_PARAMERROR (-102)
|
||||
#define UNZ_BADZIPFILE (-103)
|
||||
#define UNZ_INTERNALERROR (-104)
|
||||
#define UNZ_CRCERROR (-105)
|
||||
|
||||
#define UNZ_CASESENSITIVE 1
|
||||
#define UNZ_NOTCASESENSITIVE 2
|
||||
#define UNZ_OSDEFAULTCASE 0
|
||||
|
||||
extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity);
|
||||
|
||||
/*
|
||||
Compare two filename (fileName1,fileName2).
|
||||
If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
|
||||
If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
|
||||
or strcasecmp)
|
||||
If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
|
||||
(like 1 on Unix, 2 on Windows)
|
||||
*/
|
||||
|
||||
extern unzFile unzOpen (const char *path);
|
||||
extern unzFile unzReOpen (const char* path, unzFile file);
|
||||
|
||||
/*
|
||||
Open a Zip file. path contain the full pathname (by example,
|
||||
on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer
|
||||
"zlib/zlib111.zip".
|
||||
If the zipfile cannot be opened (file don't exist or in not valid), the
|
||||
return value is NULL.
|
||||
Else, the return value is a unzFile Handle, usable with other function
|
||||
of this unzip package.
|
||||
*/
|
||||
|
||||
extern int unzClose (unzFile file);
|
||||
|
||||
/*
|
||||
Close a ZipFile opened with unzipOpen.
|
||||
If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
|
||||
these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
|
||||
return UNZ_OK if there is no problem. */
|
||||
|
||||
extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info);
|
||||
|
||||
/*
|
||||
Write info about the ZipFile in the *pglobal_info structure.
|
||||
No preparation of the structure is needed
|
||||
return UNZ_OK if there is no problem. */
|
||||
|
||||
|
||||
extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf);
|
||||
|
||||
/*
|
||||
Get the global comment string of the ZipFile, in the szComment buffer.
|
||||
uSizeBuf is the size of the szComment buffer.
|
||||
return the number of unsigned char copied or an error code <0
|
||||
*/
|
||||
|
||||
|
||||
/***************************************************************************/
|
||||
/* Unzip package allow you browse the directory of the zipfile */
|
||||
|
||||
extern int unzGoToFirstFile (unzFile file);
|
||||
|
||||
/*
|
||||
Set the current file of the zipfile to the first file.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzGoToNextFile (unzFile file);
|
||||
|
||||
/*
|
||||
Set the current file of the zipfile to the next file.
|
||||
return UNZ_OK if there is no problem
|
||||
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
|
||||
*/
|
||||
|
||||
extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos );
|
||||
|
||||
/*
|
||||
Get the position of the info of the current file in the zip.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos );
|
||||
|
||||
/*
|
||||
Set the position of the info of the current file in the zip.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity);
|
||||
|
||||
/*
|
||||
Try locate the file szFileName in the zipfile.
|
||||
For the iCaseSensitivity signification, see unzStringFileNameCompare
|
||||
|
||||
return value :
|
||||
UNZ_OK if the file is found. It becomes the current file.
|
||||
UNZ_END_OF_LIST_OF_FILE if the file is not found
|
||||
*/
|
||||
|
||||
|
||||
extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize);
|
||||
|
||||
/*
|
||||
Get Info about the current file
|
||||
if pfile_info!=NULL, the *pfile_info structure will contain somes info about
|
||||
the current file
|
||||
if szFileName!=NULL, the filemane string will be copied in szFileName
|
||||
(fileNameBufferSize is the size of the buffer)
|
||||
if extraField!=NULL, the extra field information will be copied in extraField
|
||||
(extraFieldBufferSize is the size of the buffer).
|
||||
This is the Central-header version of the extra field
|
||||
if szComment!=NULL, the comment string of the file will be copied in szComment
|
||||
(commentBufferSize is the size of the buffer)
|
||||
*/
|
||||
|
||||
/***************************************************************************/
|
||||
/* for reading the content of the current zipfile, you can open it, read data
|
||||
from it, and close it (you can close it before reading all the file)
|
||||
*/
|
||||
|
||||
extern int unzOpenCurrentFile (unzFile file);
|
||||
|
||||
/*
|
||||
Open for reading data the current file in the zipfile.
|
||||
If there is no error, the return value is UNZ_OK.
|
||||
*/
|
||||
|
||||
extern int unzCloseCurrentFile (unzFile file);
|
||||
|
||||
/*
|
||||
Close the file in zip opened with unzOpenCurrentFile
|
||||
Return UNZ_CRCERROR if all the file was read but the CRC is not good
|
||||
*/
|
||||
|
||||
|
||||
extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len);
|
||||
|
||||
/*
|
||||
Read unsigned chars from the current file (opened by unzOpenCurrentFile)
|
||||
buf contain buffer where data must be copied
|
||||
len the size of buf.
|
||||
|
||||
return the number of unsigned char copied if somes unsigned chars are copied
|
||||
return 0 if the end of file was reached
|
||||
return <0 with error code if there is an error
|
||||
(UNZ_ERRNO for IO error, or zLib error for uncompress error)
|
||||
*/
|
||||
|
||||
extern long unztell(unzFile file);
|
||||
|
||||
/*
|
||||
Give the current position in uncompressed data
|
||||
*/
|
||||
|
||||
extern int unzeof (unzFile file);
|
||||
|
||||
/*
|
||||
return 1 if the end of file was reached, 0 elsewhere
|
||||
*/
|
||||
|
||||
extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len);
|
||||
|
||||
/*
|
||||
Read extra field from the current file (opened by unzOpenCurrentFile)
|
||||
This is the local-header version of the extra field (sometimes, there is
|
||||
more info in the local-header version than in the central-header)
|
||||
|
||||
if buf==NULL, it return the size of the local extra field
|
||||
|
||||
if buf!=NULL, len is the size of the buffer, the extra header is copied in
|
||||
buf.
|
||||
the return value is the number of unsigned chars copied in buf, or (if <0)
|
||||
the error code
|
||||
*/
|
||||
382
code/qcommon/xb_settings.cpp
Normal file
382
code/qcommon/xb_settings.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
|
||||
#include "xb_settings.h"
|
||||
#include <xtl.h>
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
#define SETTINGS_VERSION 0x00082877
|
||||
#define SETTINGS_DIRNAME "Settings"
|
||||
#define SETTINGS_FILENAME "settings.dat"
|
||||
#define SETTINGS_IMAGE "saveimage.xbx"
|
||||
#define SETTINGS_IMAGE_SRC "d:\\base\\media\\settings.xbx"
|
||||
|
||||
// The one copy of Settings:
|
||||
XBSettings Settings;
|
||||
const DWORD settingsSize = sizeof(Settings);
|
||||
const DWORD sigSize = sizeof(XCALCSIG_SIGNATURE);
|
||||
|
||||
// This isn't user data, don't put it in XBSettings!
|
||||
enum XBSettingsStatus
|
||||
{
|
||||
SETTINGS_OK, // Everything is ok
|
||||
SETTINGS_MISSING, // File is not on disk
|
||||
SETTINGS_CORRUPT, // File on disk is corrupt
|
||||
SETTINGS_FAILED, // General error
|
||||
};
|
||||
XBSettingsStatus SettingsStatus;
|
||||
|
||||
bool settingsDisabled = false;
|
||||
|
||||
const char *buttonConfigStrings[3] = {
|
||||
"weaponsbias",
|
||||
"forcebias",
|
||||
"southpaw",
|
||||
};
|
||||
|
||||
const char *triggerConfigStrings[2] = {
|
||||
"default",
|
||||
"southpaw",
|
||||
};
|
||||
|
||||
XBSettings::XBSettings( void )
|
||||
{
|
||||
version = SETTINGS_VERSION;
|
||||
|
||||
// Defaults:
|
||||
invertAim[0] = invertAim[1] = false;
|
||||
|
||||
thumbstickMode[0] = thumbstickMode[1] = 0;
|
||||
buttonMode[0] = buttonMode[1] = 0;
|
||||
triggerMode[0] = triggerMode[1] = 0;
|
||||
|
||||
rumble[0] = rumble[1] = 1;
|
||||
autolevel[0] = autolevel[0] = 0;
|
||||
autoswitch[0] = autoswitch[1] = 1;
|
||||
sensitivityX[0] = sensitivityX[1] = 2.0f;
|
||||
sensitivityY[0] = sensitivityY[1] = 2.0f;
|
||||
|
||||
hotswapSP[0] = hotswapSP[1] = hotswapSP[2] = -1;
|
||||
hotswapMP[0] = hotswapMP[1] = -1;
|
||||
hotswapMP[2] = hotswapMP[3] = -1;
|
||||
|
||||
effectsVolume = 1.0f;
|
||||
musicVolume = 0.25f;
|
||||
voiceVolume = 1.0f;
|
||||
brightness = 6.0f;
|
||||
|
||||
subtitles = 0;
|
||||
|
||||
#ifdef XBOX_DEMO
|
||||
// Demo has no foreign audio, so we turn subtitles on if Dash language is FR/GE
|
||||
DWORD dwLang = XGetLanguage();
|
||||
if( dwLang == XC_LANGUAGE_FRENCH || dwLang == XC_LANGUAGE_GERMAN )
|
||||
subtitles = 1;
|
||||
#endif
|
||||
|
||||
voiceMode = 2;
|
||||
voiceMask = 0;
|
||||
appearOffline = 0;
|
||||
|
||||
#ifdef XBOX_DEMO
|
||||
Disable(); // Ensure that we never try to load/save settings in the demo
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write the current stored settings to the HD:
|
||||
bool XBSettings::Save( void )
|
||||
{
|
||||
// Do nothing if user chose "Continue Without Saving"
|
||||
if( settingsDisabled )
|
||||
return true;
|
||||
|
||||
char settingsPath[128];
|
||||
char *pathEnd;
|
||||
DWORD dwWritten;
|
||||
|
||||
// Build the settings directory:
|
||||
unsigned short wideName[128];
|
||||
mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) );
|
||||
|
||||
// Open/create the settings directory:
|
||||
if (XCreateSaveGame( "U:\\", wideName, OPEN_ALWAYS, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS )
|
||||
{
|
||||
SettingsStatus = SETTINGS_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build path to settings file:
|
||||
pathEnd = settingsPath + strlen( settingsPath );
|
||||
strcpy( pathEnd, SETTINGS_FILENAME );
|
||||
|
||||
// Open/create the settings file:
|
||||
HANDLE hFile = CreateFile( settingsPath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile == INVALID_HANDLE_VALUE )
|
||||
{
|
||||
SettingsStatus = SETTINGS_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the data:
|
||||
if( !WriteFile( hFile, this, settingsSize, &dwWritten, NULL ) || (dwWritten != settingsSize) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_FAILED;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sign the data:
|
||||
XCALCSIG_SIGNATURE xsig;
|
||||
if( !Sign( &xsig ) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_FAILED;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write signature:
|
||||
if( !WriteFile( hFile, &xsig, sigSize, &dwWritten, NULL ) || (dwWritten != sigSize) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_FAILED;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Truncate and close file:
|
||||
SetEndOfFile( hFile );
|
||||
CloseHandle( hFile );
|
||||
|
||||
// Copy the save image over:
|
||||
strcpy( pathEnd, SETTINGS_IMAGE );
|
||||
CopyFile( SETTINGS_IMAGE_SRC, settingsPath, FALSE );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read saved settings from the HD:
|
||||
bool XBSettings::Load( void )
|
||||
{
|
||||
// Do nothing if user chose "Continue Without Saving"
|
||||
if( settingsDisabled )
|
||||
return true;
|
||||
|
||||
char settingsPath[128];
|
||||
char *pathEnd;
|
||||
DWORD dwRead;
|
||||
|
||||
// Build the settings directory:
|
||||
unsigned short wideName[128];
|
||||
mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) );
|
||||
|
||||
// Open the settings directory:
|
||||
if( XCreateSaveGame( "U:\\", wideName, OPEN_EXISTING, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS )
|
||||
{
|
||||
SettingsStatus = SETTINGS_MISSING;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build path to settings file:
|
||||
pathEnd = settingsPath + strlen( settingsPath );
|
||||
strcpy( pathEnd, SETTINGS_FILENAME );
|
||||
|
||||
HANDLE hFile = CreateFile( settingsPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile == INVALID_HANDLE_VALUE )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify file size:
|
||||
if( GetFileSize( hFile, NULL ) != (settingsSize + sigSize) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Temp struct to read data into:
|
||||
XBSettings temp;
|
||||
if( !ReadFile( hFile, &temp, settingsSize, &dwRead, NULL ) || (dwRead != settingsSize) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate signature over the read-in data:
|
||||
XCALCSIG_SIGNATURE xsig;
|
||||
if( !temp.Sign( &xsig ) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read in stored signature:
|
||||
XCALCSIG_SIGNATURE storedSig;
|
||||
if( !ReadFile( hFile, &storedSig, sigSize, &dwRead, NULL ) || (dwRead != sigSize) )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
CloseHandle( hFile );
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're done with the file:
|
||||
CloseHandle( hFile );
|
||||
|
||||
// Compare signatures:
|
||||
if( memcmp( &xsig, &storedSig, sigSize ) != 0 )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lastly, verify that the version number is right:
|
||||
if( temp.version != SETTINGS_VERSION )
|
||||
{
|
||||
SettingsStatus = SETTINGS_CORRUPT;
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK. The data checks out!
|
||||
*this = temp;
|
||||
|
||||
// TODO: Range-check all the values?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XBSettings::Delete( void )
|
||||
{
|
||||
// Build the settings directory:
|
||||
unsigned short wideName[128];
|
||||
mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) );
|
||||
|
||||
// Delete the game:
|
||||
XDeleteSaveGame( "U:\\", wideName );
|
||||
}
|
||||
|
||||
bool XBSettings::Corrupt( void )
|
||||
{
|
||||
return (SettingsStatus == SETTINGS_CORRUPT);
|
||||
}
|
||||
|
||||
bool XBSettings::Missing( void )
|
||||
{
|
||||
return (SettingsStatus == SETTINGS_MISSING);
|
||||
}
|
||||
|
||||
// Copy all stored settings into cvars
|
||||
void XBSettings::SetAll( void )
|
||||
{
|
||||
Cvar_SetValue( "m_pitch", invertAim[0] ? 0.022f : -0.022f );
|
||||
Cvar_SetValue( "ui_thumbStickMode", thumbstickMode[0] );
|
||||
|
||||
Cvar_Set( "ui_buttonconfig", buttonConfigStrings[buttonMode[0]] );
|
||||
Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/spbuttonConfig%d.cfg\n", buttonMode[0]) );
|
||||
|
||||
Cvar_Set( "ui_triggerconfig", triggerConfigStrings[triggerMode[0]] );
|
||||
Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/triggersConfig%d.cfg\n", triggerMode[0]) );
|
||||
|
||||
Cvar_SetValue( "in_useRumble", rumble[0] );
|
||||
Cvar_SetValue( "cl_autolevel", autolevel[0] );
|
||||
Cvar_SetValue( "cg_autoswitch", autoswitch[0] );
|
||||
|
||||
Cvar_SetValue( "sensitivity", sensitivityX[0] );
|
||||
Cvar_SetValue( "sensitivityY", sensitivityY[0] );
|
||||
|
||||
if( hotswapSP[0] >= 0 )
|
||||
Cvar_SetValue( "hotswap0", hotswapSP[0] );
|
||||
else
|
||||
Cvar_Set( "hotswap0", "" );
|
||||
|
||||
if( hotswapSP[1] >= 0 )
|
||||
Cvar_SetValue( "hotswap1", hotswapSP[1] );
|
||||
else
|
||||
Cvar_Set( "hotswap1", "" );
|
||||
|
||||
if( hotswapSP[2] >= 0 )
|
||||
Cvar_SetValue( "hotswap2", hotswapSP[2] );
|
||||
else
|
||||
Cvar_Set( "hotswap2", "" );
|
||||
|
||||
Cvar_SetValue( "s_effects_volume", effectsVolume );
|
||||
Cvar_SetValue( "s_music_volume", musicVolume );
|
||||
Cvar_SetValue( "s_voice_volume", voiceVolume );
|
||||
Cvar_SetValue( "s_brightness_volume", brightness );
|
||||
extern void GLimp_SetGamma(float);
|
||||
GLimp_SetGamma(Cvar_VariableValue( "s_brightness_volume" ) / 5.0f);
|
||||
|
||||
Cvar_SetValue( "g_subtitles", subtitles );
|
||||
}
|
||||
|
||||
#ifdef XBOX_DEMO
|
||||
void XBSettings::RestoreDefaults( void )
|
||||
{
|
||||
version = SETTINGS_VERSION;
|
||||
|
||||
// Defaults:
|
||||
invertAim[0] = invertAim[1] = false;
|
||||
|
||||
thumbstickMode[0] = thumbstickMode[1] = 0;
|
||||
buttonMode[0] = buttonMode[1] = 0;
|
||||
triggerMode[0] = triggerMode[1] = 0;
|
||||
|
||||
rumble[0] = rumble[1] = 1;
|
||||
autolevel[0] = autolevel[0] = 0;
|
||||
autoswitch[0] = autoswitch[1] = 1;
|
||||
sensitivityX[0] = sensitivityX[1] = 2.0f;
|
||||
sensitivityY[0] = sensitivityY[1] = 2.0f;
|
||||
|
||||
hotswapSP[0] = hotswapSP[1] = hotswapSP[2] = -1;
|
||||
hotswapMP[0] = hotswapMP[1] = -1;
|
||||
hotswapMP[2] = hotswapMP[3] = -1;
|
||||
|
||||
effectsVolume = 1.0f;
|
||||
musicVolume = 0.25f;
|
||||
voiceVolume = 1.0f;
|
||||
brightness = 6.0f;
|
||||
|
||||
subtitles = 0;
|
||||
|
||||
// Demo has no foreign audio, so we turn subtitles on if Dash language is FR/GE
|
||||
DWORD dwLang = XGetLanguage();
|
||||
if( dwLang == XC_LANGUAGE_FRENCH || dwLang == XC_LANGUAGE_GERMAN )
|
||||
subtitles = 1;
|
||||
|
||||
voiceMode = 2;
|
||||
voiceMask = 0;
|
||||
appearOffline = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Utility - signs the current contents of this XBSettings into the supplied struct:
|
||||
bool XBSettings::Sign( XCALCSIG_SIGNATURE *pSig )
|
||||
{
|
||||
// Start the signature:
|
||||
HANDLE hSig = XCalculateSignatureBegin( 0 );
|
||||
if( hSig == INVALID_HANDLE_VALUE )
|
||||
return false;
|
||||
|
||||
// Build the signature
|
||||
if( XCalculateSignatureUpdate( hSig, (BYTE *) this, sizeof(*this) ) != ERROR_SUCCESS )
|
||||
return false;
|
||||
|
||||
// Finish the signature:
|
||||
if( XCalculateSignatureEnd( hSig, pSig ) != ERROR_SUCCESS )
|
||||
return false;
|
||||
|
||||
// Done!
|
||||
return true;
|
||||
}
|
||||
|
||||
// Master switch for turning off settings when user picks
|
||||
// "Continue Without Saving"
|
||||
void XBSettings::Disable( void )
|
||||
{
|
||||
settingsDisabled = true;
|
||||
}
|
||||
|
||||
bool XBSettings::IsDisabled( void )
|
||||
{
|
||||
return settingsDisabled;
|
||||
}
|
||||
87
code/qcommon/xb_settings.h
Normal file
87
code/qcommon/xb_settings.h
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
#ifndef __XB_SETTINGS_H
|
||||
#define __XB_SETTINGS_H
|
||||
|
||||
#include <xtl.h>
|
||||
|
||||
enum XBStartupState
|
||||
{
|
||||
STARTUP_LOAD_SETTINGS,
|
||||
STARTUP_COMBINED_SPACE_CHECK,
|
||||
STARTUP_GAME_SPACE_CHECK,
|
||||
STARTUP_INVITE_CHECK,
|
||||
STARTUP_FINISH,
|
||||
};
|
||||
|
||||
// Minimum save size on Xbox. Bleh:
|
||||
#define SETTINGS_NUM_BLOCKS 4
|
||||
|
||||
struct XBSettings
|
||||
{
|
||||
// Magic number/revision stamp:
|
||||
unsigned long version;
|
||||
|
||||
// Controls, etc... One for SP/P1 in MP, other for P2 in MP:
|
||||
bool invertAim[2];
|
||||
int thumbstickMode[2];
|
||||
int buttonMode[2];
|
||||
int triggerMode[2];
|
||||
int rumble[2];
|
||||
int autolevel[2];
|
||||
int autoswitch[2];
|
||||
float sensitivityX[2];
|
||||
float sensitivityY[2];
|
||||
|
||||
// Black/White/X assignments, SP:
|
||||
int hotswapSP[3];
|
||||
|
||||
// Black/White for players one & two, MP:
|
||||
int hotswapMP[4];
|
||||
|
||||
// A/V settings, Global:
|
||||
float effectsVolume;
|
||||
float musicVolume;
|
||||
float voiceVolume;
|
||||
float brightness;
|
||||
|
||||
// Subtitles, only used in SP:
|
||||
int subtitles;
|
||||
|
||||
// Voice/Live options, only used in MP:
|
||||
int voiceMode;
|
||||
int voiceMask;
|
||||
int appearOffline;
|
||||
|
||||
// INTERFACE:
|
||||
|
||||
XBSettings( void );
|
||||
|
||||
bool Save( void );
|
||||
bool Load( void );
|
||||
void Delete( void );
|
||||
|
||||
// For determining why a Save/Load failed:
|
||||
bool Missing( void );
|
||||
bool Corrupt( void );
|
||||
|
||||
// This copies all settings from the Settings struct to their various cvars
|
||||
void SetAll( void );
|
||||
|
||||
// Turn off the settings file completely:
|
||||
void Disable( void );
|
||||
|
||||
// Has the user turned off saving (by choosing "Continue Without Saving")?
|
||||
bool IsDisabled( void );
|
||||
|
||||
#ifdef XBOX_DEMO
|
||||
void RestoreDefaults( void );
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool Sign( XCALCSIG_SIGNATURE *pSig );
|
||||
};
|
||||
|
||||
// One global copy (declared in xb_settings.cpp)
|
||||
extern XBSettings Settings;
|
||||
|
||||
#endif
|
||||
1882
code/qcommon/z_memman_console.cpp
Normal file
1882
code/qcommon/z_memman_console.cpp
Normal file
File diff suppressed because it is too large
Load Diff
958
code/qcommon/z_memman_pc.cpp
Normal file
958
code/qcommon/z_memman_pc.cpp
Normal file
@@ -0,0 +1,958 @@
|
||||
// Created 2/3/03 by Brian Osman - split Zone code from common.cpp
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
#include "../qcommon/sstring.h"
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
int giZoneSnaphotNum=0;
|
||||
#define DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE 256
|
||||
typedef sstring<DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE> sDebugString_t;
|
||||
#endif
|
||||
|
||||
static void Z_Details_f(void);
|
||||
|
||||
// define a string table of all mem tags...
|
||||
//
|
||||
#ifdef TAGDEF // itu?
|
||||
#undef TAGDEF
|
||||
#endif
|
||||
#define TAGDEF(blah) #blah
|
||||
static const char *psTagStrings[TAG_COUNT+1]= // +1 because TAG_COUNT will itself become a string here. Oh well.
|
||||
{
|
||||
#include "../qcommon/tags.h"
|
||||
};
|
||||
|
||||
// This handles zone memory allocation.
|
||||
// It is a wrapper around malloc with a tag id and a magic number at the start
|
||||
|
||||
#define ZONE_MAGIC 0x21436587
|
||||
|
||||
// if you change ANYTHING in this structure, be sure to update the tables below using DEF_STATIC...
|
||||
//
|
||||
typedef struct zoneHeader_s
|
||||
{
|
||||
int iMagic;
|
||||
memtag_t eTag;
|
||||
int iSize;
|
||||
struct zoneHeader_s *pNext;
|
||||
struct zoneHeader_s *pPrev;
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
char sSrcFileBaseName[MAX_QPATH];
|
||||
int iSrcFileLineNum;
|
||||
char sOptionalLabel[DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE];
|
||||
int iSnapshotNumber;
|
||||
#endif
|
||||
|
||||
} zoneHeader_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int iMagic;
|
||||
|
||||
} zoneTail_t;
|
||||
|
||||
static inline zoneTail_t *ZoneTailFromHeader(zoneHeader_t *pHeader)
|
||||
{
|
||||
return (zoneTail_t*) ( (char*)pHeader + sizeof(*pHeader) + pHeader->iSize );
|
||||
}
|
||||
|
||||
#ifdef DETAILED_ZONE_DEBUG_CODE
|
||||
map <void*,int> mapAllocatedZones;
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct zoneStats_s
|
||||
{
|
||||
int iCount;
|
||||
int iCurrent;
|
||||
int iPeak;
|
||||
|
||||
// I'm keeping these updated on the fly, since it's quicker for cache-pool
|
||||
// purposes rather than recalculating each time...
|
||||
//
|
||||
int iSizesPerTag [TAG_COUNT];
|
||||
int iCountsPerTag[TAG_COUNT];
|
||||
|
||||
} zoneStats_t;
|
||||
|
||||
typedef struct zone_s
|
||||
{
|
||||
zoneStats_t Stats;
|
||||
zoneHeader_t Header;
|
||||
} zone_t;
|
||||
|
||||
cvar_t *com_validateZone;
|
||||
|
||||
zone_t TheZone = {0};
|
||||
|
||||
|
||||
|
||||
|
||||
// Scans through the linked list of mallocs and makes sure no data has been overwritten
|
||||
|
||||
int Z_Validate(void)
|
||||
{
|
||||
int ret=0;
|
||||
if(!com_validateZone || !com_validateZone->integer)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
#ifdef DETAILED_ZONE_DEBUG_CODE
|
||||
// this won't happen here, but wtf?
|
||||
int& iAllocCount = mapAllocatedZones[pMemory];
|
||||
if (iAllocCount <= 0)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Validate(): Bad block allocation count!");
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// this block of code is intended to make sure all of the data is paged in
|
||||
if (pMemory->eTag != TAG_IMAGE_T
|
||||
&& pMemory->eTag != TAG_MODEL_MD3
|
||||
&& pMemory->eTag != TAG_MODEL_GLM
|
||||
&& pMemory->eTag != TAG_MODEL_GLA ) //don't bother with disk caches as they've already been hit or will be thrown out next
|
||||
{
|
||||
unsigned char *memstart = (unsigned char *)pMemory;
|
||||
int totalSize = pMemory->iSize;
|
||||
while (totalSize > 4096)
|
||||
{
|
||||
memstart += 4096;
|
||||
ret += (int)(*memstart); // this fools the optimizer
|
||||
totalSize -= 4096;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pMemory = pMemory->pNext;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// static mem blocks to reduce a lot of small zone overhead
|
||||
//
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
typedef struct
|
||||
{
|
||||
zoneHeader_t Header;
|
||||
// byte mem[0];
|
||||
zoneTail_t Tail;
|
||||
} StaticZeroMem_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
zoneHeader_t Header;
|
||||
byte mem[2];
|
||||
zoneTail_t Tail;
|
||||
} StaticMem_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
const static StaticZeroMem_t gZeroMalloc =
|
||||
{ {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}};
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
#define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL, "<static>",0,"",0},_char,'\0',{ZONE_MAGIC}
|
||||
#else
|
||||
#define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL },_char,'\0',{ZONE_MAGIC}
|
||||
#endif
|
||||
|
||||
const static StaticMem_t gEmptyString =
|
||||
{ DEF_STATIC('\0') };
|
||||
|
||||
const static StaticMem_t gNumberString[] = {
|
||||
{ DEF_STATIC('0') },
|
||||
{ DEF_STATIC('1') },
|
||||
{ DEF_STATIC('2') },
|
||||
{ DEF_STATIC('3') },
|
||||
{ DEF_STATIC('4') },
|
||||
{ DEF_STATIC('5') },
|
||||
{ DEF_STATIC('6') },
|
||||
{ DEF_STATIC('7') },
|
||||
{ DEF_STATIC('8') },
|
||||
{ DEF_STATIC('9') },
|
||||
};
|
||||
|
||||
qboolean gbMemFreeupOccured = qfalse;
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine)
|
||||
#else
|
||||
void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int unusedAlign)
|
||||
#endif
|
||||
{
|
||||
gbMemFreeupOccured = qfalse;
|
||||
|
||||
if (iSize == 0)
|
||||
{
|
||||
zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc;
|
||||
return &pMemory[1];
|
||||
}
|
||||
|
||||
// Add in tracking info and round to a longword... (ignore longword aligning now we're not using contiguous blocks)
|
||||
//
|
||||
// int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t) + 3) & 0xfffffffc;
|
||||
int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t));
|
||||
|
||||
// Allocate a chunk...
|
||||
//
|
||||
zoneHeader_t *pMemory = NULL;
|
||||
while (pMemory == NULL)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (gbMemFreeupOccured)
|
||||
{
|
||||
Sleep(1000); // sleep for a second, so Windows has a chance to shuffle mem to de-swiss-cheese it
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bZeroit) {
|
||||
pMemory = (zoneHeader_t *) calloc ( iRealSize, 1 );
|
||||
} else {
|
||||
pMemory = (zoneHeader_t *) malloc ( iRealSize );
|
||||
}
|
||||
if (!pMemory)
|
||||
{
|
||||
// new bit, if we fail to malloc memory, try dumping some of the cached stuff that's non-vital and try again...
|
||||
//
|
||||
|
||||
// ditch the BSP cache...
|
||||
//
|
||||
if (CM_DeleteCachedMap(qfalse))
|
||||
{
|
||||
gbMemFreeupOccured = qtrue;
|
||||
continue; // we've just ditched a whole load of memory, so try again with the malloc
|
||||
}
|
||||
|
||||
|
||||
// ditch any sounds not used on this level...
|
||||
//
|
||||
extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel);
|
||||
if (SND_RegisterAudio_LevelLoadEnd(qtrue))
|
||||
{
|
||||
gbMemFreeupOccured = qtrue;
|
||||
continue; // we've dropped at least one sound, so try again with the malloc
|
||||
}
|
||||
|
||||
|
||||
// ditch any image_t's (and associated GL texture mem) not used on this level...
|
||||
//
|
||||
extern qboolean RE_RegisterImages_LevelLoadEnd(void);
|
||||
if (RE_RegisterImages_LevelLoadEnd())
|
||||
{
|
||||
gbMemFreeupOccured = qtrue;
|
||||
continue; // we've dropped at least one image, so try again with the malloc
|
||||
}
|
||||
|
||||
|
||||
// ditch the model-binaries cache... (must be getting desperate here!)
|
||||
//
|
||||
extern qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel);
|
||||
if (RE_RegisterModels_LevelLoadEnd(qtrue))
|
||||
{
|
||||
gbMemFreeupOccured = qtrue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// as a last panic measure, dump all the audio memory, but not if we're in the audio loader
|
||||
// (which is annoying, but I'm not sure how to ensure we're not dumping any memory needed by the sound
|
||||
// currently being loaded if that was the case)...
|
||||
//
|
||||
// note that this keeps querying until it's freed up as many bytes as the requested size, but freeing
|
||||
// several small blocks might not mean that one larger one is satisfiable after freeup, however that'll
|
||||
// just make it go round again and try for freeing up another bunch of blocks until the total is satisfied
|
||||
// again (though this will have freed twice the requested amount in that case), so it'll either work
|
||||
// eventually or not free up enough and drop through to the final ERR_DROP. No worries...
|
||||
//
|
||||
extern qboolean gbInsideLoadSound;
|
||||
extern int SND_FreeOldestSound(void); // I had to add a void-arg version of this because of link issues, sigh
|
||||
if (!gbInsideLoadSound)
|
||||
{
|
||||
int iBytesFreed = SND_FreeOldestSound();
|
||||
if (iBytesFreed)
|
||||
{
|
||||
int iTheseBytesFreed = 0;
|
||||
while ( (iTheseBytesFreed = SND_FreeOldestSound()) != 0)
|
||||
{
|
||||
iBytesFreed += iTheseBytesFreed;
|
||||
if (iBytesFreed >= iRealSize)
|
||||
break; // early opt-out since we've managed to recover enough (mem-contiguity issues aside)
|
||||
}
|
||||
gbMemFreeupOccured = qtrue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// sigh, dunno what else to try, I guess we'll have to give up and report this as an out-of-mem error...
|
||||
//
|
||||
// findlabel: "recovermem"
|
||||
|
||||
Com_Printf(S_COLOR_RED"Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]);
|
||||
Z_Details_f();
|
||||
Com_Error(ERR_FATAL,"(Repeat): Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
extern char *Filename_WithoutPath(const char *psFilename);
|
||||
|
||||
Q_strncpyz(pMemory->sSrcFileBaseName, Filename_WithoutPath(psFile), sizeof(pMemory->sSrcFileBaseName));
|
||||
pMemory->iSrcFileLineNum = iLine;
|
||||
pMemory->sOptionalLabel[0] = '\0';
|
||||
pMemory->iSnapshotNumber = giZoneSnaphotNum;
|
||||
#endif
|
||||
|
||||
// Link in
|
||||
pMemory->iMagic = ZONE_MAGIC;
|
||||
pMemory->eTag = eTag;
|
||||
pMemory->iSize = iSize;
|
||||
pMemory->pNext = TheZone.Header.pNext;
|
||||
TheZone.Header.pNext = pMemory;
|
||||
if (pMemory->pNext)
|
||||
{
|
||||
pMemory->pNext->pPrev = pMemory;
|
||||
}
|
||||
pMemory->pPrev = &TheZone.Header;
|
||||
//
|
||||
// add tail...
|
||||
//
|
||||
ZoneTailFromHeader(pMemory)->iMagic = ZONE_MAGIC;
|
||||
|
||||
// Update stats...
|
||||
//
|
||||
TheZone.Stats.iCurrent += iSize;
|
||||
TheZone.Stats.iCount++;
|
||||
TheZone.Stats.iSizesPerTag [eTag] += iSize;
|
||||
TheZone.Stats.iCountsPerTag [eTag]++;
|
||||
|
||||
if (TheZone.Stats.iCurrent > TheZone.Stats.iPeak)
|
||||
{
|
||||
TheZone.Stats.iPeak = TheZone.Stats.iCurrent;
|
||||
}
|
||||
|
||||
#ifdef DETAILED_ZONE_DEBUG_CODE
|
||||
mapAllocatedZones[pMemory]++;
|
||||
#endif
|
||||
|
||||
Z_Validate(); // check for corruption
|
||||
|
||||
void *pvReturnMem = &pMemory[1];
|
||||
return pvReturnMem;
|
||||
}
|
||||
|
||||
// used during model cacheing to save an extra malloc, lets us morph the disk-load buffer then
|
||||
// just not fs_freefile() it afterwards.
|
||||
//
|
||||
void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag )
|
||||
{
|
||||
zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
|
||||
|
||||
if (pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_MorphMallocTag(): Not a valid zone header!");
|
||||
return; // won't get here
|
||||
}
|
||||
|
||||
// DEC existing tag stats...
|
||||
//
|
||||
// TheZone.Stats.iCurrent - unchanged
|
||||
// TheZone.Stats.iCount - unchanged
|
||||
TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize;
|
||||
TheZone.Stats.iCountsPerTag [pMemory->eTag]--;
|
||||
|
||||
// morph...
|
||||
//
|
||||
pMemory->eTag = eDesiredTag;
|
||||
|
||||
// INC new tag stats...
|
||||
//
|
||||
// TheZone.Stats.iCurrent - unchanged
|
||||
// TheZone.Stats.iCount - unchanged
|
||||
TheZone.Stats.iSizesPerTag [pMemory->eTag] += pMemory->iSize;
|
||||
TheZone.Stats.iCountsPerTag [pMemory->eTag]++;
|
||||
}
|
||||
|
||||
|
||||
static int Zone_FreeBlock(zoneHeader_t *pMemory)
|
||||
{
|
||||
const int iSize = pMemory->iSize;
|
||||
if (pMemory->eTag != TAG_STATIC) // belt and braces, should never hit this though
|
||||
{
|
||||
// Update stats...
|
||||
//
|
||||
TheZone.Stats.iCount--;
|
||||
TheZone.Stats.iCurrent -= pMemory->iSize;
|
||||
TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize;
|
||||
TheZone.Stats.iCountsPerTag [pMemory->eTag]--;
|
||||
|
||||
// Sanity checks...
|
||||
//
|
||||
assert(pMemory->pPrev->pNext == pMemory);
|
||||
assert(!pMemory->pNext || (pMemory->pNext->pPrev == pMemory));
|
||||
|
||||
// Unlink and free...
|
||||
//
|
||||
pMemory->pPrev->pNext = pMemory->pNext;
|
||||
if(pMemory->pNext)
|
||||
{
|
||||
pMemory->pNext->pPrev = pMemory->pPrev;
|
||||
}
|
||||
|
||||
//debugging double frees
|
||||
pMemory->iMagic = 'FREE';
|
||||
free (pMemory);
|
||||
|
||||
|
||||
#ifdef DETAILED_ZONE_DEBUG_CODE
|
||||
// this has already been checked for in execution order, but wtf?
|
||||
int& iAllocCount = mapAllocatedZones[pMemory];
|
||||
if (iAllocCount == 0)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Zone_FreeBlock(): Double-freeing block!");
|
||||
return -1;
|
||||
}
|
||||
iAllocCount--;
|
||||
#endif
|
||||
}
|
||||
return iSize;
|
||||
}
|
||||
|
||||
// stats-query function to to see if it's our malloc
|
||||
// returns block size if so
|
||||
qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag)
|
||||
{
|
||||
zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
|
||||
#if 1 //debugging double free
|
||||
if (pMemory->iMagic == 'FREE')
|
||||
{
|
||||
Com_Printf("Z_IsFromZone(%x): Ptr has been freed already!(%9s)\n",pvAddress,pvAddress);
|
||||
return qfalse;
|
||||
}
|
||||
#endif
|
||||
if (pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
//looks like it is from our zone, let's double check the tag
|
||||
|
||||
if (pMemory->eTag != eTag)
|
||||
{
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
return pMemory->iSize;
|
||||
}
|
||||
|
||||
// stats-query function to ask how big a malloc is...
|
||||
//
|
||||
int Z_Size(void *pvAddress)
|
||||
{
|
||||
zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
|
||||
|
||||
if (pMemory->eTag == TAG_STATIC)
|
||||
{
|
||||
return 0; // kind of
|
||||
}
|
||||
|
||||
if (pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!");
|
||||
return 0; // won't get here
|
||||
}
|
||||
|
||||
return pMemory->iSize;
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
void _D_Z_Label(const void *pvAddress, const char *psLabel)
|
||||
{
|
||||
zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
|
||||
|
||||
if (pMemory->eTag == TAG_STATIC)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "_D_Z_Label(): Not a valid zone header!");
|
||||
}
|
||||
|
||||
Q_strncpyz( pMemory->sOptionalLabel, psLabel, sizeof(pMemory->sOptionalLabel));
|
||||
pMemory->sOptionalLabel[ sizeof(pMemory->sOptionalLabel)-1 ] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Frees a block of memory...
|
||||
//
|
||||
int Z_Free(void *pvAddress)
|
||||
{
|
||||
if (!TheZone.Stats.iCount)
|
||||
{
|
||||
//Com_Error(ERR_FATAL, "Z_Free(): Zone has been cleard already!");
|
||||
Com_Printf("Z_Free(%x): Zone has been cleard already!\n",pvAddress);
|
||||
return -1;
|
||||
}
|
||||
|
||||
zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
|
||||
|
||||
#if 1 //debugging double free
|
||||
if (pMemory->iMagic == 'FREE')
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Free(%s): Block already-freed, or not allocated through Z_Malloc!",pvAddress);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pMemory->eTag == TAG_STATIC)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef DETAILED_ZONE_DEBUG_CODE
|
||||
//
|
||||
// check this error *before* barfing on bad magics...
|
||||
//
|
||||
int& iAllocCount = mapAllocatedZones[pMemory];
|
||||
if (iAllocCount <= 0)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Free(): Block already-freed, or not allocated through Z_Malloc!");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pMemory->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!");
|
||||
return -1;
|
||||
}
|
||||
if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC)
|
||||
{
|
||||
Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Zone_FreeBlock(pMemory);
|
||||
}
|
||||
|
||||
|
||||
int Z_MemSize(memtag_t eTag)
|
||||
{
|
||||
return TheZone.Stats.iSizesPerTag[eTag];
|
||||
}
|
||||
|
||||
// Frees all blocks with the specified tag...
|
||||
//
|
||||
void Z_TagFree(memtag_t eTag)
|
||||
{
|
||||
//#ifdef _DEBUG
|
||||
// int iZoneBlocks = TheZone.Stats.iCount;
|
||||
//#endif
|
||||
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
zoneHeader_t *pNext = pMemory->pNext;
|
||||
if ( (eTag == TAG_ALL) || (pMemory->eTag == eTag))
|
||||
{
|
||||
Zone_FreeBlock(pMemory);
|
||||
}
|
||||
pMemory = pNext;
|
||||
}
|
||||
|
||||
// these stupid pragmas don't work here???!?!?!
|
||||
//
|
||||
//#ifdef _DEBUG
|
||||
//#pragma warning( disable : 4189)
|
||||
// int iBlocksFreed = iZoneBlocks - TheZone.Stats.iCount;
|
||||
//#pragma warning( default : 4189)
|
||||
//#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
void *_D_S_Malloc ( int iSize, const char *psFile, int iLine)
|
||||
{
|
||||
return _D_Z_Malloc( iSize, TAG_SMALL, qfalse, psFile, iLine );
|
||||
}
|
||||
#else
|
||||
void *S_Malloc( int iSize )
|
||||
{
|
||||
return Z_Malloc( iSize, TAG_SMALL, qfalse);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
static void Z_MemRecoverTest_f(void)
|
||||
{
|
||||
// needs to be in _DEBUG only, not good for final game!
|
||||
//
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf( "Usage: zone_memrecovertest max2alloc\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
int iMaxAlloc = 1024*1024*atoi( Cmd_Argv(1) );
|
||||
int iTotalMalloc = 0;
|
||||
while (1)
|
||||
{
|
||||
const int iThisMalloc = 5* (1024 * 1024);
|
||||
Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse); // and lose, just to consume memory
|
||||
iTotalMalloc += iThisMalloc;
|
||||
|
||||
if (gbMemFreeupOccured || (iTotalMalloc >= iMaxAlloc) )
|
||||
break;
|
||||
}
|
||||
|
||||
Z_TagFree(TAG_SPECIAL_MEM_TEST);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Gives a summary of the zone memory usage
|
||||
|
||||
static void Z_Stats_f(void)
|
||||
{
|
||||
Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n",
|
||||
TheZone.Stats.iCurrent,
|
||||
(float)TheZone.Stats.iCurrent / 1024.0f / 1024.0f,
|
||||
TheZone.Stats.iCount
|
||||
);
|
||||
|
||||
Com_Printf("The zone peaked at %d bytes (%.2fMB)\n",
|
||||
TheZone.Stats.iPeak,
|
||||
(float)TheZone.Stats.iPeak / 1024.0f / 1024.0f
|
||||
);
|
||||
}
|
||||
|
||||
// Gives a detailed breakdown of the memory blocks in the zone
|
||||
//
|
||||
static void Z_Details_f(void)
|
||||
{
|
||||
|
||||
Com_Printf("---------------------------------------------------------------------------\n");
|
||||
Com_Printf("%20s %9s\n","Zone Tag","Bytes");
|
||||
Com_Printf("%20s %9s\n","--------","-----");
|
||||
for (int i=0; i<TAG_COUNT; i++)
|
||||
{
|
||||
int iThisCount = TheZone.Stats.iCountsPerTag[i];
|
||||
int iThisSize = TheZone.Stats.iSizesPerTag [i];
|
||||
|
||||
if (iThisCount)
|
||||
{
|
||||
// can you believe that using %2.2f as a format specifier doesn't bloody work?
|
||||
// It ignores the left-hand specifier. Sigh, now I've got to do shit like this...
|
||||
//
|
||||
float fSize = (float)(iThisSize) / 1024.0f / 1024.0f;
|
||||
int iSize = fSize;
|
||||
int iRemainder = 100.0f * (fSize - floor(fSize));
|
||||
Com_Printf("%20s %9d (%2d.%02dMB) in %6d blocks (%9d Bytes/block)\n",
|
||||
psTagStrings[i],
|
||||
iThisSize,
|
||||
iSize,iRemainder,
|
||||
iThisCount, iThisSize / iThisCount
|
||||
);
|
||||
}
|
||||
}
|
||||
Com_Printf("---------------------------------------------------------------------------\n");
|
||||
|
||||
Z_Stats_f();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
#pragma warning (disable:4503) // decorated name length xceeded, name was truncated
|
||||
typedef map <sDebugString_t,int> LabelRefCount_t; // yet another place where Gil's tring class works and MS's doesn't
|
||||
typedef map <sDebugString_t,LabelRefCount_t> TagBlockLabels_t;
|
||||
TagBlockLabels_t AllTagBlockLabels;
|
||||
#pragma warning (disable:4503) // decorated name length xceeded, name was truncated
|
||||
static void Z_Snapshot_f(void)
|
||||
{
|
||||
AllTagBlockLabels.clear();
|
||||
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
AllTagBlockLabels[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++;
|
||||
pMemory = pMemory->pNext;
|
||||
}
|
||||
|
||||
giZoneSnaphotNum++;
|
||||
Com_Printf("Ok. ( Current snapshot num is now %d )\n",giZoneSnaphotNum);
|
||||
}
|
||||
|
||||
static void Z_TagDebug_f(void)
|
||||
{
|
||||
TagBlockLabels_t AllTagBlockLabels_Local;
|
||||
qboolean bSnapShotTestActive = qfalse;
|
||||
|
||||
memtag_t eTag = TAG_ALL;
|
||||
|
||||
const char *psTAGName = Cmd_Argv(1);
|
||||
if (psTAGName[0])
|
||||
{
|
||||
// check optional arg...
|
||||
//
|
||||
if (!Q_stricmp(psTAGName,"#snap"))
|
||||
{
|
||||
bSnapShotTestActive = qtrue;
|
||||
|
||||
AllTagBlockLabels_Local = AllTagBlockLabels; // horrible great STL copy
|
||||
|
||||
psTAGName = Cmd_Argv(2);
|
||||
}
|
||||
|
||||
if (psTAGName[0])
|
||||
{
|
||||
// skip over "tag_" if user supplied it...
|
||||
//
|
||||
if (!Q_stricmpn(psTAGName,"TAG_",4))
|
||||
{
|
||||
psTAGName += 4;
|
||||
}
|
||||
|
||||
// see if the user specified a valid tag...
|
||||
//
|
||||
for (int i=0; i<TAG_COUNT; i++)
|
||||
{
|
||||
if (!Q_stricmp(psTAGName,psTagStrings[i]))
|
||||
{
|
||||
eTag = (memtag_t) i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Com_Printf("Usage: 'zone_tagdebug [#snap] <tag>', e.g. TAG_GHOUL2, TAG_ALL (careful!)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Printf("Dumping debug data for tag \"%s\"...%s\n\n",psTagStrings[eTag], bSnapShotTestActive?"( since snapshot only )":"");
|
||||
|
||||
Com_Printf("%8s"," "); // to compensate for code further down: Com_Printf("(%5d) ",iBlocksListed);
|
||||
if (eTag == TAG_ALL)
|
||||
{
|
||||
Com_Printf("%20s ","Zone Tag");
|
||||
}
|
||||
Com_Printf("%9s\n","Bytes");
|
||||
Com_Printf("%8s"," ");
|
||||
if (eTag == TAG_ALL)
|
||||
{
|
||||
Com_Printf("%20s ","--------");
|
||||
}
|
||||
Com_Printf("%9s\n","-----");
|
||||
|
||||
|
||||
if (bSnapShotTestActive)
|
||||
{
|
||||
// dec ref counts in last snapshot for all current blocks (which will make new stuff go negative)
|
||||
//
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
if (pMemory->eTag == eTag || eTag == TAG_ALL)
|
||||
{
|
||||
AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]--;
|
||||
}
|
||||
pMemory = pMemory->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
// now dump them out...
|
||||
//
|
||||
int iBlocksListed = 0;
|
||||
int iTotalSize = 0;
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
if ( (pMemory->eTag == eTag || eTag == TAG_ALL)
|
||||
&& (!bSnapShotTestActive || (pMemory->iSnapshotNumber == giZoneSnaphotNum && AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel] <0) )
|
||||
)
|
||||
{
|
||||
float fSize = (float)(pMemory->iSize) / 1024.0f / 1024.0f;
|
||||
int iSize = fSize;
|
||||
int iRemainder = 100.0f * (fSize - floor(fSize));
|
||||
|
||||
Com_Printf("(%5d) ",iBlocksListed);
|
||||
|
||||
if (eTag == TAG_ALL)
|
||||
{
|
||||
Com_Printf("%20s",psTagStrings[pMemory->eTag]);
|
||||
}
|
||||
|
||||
Com_Printf(" %9d (%2d.%02dMB) File: \"%s\", Line: %d\n",
|
||||
pMemory->iSize,
|
||||
iSize,iRemainder,
|
||||
pMemory->sSrcFileBaseName,
|
||||
pMemory->iSrcFileLineNum
|
||||
);
|
||||
if (pMemory->sOptionalLabel[0])
|
||||
{
|
||||
Com_Printf("( Label: \"%s\" )\n",pMemory->sOptionalLabel);
|
||||
}
|
||||
iBlocksListed++;
|
||||
iTotalSize += pMemory->iSize;
|
||||
|
||||
if (bSnapShotTestActive)
|
||||
{
|
||||
// bump ref count so we only 1 warning per new string, not for every one sharing that label...
|
||||
//
|
||||
AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++;
|
||||
}
|
||||
}
|
||||
pMemory = pMemory->pNext;
|
||||
}
|
||||
|
||||
Com_Printf("( %d blocks listed, %d bytes (%.2fMB) total )\n",iBlocksListed, iTotalSize, (float)iTotalSize / 1024.0f / 1024.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shuts down the zone memory system and frees up all memory
|
||||
void Com_ShutdownZoneMemory(void)
|
||||
{
|
||||
Cmd_RemoveCommand("zone_stats");
|
||||
Cmd_RemoveCommand("zone_details");
|
||||
|
||||
#ifdef _DEBUG
|
||||
Cmd_RemoveCommand("zone_memrecovertest");
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
Cmd_RemoveCommand("zone_tagdebug");
|
||||
Cmd_RemoveCommand("zone_snapshot");
|
||||
#endif
|
||||
|
||||
if(TheZone.Stats.iCount)
|
||||
{
|
||||
//Com_Printf("Automatically freeing %d blocks making up %d bytes\n", TheZone.Stats.iCount, TheZone.Stats.iCurrent);
|
||||
Z_TagFree(TAG_ALL);
|
||||
|
||||
assert(!TheZone.Stats.iCount);
|
||||
assert(!TheZone.Stats.iCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialises the zone memory system
|
||||
|
||||
void Com_InitZoneMemory( void )
|
||||
{
|
||||
Com_Printf("Initialising zone memory .....\n");
|
||||
|
||||
memset(&TheZone, 0, sizeof(TheZone));
|
||||
TheZone.Header.iMagic = ZONE_MAGIC;
|
||||
|
||||
com_validateZone = Cvar_Get("com_validateZone", "0", 0);
|
||||
|
||||
Cmd_AddCommand("zone_stats", Z_Stats_f);
|
||||
Cmd_AddCommand("zone_details", Z_Details_f);
|
||||
|
||||
#ifdef _DEBUG
|
||||
Cmd_AddCommand("zone_memrecovertest", Z_MemRecoverTest_f);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef DEBUG_ZONE_ALLOCS
|
||||
Cmd_AddCommand("zone_tagdebug", Z_TagDebug_f);
|
||||
Cmd_AddCommand("zone_snapshot", Z_Snapshot_f);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
========================
|
||||
CopyString
|
||||
|
||||
NOTE: never write over the memory CopyString returns because
|
||||
memory from a memstatic_t might be returned
|
||||
========================
|
||||
*/
|
||||
char *CopyString( const char *in ) {
|
||||
char *out;
|
||||
|
||||
if (!in[0]) {
|
||||
return ((char *)&gEmptyString) + sizeof(zoneHeader_t);
|
||||
}
|
||||
else if (!in[1]) {
|
||||
if (in[0] >= '0' && in[0] <= '9') {
|
||||
return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t);
|
||||
}
|
||||
}
|
||||
|
||||
out = (char *) S_Malloc (strlen(in)+1);
|
||||
strcpy (out, in);
|
||||
|
||||
Z_Label(out,in);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Com_TouchMemory
|
||||
|
||||
Touch all known used data to make sure it is paged in
|
||||
===============
|
||||
*/
|
||||
void Com_TouchMemory( void ) {
|
||||
int start, end;
|
||||
int i, j;
|
||||
int sum;
|
||||
int totalTouched;
|
||||
|
||||
Z_Validate();
|
||||
|
||||
start = Sys_Milliseconds();
|
||||
|
||||
sum = 0;
|
||||
totalTouched=0;
|
||||
|
||||
zoneHeader_t *pMemory = TheZone.Header.pNext;
|
||||
while (pMemory)
|
||||
{
|
||||
byte *pMem = (byte *) &pMemory[1];
|
||||
j = pMemory->iSize >> 2;
|
||||
for (i=0; i<j; i+=64){
|
||||
sum += ((int*)pMem)[i];
|
||||
}
|
||||
totalTouched+=pMemory->iSize;
|
||||
pMemory = pMemory->pNext;
|
||||
}
|
||||
|
||||
end = Sys_Milliseconds();
|
||||
|
||||
//Com_Printf( "Com_TouchMemory: %i bytes, %i msec\n", totalTouched, end - start );
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user