openmohaa/code/cgame/cg_ents.c

686 lines
19 KiB
C
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
2023-04-30 00:02:16 +02:00
Copyright (C) 2023 the OpenMoHAA team
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
This file is part of OpenMoHAA source code.
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
OpenMoHAA source code is free software; you can redistribute it
2016-03-27 11:49:47 +02:00
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.
2023-04-30 00:02:16 +02:00
OpenMoHAA source code is distributed in the hope that it will be
2016-03-27 11:49:47 +02:00
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
2023-04-30 00:02:16 +02:00
along with OpenMoHAA source code; if not, write to the Free Software
2016-03-27 11:49:47 +02:00
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
2023-04-30 00:02:16 +02:00
2016-03-27 11:49:47 +02:00
// cg_ents.c -- present snapshot entities, happens every single frame
#include "cg_local.h"
#include "cg_radar.h"
2016-03-27 11:49:47 +02:00
/*
==========================================================================
FUNCTIONS CALLED EACH FRAME
==========================================================================
*/
/*
======================
CG_SetEntitySoundPosition
======================
*/
2023-07-05 21:24:23 +02:00
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);
}
}
}
2016-03-27 11:49:47 +02:00
/*
==================
CG_EntityEffects
Add continuous entity effects, like local entity emission and lighting
==================
*/
2023-07-05 21:24:23 +02:00
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;
2023-04-30 00:02:16 +02:00
}
2023-07-05 21:24:23 +02:00
if (cent->currentState.renderfx & RF_ADDITIVE_DLIGHT) {
flags |= additive;
2023-04-30 00:02:16 +02:00
}
2023-07-05 21:24:23 +02:00
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;
}
}
}
2016-03-27 11:49:47 +02:00
/*
==================
CG_General
==================
*/
2023-07-05 21:24:23 +02:00
void CG_General(centity_t *cent)
{
refEntity_t ent;
entityState_t *s1;
int i;
vec3_t vMins, vMaxs, vTmp;
s1 = &cent->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
);
}
2023-04-30 00:02:16 +02:00
if (s1->renderfx & RF_SKYORIGIN) {
AnglesToAxis(cent->lerpAngles, cg.sky_axis);
VectorCopy(cent->lerpOrigin, cg.sky_origin);
}
2023-07-05 21:24:23 +02:00
// if set to invisible, skip
if (!s1->modelindex) {
return;
}
2016-03-27 11:49:47 +02:00
if (s1->renderfx & RF_DONTDRAW) {
return;
}
2023-07-05 21:24:23 +02:00
memset(&ent, 0, sizeof(ent));
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// set frame
2023-04-30 00:02:16 +02:00
2023-07-05 21:24:23 +02:00
ent.wasframe = s1->wasframe;
2023-04-30 00:02:16 +02:00
2023-07-05 21:24:23 +02:00
VectorCopy(cent->lerpOrigin, ent.origin);
VectorCopy(cent->lerpOrigin, ent.oldorigin);
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// set skin
IntegerToBoundingBox(s1->solid, vMins, vMaxs);
VectorMA(ent.origin, 0.5f, ent.lightingOrigin, ent.lightingOrigin);
2023-07-05 21:24:23 +02:00
VectorSubtract(vMins, vMaxs, vTmp);
ent.radius = VectorLength(vTmp) * 0.5;
2023-07-05 21:24:23 +02:00
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;
2023-07-05 21:24:23 +02:00
// add to refresh list
cgi.R_AddRefEntityToScene(&ent, ENTITYNUM_NONE);
if (ent.tiki) {
2023-04-30 01:42:57 +02:00
// update any emitter's...
CG_UpdateEntityEmitters(s1->number, &ent, cent);
}
2016-03-27 11:49:47 +02:00
}
2023-04-30 00:02:16 +02:00
/*
==================
CG_Speaker
Speaker entities can automatically play sounds
==================
*/
2023-07-05 21:24:23 +02:00
void CG_Speaker(centity_t *cent)
{
if (!cent->currentState.clientNum) // FIXME: use something other than clientNum...
{
return; // not auto triggering
}
2023-04-30 00:02:16 +02:00
2023-07-05 21:24:23 +02:00
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();
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_Mover
===============
*/
2023-07-05 21:24:23 +02:00
void CG_Mover(centity_t *cent)
{
refEntity_t ent;
entityState_t *s1;
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
s1 = &cent->currentState;
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// create the render entity
memset(&ent, 0, sizeof(ent));
VectorCopy(cent->lerpOrigin, ent.origin);
VectorCopy(cent->lerpOrigin, ent.oldorigin);
AnglesToAxis(cent->lerpAngles, ent.axis);
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
ent.renderfx &= ~RF_SHADOW;
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// flicker between two skins (FIXME?)
ent.skinNum = (cg.time >> 6) & 1;
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// 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];
}
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
// add to refresh list
cgi.R_AddRefEntityToScene(&ent, ENTITYNUM_NONE);
2016-03-27 11:49:47 +02:00
}
2023-04-30 00:02:16 +02:00
/*
===============
CG_Beam
===============
*/
2023-07-05 21:24:23 +02:00
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 = &cent->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,
""
);
2016-03-27 11:49:47 +02:00
}
2023-07-05 21:24:23 +02:00
void CG_Decal(centity_t *cent)
2016-03-27 11:49:47 +02:00
2023-07-05 21:24:23 +02:00
{
qhandle_t shader;
vec3_t dir;
entityState_t *s1;
s1 = &cent->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],
2023-07-10 21:09:56 +02:00
s1->scale,
s1->scale,
2023-07-05 21:24:23 +02:00
cent->color[0],
cent->color[1],
cent->color[2],
cent->color[3],
qtrue,
qfalse,
2023-07-10 21:09:56 +02:00
qtrue,
2023-07-05 21:24:23 +02:00
qfalse,
2023-07-10 21:09:56 +02:00
0.5f,
0.5f
2023-07-05 21:24:23 +02:00
);
}
2023-04-30 00:02:16 +02:00
2016-03-27 11:49:47 +02:00
/*
===============
CG_Portal
===============
*/
2023-07-05 21:24:23 +02:00
void CG_Portal(centity_t *cent) {}
2016-03-27 11:49:47 +02:00
/*
================
BG_EvaluateTrajectory
================
*/
2023-07-05 21:24:23 +02:00
void BG_EvaluateTrajectory(const trajectory_t *tr, int atTime, const vec3_t base, vec3_t result)
{
float deltaTime;
2023-07-05 21:24:23 +02:00
if (atTime > cg_smoothClientsTime->integer + tr->trTime) {
atTime = cg_smoothClientsTime->integer + tr->trTime;
}
2023-07-05 21:24:23 +02:00
deltaTime = (float)(atTime - tr->trTime) / 1000.0;
2023-07-05 21:24:23 +02:00
result[0] = tr->trDelta[0] * deltaTime + base[0];
result[1] = tr->trDelta[1] * deltaTime + base[1];
result[2] = tr->trDelta[2] * deltaTime + base[2];
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_CalcEntityLerpPositions
===============
*/
2023-07-05 21:24:23 +02:00
void CG_CalcEntityLerpPositions(centity_t *cent)
2023-04-30 01:42:57 +02:00
{
int i;
2023-07-05 21:24:23 +02:00
float f;
2023-04-30 01:42:57 +02:00
f = cg.frameInterpolation;
2023-07-05 21:24:23 +02:00
if (cent->currentState.eType == ET_PLAYER) {
if (cent->currentState.number == cg.snap->ps.clientNum) {
2023-04-30 01:42:57 +02:00
// 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);
}
2023-04-30 00:02:16 +02:00
2023-04-30 01:42:57 +02:00
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++) {
2023-07-05 21:24:23 +02:00
cent->lerpOrigin[i] =
cent->currentState.origin[i] + f * (cent->nextState.origin[i] - cent->currentState.origin[i]);
2023-04-30 01:42:57 +02:00
}
if (!memcmp(cent->currentState.angles, cent->nextState.angles, sizeof(vec3_t))) {
VectorCopy(cent->currentState.angles, cent->lerpAngles);
2023-07-05 21:24:23 +02:00
} else {
2023-04-30 01:42:57 +02:00
// 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);
}
2023-07-05 21:24:23 +02:00
} else if (cent->interpolate) {
2023-04-30 01:42:57 +02:00
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
2023-07-05 21:24:23 +02:00
vec3_t current, next;
2023-04-30 01:42:57 +02:00
// 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(&cent->currentState.pos, cg.snap->serverTime, cent->currentState.origin, current);
BG_EvaluateTrajectory(&cent->nextState.pos, cg.nextSnap->serverTime, cent->nextState.origin, next);
2023-04-30 01:42:57 +02:00
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);
2023-07-05 21:24:23 +02:00
} else {
2023-04-30 01:42:57 +02:00
// 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);
}
2023-07-05 21:24:23 +02:00
} else {
2023-04-30 01:42:57 +02:00
// just use the current frame and evaluate as best we can
BG_EvaluateTrajectory(&cent->currentState.pos, cg.time, cent->currentState.origin, cent->lerpOrigin);
2023-04-30 01:42:57 +02:00
VectorCopy(cent->currentState.angles, cent->lerpAngles);
}
2016-03-27 11:49:47 +02:00
}
/*
===============
CG_AddCEntity
===============
*/
2023-07-05 21:24:23 +02:00
void CG_AddCEntity(centity_t *cent)
2023-04-30 00:02:16 +02:00
{
2023-04-30 20:36:40 +02:00
// event-only entities will have been dealt with already
if (cent->currentState.eType >= ET_EVENTS) {
return;
}
2016-03-27 11:49:47 +02:00
2023-04-30 20:36:40 +02:00
// 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:
2023-05-07 16:50:53 +02:00
case ET_MODELANIM:
2023-04-30 20:36:40 +02:00
CG_Splash(cent);
CG_ModelAnim(cent, qfalse);
break;
2023-05-07 16:50:53 +02:00
case ET_VEHICLE:
CG_Vehicle(cent);
CG_Splash(cent);
CG_ModelAnim(cent, qtrue);
break;
case ET_PLAYER:
2023-07-05 21:24:23 +02:00
CG_Player(cent);
CG_Splash(cent);
CG_ModelAnim(cent, qfalse);
CG_UpdateRadarClient(cent);
break;
2023-07-05 21:24:23 +02:00
case ET_ITEM:
CG_ModelAnim(cent, qfalse);
2023-04-30 20:36:40 +02:00
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:
2023-05-07 16:50:53 +02:00
CG_Rain(cent);
2023-04-30 20:36:40 +02:00
break;
case ET_DECAL:
CG_Decal(cent);
break;
2023-05-07 16:50:53 +02:00
case ET_EMITTER:
CG_Emitter(cent);
break;
case ET_ROPE: // skip
CG_Rope(cent);
2023-07-05 21:24:23 +02:00
break;
case ET_EXEC_COMMANDS:
CG_ModelAnim(cent, qfalse);
break;
2023-04-30 20:36:40 +02:00
}
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_AddPacketEntities
===============
*/
2023-07-05 21:24:23 +02:00
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);
2016-03-27 11:49:47 +02:00
for (i = 0; i < MAX_ENTITIES; i++) {
processed[i] = qtrue;
}
2016-03-27 11:49:47 +02:00
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;
2023-07-05 21:24:23 +02:00
cent = &cg_entities[child];
// add the parent first
// so attachments are consistent
2023-07-05 21:24:23 +02:00
for (parent = cent->currentState.parent; parent != ENTITYNUM_NONE && !processed[parent];
parent = cg_entities[parent].currentState.parent) {
processed[parent] = qtrue;
CG_AddCEntity(&cg_entities[parent]);
}
2023-07-05 21:24:23 +02:00
if (!processed[child]) {
// now add the children if not processed
processed[child] = qtrue;
CG_AddCEntity(cent);
}
}
2023-04-30 00:02:16 +02:00
2023-07-05 21:24:23 +02:00
// 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);
}
}
2016-03-27 11:49:47 +02:00
}
2023-07-05 21:24:23 +02:00
void CG_GetOrigin(centity_t *cent, vec3_t origin)
2023-04-30 01:42:57 +02:00
{
2023-07-05 21:24:23 +02:00
if (cent->currentState.parent == ENTITYNUM_NONE) {
2023-04-30 01:42:57 +02:00
VectorCopy(cent->lerpOrigin, origin);
2023-07-05 21:24:23 +02:00
} else {
2023-04-30 01:42:57 +02:00
int i;
2023-07-05 21:24:23 +02:00
orientation_t or ;
refEntity_t *parent;
2023-04-30 01:42:57 +02:00
parent = cgi.R_GetRenderEntity(cent->currentState.parent);
2023-07-05 21:24:23 +02:00
if (!parent) {
2023-04-30 01:42:57 +02:00
return;
}
cgi.R_Model_GetHandle(parent->hModel);
or = cgi.TIKI_Orientation(parent, cent->currentState.tag_num);
VectorCopy(parent->origin, origin);
2023-07-05 21:24:23 +02:00
for (i = 0; i < 3; i++) {
VectorMA(origin, or.origin[i], parent->axis[i], origin);
2023-04-30 01:42:57 +02:00
}
}
}