mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-02-10 17:51:30 +00:00
1408 lines
22 KiB
C
1408 lines
22 KiB
C
/*
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* Support functions, linked into client, server, renderer and game.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "../header/shared.h"
|
|
|
|
#define DEG2RAD(a) (a * M_PI) / 180.0F
|
|
|
|
vec3_t vec3_origin = {0, 0, 0};
|
|
|
|
/* ============================================================================ */
|
|
|
|
void
|
|
RotatePointAroundVector(vec3_t dst, const vec3_t dir,
|
|
const vec3_t point, float degrees)
|
|
{
|
|
float m[3][3];
|
|
float im[3][3];
|
|
float zrot[3][3];
|
|
float tmpmat[3][3];
|
|
float rot[3][3];
|
|
int i;
|
|
vec3_t vr, vup, vf;
|
|
|
|
vf[0] = dir[0];
|
|
vf[1] = dir[1];
|
|
vf[2] = dir[2];
|
|
|
|
PerpendicularVector(vr, dir);
|
|
CrossProduct(vr, vf, vup);
|
|
|
|
m[0][0] = vr[0];
|
|
m[1][0] = vr[1];
|
|
m[2][0] = vr[2];
|
|
|
|
m[0][1] = vup[0];
|
|
m[1][1] = vup[1];
|
|
m[2][1] = vup[2];
|
|
|
|
m[0][2] = vf[0];
|
|
m[1][2] = vf[1];
|
|
m[2][2] = vf[2];
|
|
|
|
memcpy(im, m, sizeof(im));
|
|
|
|
im[0][1] = m[1][0];
|
|
im[0][2] = m[2][0];
|
|
im[1][0] = m[0][1];
|
|
im[1][2] = m[2][1];
|
|
im[2][0] = m[0][2];
|
|
im[2][1] = m[1][2];
|
|
|
|
memset(zrot, 0, sizeof(zrot));
|
|
zrot[2][2] = 1.0F;
|
|
|
|
zrot[0][0] = (float)cos(DEG2RAD(degrees));
|
|
zrot[0][1] = (float)sin(DEG2RAD(degrees));
|
|
zrot[1][0] = (float)-sin(DEG2RAD(degrees));
|
|
zrot[1][1] = (float)cos(DEG2RAD(degrees));
|
|
|
|
R_ConcatRotations(m, zrot, tmpmat);
|
|
R_ConcatRotations(tmpmat, im, rot);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] *
|
|
point[2];
|
|
}
|
|
}
|
|
|
|
void
|
|
AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
|
|
{
|
|
float angle;
|
|
static float sr, sp, sy, cr, cp, cy;
|
|
|
|
angle = angles[YAW] * (M_PI * 2 / 360);
|
|
sy = (float)sin(angle);
|
|
cy = (float)cos(angle);
|
|
angle = angles[PITCH] * (M_PI * 2 / 360);
|
|
sp = (float)sin(angle);
|
|
cp = (float)cos(angle);
|
|
angle = angles[ROLL] * (M_PI * 2 / 360);
|
|
sr = (float)sin(angle);
|
|
cr = (float)cos(angle);
|
|
|
|
if (forward)
|
|
{
|
|
forward[0] = cp * cy;
|
|
forward[1] = cp * sy;
|
|
forward[2] = -sp;
|
|
}
|
|
|
|
if (right)
|
|
{
|
|
right[0] = (-1 * sr * sp * cy + - 1 * cr * -sy);
|
|
right[1] = (-1 * sr * sp * sy + - 1 * cr * cy);
|
|
right[2] = -1 * sr * cp;
|
|
}
|
|
|
|
if (up)
|
|
{
|
|
up[0] = (cr * sp * cy + - sr * -sy);
|
|
up[1] = (cr * sp * sy + - sr * cy);
|
|
up[2] = cr * cp;
|
|
}
|
|
}
|
|
|
|
void
|
|
AngleVectors2(vec3_t value1, vec3_t angles)
|
|
{
|
|
float forward;
|
|
float yaw, pitch;
|
|
|
|
if ((value1[1] == 0) && (value1[0] == 0))
|
|
{
|
|
yaw = 0;
|
|
|
|
if (value1[2] > 0)
|
|
{
|
|
pitch = 90;
|
|
}
|
|
|
|
else
|
|
{
|
|
pitch = 270;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (value1[0])
|
|
{
|
|
yaw = ((float)atan2(value1[1], value1[0]) * 180 / M_PI);
|
|
}
|
|
|
|
else if (value1[1] > 0)
|
|
{
|
|
yaw = 90;
|
|
}
|
|
|
|
else
|
|
{
|
|
yaw = 270;
|
|
}
|
|
|
|
if (yaw < 0)
|
|
{
|
|
yaw += 360;
|
|
}
|
|
|
|
forward = (float)sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
|
|
pitch = ((float)atan2(value1[2], forward) * 180 / M_PI);
|
|
|
|
if (pitch < 0)
|
|
{
|
|
pitch += 360;
|
|
}
|
|
}
|
|
|
|
angles[PITCH] = -pitch;
|
|
angles[YAW] = yaw;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
void
|
|
ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal)
|
|
{
|
|
float d;
|
|
vec3_t n;
|
|
float inv_denom;
|
|
|
|
inv_denom = 1.0F / DotProduct(normal, normal);
|
|
|
|
d = DotProduct(normal, p) * inv_denom;
|
|
|
|
n[0] = normal[0] * inv_denom;
|
|
n[1] = normal[1] * inv_denom;
|
|
n[2] = normal[2] * inv_denom;
|
|
|
|
dst[0] = p[0] - d * n[0];
|
|
dst[1] = p[1] - d * n[1];
|
|
dst[2] = p[2] - d * n[2];
|
|
}
|
|
|
|
/* assumes "src" is normalized */
|
|
void
|
|
PerpendicularVector(vec3_t dst, const vec3_t src)
|
|
{
|
|
int pos;
|
|
int i;
|
|
float minelem = 1.0F;
|
|
vec3_t tempvec;
|
|
|
|
/* find the smallest magnitude axially aligned vector */
|
|
for (pos = 0, i = 0; i < 3; i++)
|
|
{
|
|
if (fabs(src[i]) < minelem)
|
|
{
|
|
pos = i;
|
|
minelem = (float)fabs(src[i]);
|
|
}
|
|
}
|
|
|
|
tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
|
|
tempvec[pos] = 1.0F;
|
|
|
|
/* project the point onto the plane defined by src */
|
|
ProjectPointOnPlane(dst, tempvec, src);
|
|
|
|
/* normalize the result */
|
|
VectorNormalize(dst);
|
|
}
|
|
|
|
void
|
|
R_ConcatRotations(float in1[3][3], float in2[3][3], float out[3][3])
|
|
{
|
|
out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
|
|
in1[0][2] * in2[2][0];
|
|
out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
|
|
in1[0][2] * in2[2][1];
|
|
out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
|
|
in1[0][2] * in2[2][2];
|
|
out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
|
|
in1[1][2] * in2[2][0];
|
|
out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
|
|
in1[1][2] * in2[2][1];
|
|
out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
|
|
in1[1][2] * in2[2][2];
|
|
out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
|
|
in1[2][2] * in2[2][0];
|
|
out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
|
|
in1[2][2] * in2[2][1];
|
|
out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
|
|
in1[2][2] * in2[2][2];
|
|
}
|
|
|
|
void
|
|
R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4])
|
|
{
|
|
out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
|
|
in1[0][2] * in2[2][0];
|
|
out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
|
|
in1[0][2] * in2[2][1];
|
|
out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
|
|
in1[0][2] * in2[2][2];
|
|
out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
|
|
in1[0][2] * in2[2][3] + in1[0][3];
|
|
out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
|
|
in1[1][2] * in2[2][0];
|
|
out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
|
|
in1[1][2] * in2[2][1];
|
|
out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
|
|
in1[1][2] * in2[2][2];
|
|
out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
|
|
in1[1][2] * in2[2][3] + in1[1][3];
|
|
out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
|
|
in1[2][2] * in2[2][0];
|
|
out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
|
|
in1[2][2] * in2[2][1];
|
|
out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
|
|
in1[2][2] * in2[2][2];
|
|
out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
|
|
in1[2][2] * in2[2][3] + in1[2][3];
|
|
}
|
|
|
|
/* ============================================================================ */
|
|
|
|
float
|
|
Q_fabs(float f)
|
|
{
|
|
int tmp = *(int *)&f;
|
|
|
|
tmp &= 0x7FFFFFFF;
|
|
return *(float *)&tmp;
|
|
}
|
|
|
|
float
|
|
LerpAngle(float a2, float a1, float frac)
|
|
{
|
|
if (a1 - a2 > 180)
|
|
{
|
|
a1 -= 360;
|
|
}
|
|
|
|
if (a1 - a2 < -180)
|
|
{
|
|
a1 += 360;
|
|
}
|
|
|
|
return a2 + frac * (a1 - a2);
|
|
}
|
|
|
|
float
|
|
anglemod(float a)
|
|
{
|
|
a = (360.0 / 65536) * ((int)(a * (65536 / 360.0)) & 65535);
|
|
return a;
|
|
}
|
|
|
|
/*
|
|
* This is the slow, general version
|
|
*/
|
|
int
|
|
BoxOnPlaneSide2(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
|
|
{
|
|
int i;
|
|
float dist1, dist2;
|
|
int sides;
|
|
vec3_t corners[2];
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (p->normal[i] < 0)
|
|
{
|
|
corners[0][i] = emins[i];
|
|
corners[1][i] = emaxs[i];
|
|
}
|
|
else
|
|
{
|
|
corners[1][i] = emins[i];
|
|
corners[0][i] = emaxs[i];
|
|
}
|
|
}
|
|
|
|
dist1 = DotProduct(p->normal, corners[0]) - p->dist;
|
|
dist2 = DotProduct(p->normal, corners[1]) - p->dist;
|
|
sides = 0;
|
|
|
|
if (dist1 >= 0)
|
|
{
|
|
sides = 1;
|
|
}
|
|
|
|
if (dist2 < 0)
|
|
{
|
|
sides |= 2;
|
|
}
|
|
|
|
return sides;
|
|
}
|
|
|
|
/*
|
|
* Returns 1, 2, or 1 + 2
|
|
*/
|
|
int
|
|
BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
|
|
{
|
|
float dist1, dist2;
|
|
int sides;
|
|
|
|
/* fast axial cases */
|
|
if (p->type < 3)
|
|
{
|
|
if (p->dist <= emins[p->type])
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (p->dist >= emaxs[p->type])
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
return 3;
|
|
}
|
|
|
|
/* general case */
|
|
switch (p->signbits)
|
|
{
|
|
case 0:
|
|
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emaxs[2];
|
|
dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emins[2];
|
|
break;
|
|
case 1:
|
|
dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emaxs[2];
|
|
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emins[2];
|
|
break;
|
|
case 2:
|
|
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emaxs[2];
|
|
dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emins[2];
|
|
break;
|
|
case 3:
|
|
dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emaxs[2];
|
|
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emins[2];
|
|
break;
|
|
case 4:
|
|
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emins[2];
|
|
dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emaxs[2];
|
|
break;
|
|
case 5:
|
|
dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emins[2];
|
|
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emaxs[2];
|
|
break;
|
|
case 6:
|
|
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emins[2];
|
|
dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emaxs[2];
|
|
break;
|
|
case 7:
|
|
dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
|
|
p->normal[2] * emins[2];
|
|
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
|
|
p->normal[2] * emaxs[2];
|
|
break;
|
|
default:
|
|
dist1 = dist2 = 0;
|
|
break;
|
|
}
|
|
|
|
sides = 0;
|
|
|
|
if (dist1 >= p->dist)
|
|
{
|
|
sides = 1;
|
|
}
|
|
|
|
if (dist2 < p->dist)
|
|
{
|
|
sides |= 2;
|
|
}
|
|
|
|
return sides;
|
|
}
|
|
|
|
void
|
|
ClearBounds(vec3_t mins, vec3_t maxs)
|
|
{
|
|
mins[0] = mins[1] = mins[2] = 99999;
|
|
maxs[0] = maxs[1] = maxs[2] = -99999;
|
|
}
|
|
|
|
void
|
|
AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs)
|
|
{
|
|
int i;
|
|
vec_t val;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
val = v[i];
|
|
|
|
if (val < mins[i])
|
|
{
|
|
mins[i] = val;
|
|
}
|
|
|
|
if (val > maxs[i])
|
|
{
|
|
maxs[i] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
VectorCompare(vec3_t v1, vec3_t v2)
|
|
{
|
|
if ((v1[0] != v2[0]) || (v1[1] != v2[1]) || (v1[2] != v2[2]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
vec_t
|
|
VectorNormalize(vec3_t v)
|
|
{
|
|
float length, ilength;
|
|
|
|
length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
|
|
length = (float)sqrt(length);
|
|
|
|
if (length)
|
|
{
|
|
ilength = 1 / length;
|
|
v[0] *= ilength;
|
|
v[1] *= ilength;
|
|
v[2] *= ilength;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
vec_t
|
|
VectorNormalize2(vec3_t v, vec3_t out)
|
|
{
|
|
float length, ilength;
|
|
|
|
length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
|
|
length = (float)sqrt(length);
|
|
|
|
if (length)
|
|
{
|
|
ilength = 1 / length;
|
|
out[0] = v[0] * ilength;
|
|
out[1] = v[1] * ilength;
|
|
out[2] = v[2] * ilength;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void
|
|
VectorMA(vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
|
|
{
|
|
vecc[0] = veca[0] + scale * vecb[0];
|
|
vecc[1] = veca[1] + scale * vecb[1];
|
|
vecc[2] = veca[2] + scale * vecb[2];
|
|
}
|
|
|
|
vec_t
|
|
_DotProduct(vec3_t v1, vec3_t v2)
|
|
{
|
|
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
|
|
}
|
|
|
|
void
|
|
_VectorSubtract(vec3_t veca, vec3_t vecb, vec3_t out)
|
|
{
|
|
out[0] = veca[0] - vecb[0];
|
|
out[1] = veca[1] - vecb[1];
|
|
out[2] = veca[2] - vecb[2];
|
|
}
|
|
|
|
void
|
|
_VectorAdd(vec3_t veca, vec3_t vecb, vec3_t out)
|
|
{
|
|
out[0] = veca[0] + vecb[0];
|
|
out[1] = veca[1] + vecb[1];
|
|
out[2] = veca[2] + vecb[2];
|
|
}
|
|
|
|
void
|
|
_VectorCopy(vec3_t in, vec3_t out)
|
|
{
|
|
out[0] = in[0];
|
|
out[1] = in[1];
|
|
out[2] = in[2];
|
|
}
|
|
|
|
void
|
|
CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross)
|
|
{
|
|
cross[0] = v1[1] * v2[2] - v1[2] * v2[1];
|
|
cross[1] = v1[2] * v2[0] - v1[0] * v2[2];
|
|
cross[2] = v1[0] * v2[1] - v1[1] * v2[0];
|
|
}
|
|
|
|
double sqrt(double x);
|
|
|
|
vec_t
|
|
VectorLength(vec3_t v)
|
|
{
|
|
int i;
|
|
float length;
|
|
|
|
length = 0;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
length += v[i] * v[i];
|
|
}
|
|
|
|
length = (float)sqrt(length);
|
|
|
|
return length;
|
|
}
|
|
|
|
void
|
|
VectorInverse(vec3_t v)
|
|
{
|
|
v[0] = -v[0];
|
|
v[1] = -v[1];
|
|
v[2] = -v[2];
|
|
}
|
|
|
|
void
|
|
VectorScale(vec3_t in, vec_t scale, vec3_t out)
|
|
{
|
|
out[0] = in[0] * scale;
|
|
out[1] = in[1] * scale;
|
|
out[2] = in[2] * scale;
|
|
}
|
|
|
|
int
|
|
Q_log2(int val)
|
|
{
|
|
int answer = 0;
|
|
|
|
while (val >>= 1)
|
|
{
|
|
answer++;
|
|
}
|
|
|
|
return answer;
|
|
}
|
|
|
|
/* ==================================================================================== */
|
|
|
|
char *
|
|
COM_SkipPath(char *pathname)
|
|
{
|
|
char *last;
|
|
|
|
last = pathname;
|
|
|
|
while (*pathname)
|
|
{
|
|
if (*pathname == '/')
|
|
{
|
|
last = pathname + 1;
|
|
}
|
|
|
|
pathname++;
|
|
}
|
|
|
|
return last;
|
|
}
|
|
|
|
void
|
|
COM_StripExtension(char *in, char *out)
|
|
{
|
|
while (*in && *in != '.')
|
|
{
|
|
*out++ = *in++;
|
|
}
|
|
|
|
*out = 0;
|
|
}
|
|
|
|
const char *
|
|
COM_FileExtension(const char *in)
|
|
{
|
|
const char *ext = strrchr(in, '.');
|
|
|
|
if (!ext || ext == in)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return ext + 1;
|
|
}
|
|
|
|
void
|
|
COM_FileBase(char *in, char *out)
|
|
{
|
|
char *s, *s2;
|
|
|
|
s = in + strlen(in) - 1;
|
|
|
|
while (s != in && *s != '.')
|
|
{
|
|
s--;
|
|
}
|
|
|
|
for (s2 = s; s2 != in && *s2 != '/'; s2--)
|
|
{
|
|
}
|
|
|
|
if (s - s2 < 2)
|
|
{
|
|
out[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
s--;
|
|
memcpy(out, s2 + 1, s - s2);
|
|
out[s - s2] = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the path up to, but not including the last /
|
|
*/
|
|
void
|
|
COM_FilePath(const char *in, char *out)
|
|
{
|
|
const char *s;
|
|
|
|
s = in + strlen(in) - 1;
|
|
|
|
while (s != in && *s != '/')
|
|
{
|
|
s--;
|
|
}
|
|
|
|
memcpy(out, in, s - in);
|
|
out[s - in] = 0;
|
|
}
|
|
|
|
void
|
|
COM_DefaultExtension(char *path, const char *extension)
|
|
{
|
|
char *src;
|
|
|
|
/* */
|
|
/* if path doesn't have a .EXT, append extension */
|
|
/* (extension should include the .) */
|
|
/* */
|
|
src = path + strlen(path) - 1;
|
|
|
|
while (*src != '/' && src != path)
|
|
{
|
|
if (*src == '.')
|
|
{
|
|
return; /* it has an extension */
|
|
}
|
|
|
|
src--;
|
|
}
|
|
|
|
strcat(path, extension);
|
|
}
|
|
|
|
/*
|
|
* ============================================================================
|
|
*
|
|
* BYTE ORDER FUNCTIONS
|
|
*
|
|
* ============================================================================
|
|
*/
|
|
|
|
qboolean bigendien;
|
|
|
|
/* can't just use function pointers, or dll linkage can
|
|
mess up when qcommon is included in multiple places */
|
|
short (*_BigShort)(short l);
|
|
short (*_LittleShort)(short l);
|
|
int (*_BigLong)(int l);
|
|
int (*_LittleLong)(int l);
|
|
float (*_BigFloat)(float l);
|
|
float (*_LittleFloat)(float l);
|
|
|
|
short
|
|
BigShort(short l)
|
|
{
|
|
return _BigShort(l);
|
|
}
|
|
|
|
short
|
|
LittleShort(short l)
|
|
{
|
|
return _LittleShort(l);
|
|
}
|
|
|
|
int
|
|
BigLong(int l)
|
|
{
|
|
return _BigLong(l);
|
|
}
|
|
|
|
int
|
|
LittleLong(int l)
|
|
{
|
|
return _LittleLong(l);
|
|
}
|
|
|
|
float
|
|
BigFloat(float l)
|
|
{
|
|
return _BigFloat(l);
|
|
}
|
|
|
|
float
|
|
LittleFloat(float l)
|
|
{
|
|
return _LittleFloat(l);
|
|
}
|
|
|
|
short
|
|
ShortSwap(short l)
|
|
{
|
|
byte b1, b2;
|
|
|
|
b1 = l & 255;
|
|
b2 = (l >> 8) & 255;
|
|
|
|
return (b1 << 8) + b2;
|
|
}
|
|
|
|
short
|
|
ShortNoSwap(short l)
|
|
{
|
|
return l;
|
|
}
|
|
|
|
int
|
|
LongSwap(int l)
|
|
{
|
|
byte b1, b2, b3, b4;
|
|
|
|
b1 = l & 255;
|
|
b2 = (l >> 8) & 255;
|
|
b3 = (l >> 16) & 255;
|
|
b4 = (l >> 24) & 255;
|
|
|
|
return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
|
|
}
|
|
|
|
int
|
|
LongNoSwap(int l)
|
|
{
|
|
return l;
|
|
}
|
|
|
|
float
|
|
FloatSwap(float f)
|
|
{
|
|
union
|
|
{
|
|
float f;
|
|
byte b[4];
|
|
} dat1, dat2;
|
|
|
|
dat1.f = f;
|
|
dat2.b[0] = dat1.b[3];
|
|
dat2.b[1] = dat1.b[2];
|
|
dat2.b[2] = dat1.b[1];
|
|
dat2.b[3] = dat1.b[0];
|
|
return dat2.f;
|
|
}
|
|
|
|
float
|
|
FloatNoSwap(float f)
|
|
{
|
|
return f;
|
|
}
|
|
|
|
void
|
|
Swap_Init(void)
|
|
{
|
|
byte swaptest[2] = {1, 0};
|
|
short swapTestShort;
|
|
assert(sizeof(short) == 2);
|
|
memcpy(&swapTestShort, swaptest, 2);
|
|
|
|
/* set the byte swapping variables in a portable manner */
|
|
if (swapTestShort == 1)
|
|
{
|
|
bigendien = false;
|
|
_BigShort = ShortSwap;
|
|
_LittleShort = ShortNoSwap;
|
|
_BigLong = LongSwap;
|
|
_LittleLong = LongNoSwap;
|
|
_BigFloat = FloatSwap;
|
|
_LittleFloat = FloatNoSwap;
|
|
Com_Printf("Byte ordering: little endian\n\n");
|
|
}
|
|
else
|
|
{
|
|
bigendien = true;
|
|
_BigShort = ShortNoSwap;
|
|
_LittleShort = ShortSwap;
|
|
_BigLong = LongNoSwap;
|
|
_LittleLong = LongSwap;
|
|
_BigFloat = FloatNoSwap;
|
|
_LittleFloat = FloatSwap;
|
|
Com_Printf("Byte ordering: big endian\n\n");
|
|
}
|
|
|
|
if (LittleShort(swapTestShort) != 1)
|
|
assert("Error in the endian conversion!");
|
|
}
|
|
|
|
/*
|
|
* does a varargs printf into a temp buffer, so I don't
|
|
* need to have varargs versions of all text functions.
|
|
*/
|
|
char *
|
|
va(char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
static char string[1024];
|
|
|
|
va_start(argptr, format);
|
|
vsnprintf(string, 1024, format, argptr);
|
|
va_end(argptr);
|
|
|
|
return string;
|
|
}
|
|
|
|
char com_token[MAX_TOKEN_CHARS];
|
|
|
|
/*
|
|
* Parse a token out of a string
|
|
*/
|
|
char *
|
|
COM_Parse(char **data_p)
|
|
{
|
|
int c;
|
|
int len;
|
|
char *data;
|
|
|
|
data = *data_p;
|
|
len = 0;
|
|
com_token[0] = 0;
|
|
|
|
if (!data)
|
|
{
|
|
*data_p = NULL;
|
|
return "";
|
|
}
|
|
|
|
skipwhite:
|
|
|
|
while ((c = *data) <= ' ')
|
|
{
|
|
if (c == 0)
|
|
{
|
|
*data_p = NULL;
|
|
return "";
|
|
}
|
|
|
|
data++;
|
|
}
|
|
|
|
/* skip // comments */
|
|
if ((c == '/') && (data[1] == '/'))
|
|
{
|
|
while (*data && *data != '\n')
|
|
{
|
|
data++;
|
|
}
|
|
|
|
goto skipwhite;
|
|
}
|
|
|
|
/* handle quoted strings specially */
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
|
|
while (1)
|
|
{
|
|
c = *data++;
|
|
|
|
if ((c == '\"') || !c)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
if (len < MAX_TOKEN_CHARS)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* parse a regular word */
|
|
do
|
|
{
|
|
if (len < MAX_TOKEN_CHARS)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
|
|
data++;
|
|
c = *data;
|
|
}
|
|
while (c > 32);
|
|
|
|
done:
|
|
if (len == MAX_TOKEN_CHARS)
|
|
{
|
|
len = 0;
|
|
}
|
|
|
|
com_token[len] = 0;
|
|
|
|
*data_p = data;
|
|
return com_token;
|
|
}
|
|
|
|
static int paged_total = 0;
|
|
|
|
void
|
|
Com_PageInMemory(byte *buffer, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = size - 1; i > 0; i -= 4096)
|
|
{
|
|
paged_total += buffer[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ============================================================================
|
|
*
|
|
* LIBRARY REPLACEMENT FUNCTIONS
|
|
*
|
|
* ============================================================================
|
|
*/
|
|
|
|
int
|
|
Q_stricmp(const char *s1, const char *s2)
|
|
{
|
|
return strcasecmp(s1, s2);
|
|
}
|
|
|
|
int
|
|
Q_strncasecmp(char *s1, char *s2, int n)
|
|
{
|
|
int c1, c2;
|
|
|
|
do
|
|
{
|
|
c1 = *s1++;
|
|
c2 = *s2++;
|
|
|
|
if (!n--)
|
|
{
|
|
return 0; /* strings are equal until end point */
|
|
}
|
|
|
|
if (c1 != c2)
|
|
{
|
|
if ((c1 >= 'a') && (c1 <= 'z'))
|
|
{
|
|
c1 -= ('a' - 'A');
|
|
}
|
|
|
|
if ((c2 >= 'a') && (c2 <= 'z'))
|
|
{
|
|
c2 -= ('a' - 'A');
|
|
}
|
|
|
|
if (c1 != c2)
|
|
{
|
|
return -1; /* strings not equal */
|
|
}
|
|
}
|
|
}
|
|
while (c1);
|
|
|
|
return 0; /* strings are equal */
|
|
}
|
|
|
|
int
|
|
Q_strcasecmp(char *s1, char *s2)
|
|
{
|
|
return Q_strncasecmp(s1, s2, 99999);
|
|
}
|
|
|
|
void
|
|
Com_sprintf(char *dest, int size, char *fmt, ...)
|
|
{
|
|
int len;
|
|
va_list argptr;
|
|
|
|
va_start(argptr, fmt);
|
|
len = vsnprintf(dest, size, fmt, argptr);
|
|
va_end(argptr);
|
|
|
|
if (len >= size)
|
|
{
|
|
Com_Printf("Com_sprintf: overflow\n");
|
|
}
|
|
}
|
|
|
|
char *
|
|
Q_strlwr ( char *s )
|
|
{
|
|
char *p = s;
|
|
|
|
while ( *s )
|
|
{
|
|
*s = tolower( *s );
|
|
s++;
|
|
}
|
|
|
|
return ( p );
|
|
}
|
|
|
|
int
|
|
Q_strlcpy(char *dst, const char *src, int size)
|
|
{
|
|
const char *s = src;
|
|
|
|
while (*s)
|
|
{
|
|
if (size > 1)
|
|
{
|
|
*dst++ = *s;
|
|
size--;
|
|
}
|
|
s++;
|
|
}
|
|
if (size > 0)
|
|
{
|
|
*dst = '\0';
|
|
}
|
|
|
|
return s - src;
|
|
}
|
|
|
|
int
|
|
Q_strlcat(char *dst, const char *src, int size)
|
|
{
|
|
char *d = dst;
|
|
|
|
while (size > 0 && *d)
|
|
{
|
|
size--;
|
|
d++;
|
|
}
|
|
|
|
return (d - dst) + Q_strlcpy(d, src, size);
|
|
}
|
|
|
|
/*
|
|
* An unicode compatible fopen() Wrapper for Windows.
|
|
*/
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
FILE *Q_fopen(const char *file, const char *mode)
|
|
{
|
|
WCHAR wfile[MAX_OSPATH];
|
|
WCHAR wmode[16];
|
|
|
|
int len = MultiByteToWideChar(CP_UTF8, 0, file, -1, wfile, MAX_OSPATH);
|
|
|
|
if (len > 0)
|
|
{
|
|
if (MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, 16) > 0)
|
|
{
|
|
// make sure it's a regular file and not a directory or sth, see #394
|
|
struct _stat buf;
|
|
int statret = _wstat(wfile, &buf);
|
|
if((statret == 0 && (buf.st_mode & _S_IFREG) != 0) || (statret == -1 && errno == ENOENT))
|
|
{
|
|
return _wfopen(wfile, wmode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#else
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
FILE *Q_fopen(const char *file, const char *mode)
|
|
{
|
|
// make sure it's a regular file and not a directory or sth, see #394
|
|
struct stat statbuf;
|
|
int statret = stat(file, &statbuf);
|
|
// (it's ok if it doesn't exist though, maybe we wanna write/create)
|
|
if((statret == -1 && errno != ENOENT) || (statret == 0 && (statbuf.st_mode & S_IFREG) == 0))
|
|
{
|
|
return NULL;
|
|
}
|
|
return fopen(file, mode);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* =====================================================================
|
|
*
|
|
* INFO STRINGS
|
|
*
|
|
* =====================================================================
|
|
*/
|
|
|
|
/*
|
|
* Searches the string for the given
|
|
* key and returns the associated value,
|
|
* or an empty string.
|
|
*/
|
|
char *
|
|
Info_ValueForKey(char *s, char *key)
|
|
{
|
|
char pkey[512];
|
|
static char value[2][512]; /* use two buffers so compares
|
|
work without stomping on each other */
|
|
static int valueindex;
|
|
char *o;
|
|
|
|
valueindex ^= 1;
|
|
|
|
if (*s == '\\')
|
|
{
|
|
s++;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
o = pkey;
|
|
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
*o++ = *s++;
|
|
}
|
|
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value[valueindex];
|
|
|
|
while (*s != '\\' && *s)
|
|
{
|
|
*o++ = *s++;
|
|
}
|
|
|
|
*o = 0;
|
|
|
|
if (!strcmp(key, pkey))
|
|
{
|
|
return value[valueindex];
|
|
}
|
|
|
|
if (!*s)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
s++;
|
|
}
|
|
}
|
|
|
|
void
|
|
Info_RemoveKey(char *s, char *key)
|
|
{
|
|
char *start;
|
|
char pkey[512];
|
|
char value[512];
|
|
char *o;
|
|
|
|
if (strstr(key, "\\"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
start = s;
|
|
|
|
if (*s == '\\')
|
|
{
|
|
s++;
|
|
}
|
|
|
|
o = pkey;
|
|
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
{
|
|
return;
|
|
}
|
|
|
|
*o++ = *s++;
|
|
}
|
|
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value;
|
|
|
|
while (*s != '\\' && *s)
|
|
{
|
|
*o++ = *s++;
|
|
}
|
|
|
|
*o = 0;
|
|
|
|
if (!strcmp(key, pkey))
|
|
{
|
|
memmove(start, s, strlen(s) + 1); /* remove this part */
|
|
return;
|
|
}
|
|
|
|
if (!*s)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Some characters are illegal in info strings
|
|
* because they can mess up the server's parsing
|
|
*/
|
|
qboolean
|
|
Info_Validate(char *s)
|
|
{
|
|
if (strstr(s, "\""))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (strstr(s, ";"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Info_SetValueForKey(char *s, char *key, char *value)
|
|
{
|
|
char newi[MAX_INFO_STRING], *v;
|
|
int c;
|
|
int maxsize = MAX_INFO_STRING;
|
|
|
|
if (strstr(key, "\\") || strstr(value, "\\"))
|
|
{
|
|
Com_Printf("Can't use keys or values with a \\\n");
|
|
return;
|
|
}
|
|
|
|
if (strstr(key, ";"))
|
|
{
|
|
Com_Printf("Can't use keys with a semicolon\n");
|
|
return;
|
|
}
|
|
|
|
if (strstr(key, "\"") || strstr(value, "\""))
|
|
{
|
|
Com_Printf("Can't use keys or values with a \"\n");
|
|
return;
|
|
}
|
|
|
|
if ((strlen(key) > MAX_INFO_KEY - 1) || (strlen(value) > MAX_INFO_KEY - 1))
|
|
{
|
|
Com_Printf("Keys and values must be < 64 characters.\n");
|
|
return;
|
|
}
|
|
|
|
Info_RemoveKey(s, key);
|
|
|
|
if (!value || !strlen(value))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Com_sprintf(newi, sizeof(newi), "\\%s\\%s", key, value);
|
|
|
|
if (strlen(newi) + strlen(s) > maxsize)
|
|
{
|
|
Com_Printf("Info string length exceeded\n");
|
|
return;
|
|
}
|
|
|
|
/* only copy ascii values */
|
|
s += strlen(s);
|
|
v = newi;
|
|
|
|
while (*v)
|
|
{
|
|
c = *v++;
|
|
c &= 127; /* strip high bits */
|
|
|
|
if ((c >= 32) && (c < 127))
|
|
{
|
|
*s++ = c;
|
|
}
|
|
}
|
|
|
|
*s = 0;
|
|
}
|