mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
685 lines
19 KiB
C
685 lines
19 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2023 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code 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.
|
|
|
|
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// cg_ents.c -- present snapshot entities, happens every single frame
|
|
|
|
#include "cg_local.h"
|
|
#include "cg_radar.h"
|
|
|
|
/*
|
|
==========================================================================
|
|
|
|
FUNCTIONS CALLED EACH FRAME
|
|
|
|
==========================================================================
|
|
*/
|
|
|
|
/*
|
|
======================
|
|
CG_SetEntitySoundPosition
|
|
======================
|
|
*/
|
|
void CG_SetEntitySoundPosition(centity_t *cent)
|
|
{
|
|
vec3_t origin;
|
|
|
|
if (cent->currentState.solid == SOLID_BMODEL) {
|
|
float *v;
|
|
vec3_t vel;
|
|
|
|
v = cgs.inlineModelMidpoints[cent->currentState.modelindex];
|
|
VectorAdd(cent->lerpOrigin, v, origin);
|
|
|
|
vel[0] = 0.0;
|
|
vel[1] = 0.0;
|
|
vel[2] = 0.0;
|
|
|
|
cgi.S_UpdateEntity(cent->currentState.number, origin, vel, qfalse);
|
|
} else {
|
|
if (cent && cg.snap && cent->currentState.parent == cg.snap->ps.clientNum) {
|
|
vec3_t origin;
|
|
vec3_t velocity;
|
|
|
|
origin[0] = 0;
|
|
origin[1] = 0;
|
|
origin[2] = 0;
|
|
|
|
velocity[0] = 0;
|
|
velocity[1] = 0;
|
|
velocity[2] = 0;
|
|
|
|
cgi.S_UpdateEntity(cent->currentState.number, origin, velocity, qtrue);
|
|
} else {
|
|
CG_GetOrigin(cent, origin);
|
|
cgi.S_UpdateEntity(cent->currentState.number, origin, cent->currentState.pos.trDelta, qfalse);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_EntityEffects
|
|
|
|
Add continuous entity effects, like local entity emission and lighting
|
|
==================
|
|
*/
|
|
void CG_EntityEffects(centity_t *cent)
|
|
{
|
|
// initialize with the client colors
|
|
cent->color[0] = cent->client_color[0];
|
|
cent->color[1] = cent->client_color[1];
|
|
cent->color[2] = cent->client_color[2];
|
|
cent->color[3] = cent->client_color[3];
|
|
|
|
if (cent->currentState.constantLight != 0xffffff) {
|
|
int style;
|
|
unsigned cl;
|
|
float i, r, g, b;
|
|
|
|
cl = cent->currentState.constantLight;
|
|
style = (cl & 255);
|
|
r = (float)style / 255.0f;
|
|
g = (float)((cl >> 8) & 255) / 255.0f;
|
|
b = (float)((cl >> 16) & 255) / 255.0f;
|
|
i = ((cl >> 24) & 255) * CONSTANTLIGHT_RADIUS_SCALE;
|
|
if (cent->currentState.renderfx & RF_LIGHTSTYLE_DLIGHT) {
|
|
float color[4];
|
|
|
|
CG_LightStyleColor(style, cg.time, color, qfalse);
|
|
r = color[0];
|
|
g = color[1];
|
|
b = color[2];
|
|
i *= color[3];
|
|
}
|
|
if (i) {
|
|
int flags;
|
|
|
|
flags = 0;
|
|
if (cent->currentState.renderfx & RF_LENSFLARE) {
|
|
flags |= lensflare;
|
|
} else if (cent->currentState.renderfx & RF_VIEWLENSFLARE) {
|
|
flags |= viewlensflare;
|
|
}
|
|
if (cent->currentState.renderfx & RF_ADDITIVE_DLIGHT) {
|
|
flags |= additive;
|
|
}
|
|
cgi.R_AddLightToScene(cent->lerpOrigin, i, r, g, b, flags);
|
|
}
|
|
if (r < cent->color[0]) {
|
|
cent->color[0] = r;
|
|
}
|
|
if (g < cent->color[1]) {
|
|
cent->color[1] = g;
|
|
}
|
|
if (b < cent->color[2]) {
|
|
cent->color[2] = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_General
|
|
==================
|
|
*/
|
|
void CG_General(centity_t *cent)
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
int i;
|
|
vec3_t vMins, vMaxs, vTmp;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// add loop sound
|
|
if (s1->loopSound) {
|
|
cgi.S_AddLoopingSound(
|
|
cent->lerpOrigin,
|
|
vec3_origin,
|
|
cgs.sound_precache[s1->loopSound],
|
|
s1->loopSoundVolume,
|
|
s1->loopSoundMinDist,
|
|
s1->loopSoundMaxDist,
|
|
s1->loopSoundPitch,
|
|
s1->loopSoundFlags
|
|
);
|
|
}
|
|
if (cent->tikiLoopSound) {
|
|
cgi.S_AddLoopingSound(
|
|
cent->lerpOrigin,
|
|
vec3_origin,
|
|
cent->tikiLoopSound,
|
|
cent->tikiLoopSoundVolume,
|
|
cent->tikiLoopSoundMinDist,
|
|
cent->tikiLoopSoundMaxDist,
|
|
cent->tikiLoopSoundPitch,
|
|
cent->tikiLoopSoundFlags
|
|
);
|
|
}
|
|
|
|
if (s1->renderfx & RF_SKYORIGIN) {
|
|
AnglesToAxis(cent->lerpAngles, cg.sky_axis);
|
|
VectorCopy(cent->lerpOrigin, cg.sky_origin);
|
|
}
|
|
|
|
// if set to invisible, skip
|
|
if (!s1->modelindex) {
|
|
return;
|
|
}
|
|
|
|
if (s1->renderfx & RF_DONTDRAW) {
|
|
return;
|
|
}
|
|
|
|
memset(&ent, 0, sizeof(ent));
|
|
|
|
// set frame
|
|
|
|
ent.wasframe = s1->wasframe;
|
|
|
|
VectorCopy(cent->lerpOrigin, ent.origin);
|
|
VectorCopy(cent->lerpOrigin, ent.oldorigin);
|
|
|
|
// set skin
|
|
IntegerToBoundingBox(s1->solid, vMins, vMaxs);
|
|
VectorMA(ent.origin, 0.5f, ent.lightingOrigin, ent.lightingOrigin);
|
|
VectorSubtract(vMins, vMaxs, vTmp);
|
|
ent.radius = VectorLength(vTmp) * 0.5;
|
|
|
|
ent.skinNum = s1->skinNum;
|
|
|
|
ent.hModel = cgs.model_draw[s1->modelindex];
|
|
|
|
// set surfaces
|
|
memcpy(ent.surfaces, s1->surfaces, MAX_MODEL_SURFACES);
|
|
|
|
// Modulation based off the color
|
|
for (i = 0; i < 3; i++) {
|
|
ent.shaderRGBA[i] = cent->color[i] * 255;
|
|
}
|
|
|
|
// take the alpha from the entity if less than 1, else grab it from the client commands version
|
|
if (s1->alpha < 1) {
|
|
ent.shaderRGBA[3] = s1->alpha * 255;
|
|
} else {
|
|
ent.shaderRGBA[3] = cent->color[3] * 255;
|
|
}
|
|
|
|
// convert angles to axis
|
|
AnglesToAxis(cent->lerpAngles, ent.axis);
|
|
|
|
// Interpolated state variables
|
|
if (cent->interpolate) {
|
|
ent.scale = s1->scale + cg.frameInterpolation * (cent->nextState.scale - cent->currentState.scale);
|
|
} else {
|
|
ent.scale = s1->scale;
|
|
}
|
|
// set the entity number
|
|
ent.entityNumber = s1->number;
|
|
|
|
// copy shader specific data
|
|
ent.shader_data[0] = s1->tag_num;
|
|
ent.shader_data[1] = s1->skinNum;
|
|
ent.renderfx |= s1->renderfx;
|
|
|
|
ent.tiki = cgi.R_Model_GetHandle(cgs.model_draw[s1->modelindex]);
|
|
ent.frameInfo[0] = s1->frameInfo[0];
|
|
ent.actionWeight = 1.0;
|
|
|
|
// add to refresh list
|
|
cgi.R_AddRefEntityToScene(&ent, ENTITYNUM_NONE);
|
|
|
|
if (ent.tiki) {
|
|
// update any emitter's...
|
|
CG_UpdateEntityEmitters(s1->number, &ent, cent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_Speaker
|
|
|
|
Speaker entities can automatically play sounds
|
|
==================
|
|
*/
|
|
void CG_Speaker(centity_t *cent)
|
|
{
|
|
if (!cent->currentState.clientNum) // FIXME: use something other than clientNum...
|
|
{
|
|
return; // not auto triggering
|
|
}
|
|
|
|
if (cg.time < cent->miscTime) {
|
|
return;
|
|
}
|
|
|
|
// FIXME
|
|
//cgi.S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.sound_precache[cent->currentState.eventParm] );
|
|
|
|
// ent->s.frame = ent->wait * 10;
|
|
// ent->s.clientNum = ent->random * 10;
|
|
cent->miscTime = cg.time + cent->currentState.wasframe * 100 + cent->currentState.clientNum * 100 * crandom();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Mover
|
|
===============
|
|
*/
|
|
void CG_Mover(centity_t *cent)
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// create the render entity
|
|
memset(&ent, 0, sizeof(ent));
|
|
VectorCopy(cent->lerpOrigin, ent.origin);
|
|
VectorCopy(cent->lerpOrigin, ent.oldorigin);
|
|
AnglesToAxis(cent->lerpAngles, ent.axis);
|
|
|
|
ent.renderfx &= ~RF_SHADOW;
|
|
|
|
// flicker between two skins (FIXME?)
|
|
ent.skinNum = (cg.time >> 6) & 1;
|
|
|
|
// get the model, either as a bmodel or a modelindex
|
|
if (s1->solid == SOLID_BMODEL) {
|
|
ent.hModel = cgs.inlineDrawModel[s1->modelindex];
|
|
} else {
|
|
ent.hModel = cgs.model_draw[s1->modelindex];
|
|
}
|
|
|
|
// add to refresh list
|
|
cgi.R_AddRefEntityToScene(&ent, ENTITYNUM_NONE);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Beam
|
|
===============
|
|
*/
|
|
void CG_Beam(centity_t *cent)
|
|
{
|
|
entityState_t *s1;
|
|
vec3_t vz = {0, 0, 0}, origin = {0, 0, 0};
|
|
float modulate[4];
|
|
int i;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
modulate[i] = cent->color[i];
|
|
}
|
|
|
|
if (s1->beam_entnum != ENTITYNUM_NONE) {
|
|
refEntity_t *parent;
|
|
parent = cgi.R_GetRenderEntity(s1->beam_entnum);
|
|
|
|
if (!parent) {
|
|
cgi.DPrintf("CG_Beam: Could not find parent entity\n");
|
|
return;
|
|
}
|
|
|
|
VectorAdd(s1->origin, parent->origin, origin);
|
|
} else {
|
|
VectorCopy(s1->origin, origin);
|
|
}
|
|
|
|
CG_CreateBeam(
|
|
origin, // start
|
|
vz, // dir ( auto calculated by using origin2-origin )
|
|
s1->number, // owner number
|
|
cgs.model_draw[s1->modelindex], //hModel
|
|
s1->alpha, // alpha
|
|
s1->scale, // scale
|
|
s1->skinNum, // flags
|
|
0, // length ( auto calculated )
|
|
PKT_TO_BEAM_PARM(s1->surfaces[0]) * 1000, // life
|
|
qfalse, // don't always create the beam, just update it
|
|
s1->origin2, // endpoint
|
|
s1->bone_angles[0][0], // min offset
|
|
s1->bone_angles[0][1], // max offset
|
|
PKT_TO_BEAM_PARM(s1->surfaces[3]), // overlap
|
|
s1->surfaces[4], // subdivisions
|
|
PKT_TO_BEAM_PARM(s1->surfaces[5]) * 1000, // delay
|
|
CG_ConfigString(CS_IMAGES + s1->tag_num), // index for shader configstring
|
|
modulate, // modulate color
|
|
s1->surfaces[6], // num sphere beams
|
|
PKT_TO_BEAM_PARM(s1->surfaces[7]), // sphere radius
|
|
PKT_TO_BEAM_PARM(s1->surfaces[8]), // toggle delay
|
|
PKT_TO_BEAM_PARM(s1->surfaces[9]), // end alpha
|
|
s1->renderfx,
|
|
""
|
|
);
|
|
}
|
|
|
|
void CG_Decal(centity_t *cent)
|
|
|
|
{
|
|
qhandle_t shader;
|
|
vec3_t dir;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
shader = cgi.R_RegisterShader(CG_ConfigString(CS_IMAGES + s1->tag_num));
|
|
ByteToDir(s1->surfaces[0], dir);
|
|
CG_ImpactMark(
|
|
shader,
|
|
s1->origin,
|
|
dir,
|
|
s1->angles[2],
|
|
s1->scale,
|
|
s1->scale,
|
|
cent->color[0],
|
|
cent->color[1],
|
|
cent->color[2],
|
|
cent->color[3],
|
|
qtrue,
|
|
qfalse,
|
|
qtrue,
|
|
qfalse,
|
|
0.5f,
|
|
0.5f
|
|
);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Portal
|
|
===============
|
|
*/
|
|
void CG_Portal(centity_t *cent) {}
|
|
|
|
/*
|
|
================
|
|
BG_EvaluateTrajectory
|
|
================
|
|
*/
|
|
void BG_EvaluateTrajectory(const trajectory_t *tr, int atTime, const vec3_t base, vec3_t result)
|
|
{
|
|
float deltaTime;
|
|
|
|
if (atTime > cg_smoothClientsTime->integer + tr->trTime) {
|
|
atTime = cg_smoothClientsTime->integer + tr->trTime;
|
|
}
|
|
|
|
deltaTime = (float)(atTime - tr->trTime) / 1000.0;
|
|
|
|
result[0] = tr->trDelta[0] * deltaTime + base[0];
|
|
result[1] = tr->trDelta[1] * deltaTime + base[1];
|
|
result[2] = tr->trDelta[2] * deltaTime + base[2];
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_CalcEntityLerpPositions
|
|
|
|
===============
|
|
*/
|
|
void CG_CalcEntityLerpPositions(centity_t *cent)
|
|
{
|
|
int i;
|
|
float f;
|
|
|
|
f = cg.frameInterpolation;
|
|
|
|
if (cent->currentState.eType == ET_PLAYER) {
|
|
if (cent->currentState.number == cg.snap->ps.clientNum) {
|
|
// if the player, take position from prediction
|
|
VectorCopy(cg.predicted_player_state.origin, cent->lerpOrigin);
|
|
for (i = 0; i < 3; i++) {
|
|
cent->lerpAngles[i] = LerpAngle(cent->currentState.angles[i], cent->nextState.angles[i], f);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cent->currentState.eType != ET_PLAYER || !cg_smoothClients->integer) {
|
|
float quat[4];
|
|
float mat[3][3];
|
|
|
|
if (!cent->interpolate) {
|
|
VectorCopy(cent->currentState.angles, cent->lerpAngles);
|
|
VectorCopy(cent->currentState.origin, cent->lerpOrigin);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
cent->lerpOrigin[i] =
|
|
cent->currentState.origin[i] + f * (cent->nextState.origin[i] - cent->currentState.origin[i]);
|
|
}
|
|
|
|
if (!memcmp(cent->currentState.angles, cent->nextState.angles, sizeof(vec3_t))) {
|
|
VectorCopy(cent->currentState.angles, cent->lerpAngles);
|
|
} else {
|
|
// use spherical interpolation using quaternions so that bound objects
|
|
// rotate properly without gimble lock.
|
|
SlerpQuaternion(cent->currentState.quat, cent->nextState.quat, f, quat);
|
|
QuatToMat(quat, mat);
|
|
MatrixToEulerAngles(mat, cent->lerpAngles);
|
|
}
|
|
} else if (cent->interpolate) {
|
|
float quat[4];
|
|
float mat[3][3];
|
|
|
|
// if the entity has a valid next state, interpolate a value between the frames
|
|
// unless it is a mover with a known start and stop
|
|
vec3_t current, next;
|
|
|
|
// this will linearize a sine or parabolic curve, but it is important
|
|
// to not extrapolate player positions if more recent data is available
|
|
BG_EvaluateTrajectory(¢->currentState.pos, cg.snap->serverTime, cent->currentState.origin, current);
|
|
BG_EvaluateTrajectory(¢->nextState.pos, cg.nextSnap->serverTime, cent->nextState.origin, next);
|
|
|
|
cent->lerpOrigin[0] = current[0] + f * (next[0] - current[0]);
|
|
cent->lerpOrigin[1] = current[1] + f * (next[1] - current[1]);
|
|
cent->lerpOrigin[2] = current[2] + f * (next[2] - current[2]);
|
|
|
|
if (!memcmp(cent->currentState.angles, cent->nextState.angles, sizeof(vec3_t))) {
|
|
VectorCopy(cent->currentState.angles, cent->lerpAngles);
|
|
} else {
|
|
// use spherical interpolation using quaternions so that bound objects
|
|
// rotate properly without gimble lock.
|
|
SlerpQuaternion(cent->currentState.quat, cent->nextState.quat, f, quat);
|
|
QuatToMat(quat, mat);
|
|
MatrixToEulerAngles(mat, cent->lerpAngles);
|
|
}
|
|
} else {
|
|
// just use the current frame and evaluate as best we can
|
|
BG_EvaluateTrajectory(¢->currentState.pos, cg.time, cent->currentState.origin, cent->lerpOrigin);
|
|
VectorCopy(cent->currentState.angles, cent->lerpAngles);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_AddCEntity
|
|
|
|
===============
|
|
*/
|
|
void CG_AddCEntity(centity_t *cent)
|
|
{
|
|
// event-only entities will have been dealt with already
|
|
if (cent->currentState.eType >= ET_EVENTS) {
|
|
return;
|
|
}
|
|
|
|
// calculate the current origin
|
|
CG_CalcEntityLerpPositions(cent);
|
|
|
|
// add automatic effects
|
|
CG_EntityEffects(cent);
|
|
|
|
CG_SetEntitySoundPosition(cent);
|
|
|
|
switch (cent->currentState.eType) {
|
|
default:
|
|
cgi.Error(ERR_DROP, "Bad entity type: %i\n", cent->currentState.eType);
|
|
break;
|
|
// intentional fallthrough
|
|
case ET_MODELANIM_SKEL:
|
|
case ET_MODELANIM:
|
|
CG_Splash(cent);
|
|
CG_ModelAnim(cent, qfalse);
|
|
break;
|
|
case ET_VEHICLE:
|
|
CG_Vehicle(cent);
|
|
CG_Splash(cent);
|
|
CG_ModelAnim(cent, qtrue);
|
|
break;
|
|
case ET_PLAYER:
|
|
CG_Player(cent);
|
|
CG_Splash(cent);
|
|
CG_ModelAnim(cent, qfalse);
|
|
CG_UpdateRadarClient(cent);
|
|
break;
|
|
case ET_ITEM:
|
|
CG_ModelAnim(cent, qfalse);
|
|
break;
|
|
case ET_GENERAL:
|
|
CG_General(cent);
|
|
break;
|
|
case ET_MOVER:
|
|
CG_Mover(cent);
|
|
break;
|
|
case ET_BEAM:
|
|
CG_Beam(cent);
|
|
break;
|
|
case ET_MULTIBEAM: // skip
|
|
break;
|
|
case ET_PORTAL:
|
|
CG_Portal(cent);
|
|
break;
|
|
case ET_RAIN:
|
|
CG_Rain(cent);
|
|
break;
|
|
case ET_DECAL:
|
|
CG_Decal(cent);
|
|
break;
|
|
case ET_EMITTER:
|
|
CG_Emitter(cent);
|
|
break;
|
|
case ET_ROPE: // skip
|
|
CG_Rope(cent);
|
|
break;
|
|
case ET_EXEC_COMMANDS:
|
|
CG_ModelAnim(cent, qfalse);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_AddPacketEntities
|
|
|
|
===============
|
|
*/
|
|
void CG_AddPacketEntities(void)
|
|
{
|
|
int num;
|
|
centity_t *cent;
|
|
int child, parent;
|
|
qboolean processed[MAX_ENTITIES];
|
|
int i;
|
|
|
|
// the auto-rotating items will all have the same axis
|
|
cg.autoAngles[0] = 0;
|
|
cg.autoAngles[1] = (cg.time & 2047) * 360 / 2048.0;
|
|
cg.autoAngles[2] = 0;
|
|
|
|
cg.autoAnglesSlow[0] = 0;
|
|
cg.autoAnglesSlow[1] = (cg.time & 4095) * 360 / 4096.0f;
|
|
cg.autoAnglesSlow[2] = 0;
|
|
|
|
cg.autoAnglesFast[0] = 0;
|
|
cg.autoAnglesFast[1] = (cg.time & 1023) * 360 / 1024.0f;
|
|
cg.autoAnglesFast[2] = 0;
|
|
|
|
AnglesToAxis(cg.autoAngles, cg.autoAxis);
|
|
AnglesToAxis(cg.autoAnglesSlow, cg.autoAxisSlow);
|
|
AnglesToAxis(cg.autoAnglesFast, cg.autoAxisFast);
|
|
|
|
for (i = 0; i < MAX_ENTITIES; i++) {
|
|
processed[i] = qtrue;
|
|
}
|
|
|
|
for (num = 0; num < cg.snap->numEntities; ++num) {
|
|
processed[cg.snap->entities[num].number] = qfalse;
|
|
}
|
|
|
|
// add each entity sent over by the server
|
|
for (num = 0; num < cg.snap->numEntities; num++) {
|
|
child = cg.snap->entities[num].number;
|
|
cent = &cg_entities[child];
|
|
// add the parent first
|
|
// so attachments are consistent
|
|
for (parent = cent->currentState.parent; parent != ENTITYNUM_NONE && !processed[parent];
|
|
parent = cg_entities[parent].currentState.parent) {
|
|
processed[parent] = qtrue;
|
|
CG_AddCEntity(&cg_entities[parent]);
|
|
}
|
|
|
|
if (!processed[child]) {
|
|
// now add the children if not processed
|
|
processed[child] = qtrue;
|
|
CG_AddCEntity(cent);
|
|
}
|
|
}
|
|
|
|
// Add in the multibeams at the end
|
|
for (num = 0; num < cg.snap->numEntities; num++) {
|
|
cent = &cg_entities[cg.snap->entities[num].number];
|
|
if (cent->currentState.eType == ET_MULTIBEAM) {
|
|
CG_MultiBeam(cent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_GetOrigin(centity_t *cent, vec3_t origin)
|
|
{
|
|
if (cent->currentState.parent == ENTITYNUM_NONE) {
|
|
VectorCopy(cent->lerpOrigin, origin);
|
|
} else {
|
|
int i;
|
|
orientation_t or ;
|
|
refEntity_t *parent;
|
|
|
|
parent = cgi.R_GetRenderEntity(cent->currentState.parent);
|
|
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
|
|
cgi.R_Model_GetHandle(parent->hModel);
|
|
or = cgi.TIKI_Orientation(parent, cent->currentState.tag_num);
|
|
|
|
VectorCopy(parent->origin, origin);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(origin, or.origin[i], parent->axis[i], origin);
|
|
}
|
|
}
|
|
}
|