2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2025-01-22 20:52:05 +01:00
|
|
|
Copyright (C) 2025 the OpenMoHAA team
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
// DESCRIPTION:
|
|
|
|
// Functions for doing model animation and attachments
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
#include "cg_local.h"
|
2023-05-01 19:56:43 +02:00
|
|
|
#include "tiki.h"
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
static qboolean cg_forceModelAllowed = qfalse;
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_GetPlayerModelTiki
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
const char *CG_GetPlayerModelTiki(const char *modelName)
|
|
|
|
{
|
|
|
|
return va("models/player/%s.tik", modelName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_GetPlayerLocalModelTiki
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
const char *CG_GetPlayerLocalModelTiki(const char *modelName)
|
|
|
|
{
|
|
|
|
return va("models/player/%s.tik", modelName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_PlayerTeamIcon
|
|
|
|
===============
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_PlayerTeamIcon(refEntity_t *pModel, entityState_t *pPlayerState)
|
2023-05-01 19:56:43 +02:00
|
|
|
{
|
2023-05-22 01:44:35 +02:00
|
|
|
qboolean bInArtillery, bInTeam, bSpecialIcon;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg_protocol < PROTOCOL_MOHTA_MIN) {
|
|
|
|
if (pPlayerState->eFlags & EF_ALLIES) {
|
2023-06-29 21:29:57 +02:00
|
|
|
cg.clientinfo[pPlayerState->number].team = TEAM_ALLIES;
|
|
|
|
} else if (pPlayerState->eFlags & EF_AXIS) {
|
|
|
|
cg.clientinfo[pPlayerState->number].team = TEAM_AXIS;
|
|
|
|
} else {
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.clientinfo[pPlayerState->number].team = TEAM_NONE;
|
2023-06-29 21:29:57 +02:00
|
|
|
}
|
2023-05-22 01:44:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pPlayerState->number == cg.snap->ps.clientNum) {
|
|
|
|
return;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
|
|
|
|
bInTeam = qfalse;
|
2023-05-22 01:44:35 +02:00
|
|
|
bSpecialIcon = qfalse;
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cgs.gametype > GT_FFA
|
|
|
|
&& (cg.snap->ps.stats[STAT_TEAM] == TEAM_ALLIES && (pPlayerState->eFlags & EF_ALLIES)
|
|
|
|
|| cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS && (pPlayerState->eFlags & EF_AXIS)
|
|
|
|
|| cg.snap->ps.stats[STAT_TEAM] != TEAM_AXIS && cg.snap->ps.stats[STAT_TEAM] != TEAM_ALLIES
|
|
|
|
&& (pPlayerState->eFlags & EF_ANY_TEAM) != 0)) {
|
|
|
|
bInTeam = qtrue;
|
|
|
|
}
|
2023-05-22 01:44:35 +02:00
|
|
|
|
|
|
|
if (cgs.gametype <= GT_FFA) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bInArtillery = qfalse;
|
|
|
|
if (pPlayerState->eFlags & EF_PLAYER_ARTILLERY) {
|
|
|
|
bInArtillery = qtrue;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bInTeam || (pPlayerState->eFlags & (EF_PLAYER_IN_MENU | EF_PLAYER_TALKING)) || bInArtillery) {
|
|
|
|
int i;
|
|
|
|
int iTag;
|
|
|
|
float fAlpha;
|
|
|
|
float fDist;
|
|
|
|
vec3_t vTmp;
|
2023-05-22 01:44:35 +02:00
|
|
|
refEntity_t iconEnt;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
memset(&iconEnt, 0, sizeof(iconEnt));
|
|
|
|
if ((pPlayerState->eFlags & EF_PLAYER_TALKING) != 0 && ((cg.time >> 8) & 1) != 0) {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/talking_headicon.spr");
|
|
|
|
bSpecialIcon = qtrue;
|
|
|
|
} else if ((pPlayerState->eFlags & EF_PLAYER_IN_MENU) != 0) {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/inmenu_headicon.spr");
|
|
|
|
bSpecialIcon = qtrue;
|
|
|
|
} else {
|
2023-05-22 01:44:35 +02:00
|
|
|
if (!bInTeam) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bInArtillery) {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/inmenu_artilleryicon.spr");
|
|
|
|
bSpecialIcon = qtrue;
|
|
|
|
} else if ((pPlayerState->eFlags & 0x80) != 0) {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/allies_headicon.spr");
|
|
|
|
} else {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/axis_headicon.spr");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(vTmp, 0, sizeof(vTmp));
|
|
|
|
AnglesToAxis(vTmp, iconEnt.axis);
|
|
|
|
|
|
|
|
iconEnt.scale = 0.5f;
|
|
|
|
iconEnt.renderfx = 0;
|
|
|
|
iconEnt.reType = RT_SPRITE;
|
|
|
|
iconEnt.shaderTime = 0.0f;
|
|
|
|
iconEnt.frameInfo[0].index = 0;
|
|
|
|
iconEnt.shaderRGBA[0] = -1;
|
|
|
|
iconEnt.shaderRGBA[1] = -1;
|
|
|
|
iconEnt.shaderRGBA[2] = -1;
|
2023-05-22 01:44:35 +02:00
|
|
|
VectorCopy(pModel->origin, iconEnt.origin);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
iTag = cgi.Tag_NumForName(pModel->tiki, "eyes bone");
|
|
|
|
if (iTag == -1) {
|
|
|
|
iconEnt.origin[2] = iconEnt.origin[2] + 96.0f;
|
|
|
|
} else {
|
2023-05-22 01:44:35 +02:00
|
|
|
orientation_t oEyes = cgi.TIKI_Orientation(pModel, iTag);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; ++i) {
|
|
|
|
VectorMA(iconEnt.origin, oEyes.origin[i], pModel->axis[i], iconEnt.origin);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
iconEnt.origin[2] = iconEnt.origin[2] + 20.0f;
|
|
|
|
}
|
2023-05-22 01:44:35 +02:00
|
|
|
|
|
|
|
VectorSubtract(iconEnt.origin, cg.refdef.vieworg, vTmp);
|
|
|
|
fDist = VectorLength(vTmp);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (fDist < 256.0f) {
|
2023-05-29 15:25:04 +02:00
|
|
|
iconEnt.scale = fDist / 853.0f + 0.2f;
|
|
|
|
} else if (fDist > 512.0f) {
|
|
|
|
// Make sure to scale so the icon can be seen far away
|
|
|
|
iconEnt.scale = (fDist - 512.0f) / 2560.0f + 0.5f;
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
2023-05-22 01:44:35 +02:00
|
|
|
|
2023-05-29 15:25:04 +02:00
|
|
|
if (iconEnt.scale > 1.0f) {
|
|
|
|
iconEnt.scale = 1.0f;
|
2023-05-22 01:44:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fDist > 256.0) {
|
2023-05-29 15:25:04 +02:00
|
|
|
fAlpha = 1.0f;
|
|
|
|
} else if (fDist >= 72.0f) {
|
|
|
|
fAlpha = (fDist - 72.0f) / 184.0f;
|
|
|
|
} else {
|
|
|
|
fAlpha = 0.0f;
|
2023-05-22 01:44:35 +02:00
|
|
|
}
|
|
|
|
|
2023-05-29 15:25:04 +02:00
|
|
|
if (cg.snap->ps.stats[STAT_TEAM] == TEAM_ALLIES || cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS) {
|
|
|
|
fAlpha = fAlpha * 0.65f;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-29 15:25:04 +02:00
|
|
|
fAlpha = fAlpha * 0.4f;
|
2023-05-22 01:44:35 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bSpecialIcon) {
|
|
|
|
int value = (int)((fAlpha + 0.6f) * 255.0f);
|
|
|
|
if (value > 255) {
|
|
|
|
value = 255;
|
|
|
|
}
|
|
|
|
iconEnt.shaderRGBA[3] = value;
|
|
|
|
} else {
|
|
|
|
iconEnt.shaderRGBA[3] = (int)(fAlpha * 255.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fAlpha > 0.0 || bSpecialIcon) {
|
|
|
|
if (bSpecialIcon) {
|
|
|
|
VectorMA(iconEnt.origin, -2.0f, cg.refdef.viewaxis[0], iconEnt.origin);
|
|
|
|
iconEnt.scale += 0.05f;
|
|
|
|
}
|
2023-05-22 01:44:35 +02:00
|
|
|
|
|
|
|
cgi.R_AddRefSpriteToScene(&iconEnt);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bSpecialIcon && bInTeam && fAlpha > 0.0f) {
|
2023-05-22 01:44:35 +02:00
|
|
|
if (pPlayerState->eFlags & EF_ALLIES) {
|
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/allies_headicon.spr");
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-22 01:44:35 +02:00
|
|
|
iconEnt.hModel = cgi.R_RegisterModel("textures/hud/axis_headicon.spr");
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
VectorMA(iconEnt.origin, 4.0f, cg.refdef.viewaxis[0], iconEnt.origin);
|
|
|
|
iconEnt.scale = iconEnt.scale - 0.1;
|
|
|
|
iconEnt.shaderRGBA[3] = (int)(fAlpha * 255.0f);
|
|
|
|
cgi.R_AddRefSpriteToScene(&iconEnt);
|
2023-05-22 01:44:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2024-10-20 22:18:40 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_InterpolateAnimParms
|
|
|
|
|
|
|
|
Interpolate between current and next entity
|
|
|
|
===============
|
|
|
|
*/
|
2025-01-22 20:52:05 +01:00
|
|
|
void CG_InterpolateAnimParms(entityState_t *state, entityState_t *sNext, refEntity_t *model)
|
2024-10-20 22:18:40 +02:00
|
|
|
{
|
|
|
|
static cvar_t *vmEntity = NULL;
|
2025-01-22 20:52:05 +01:00
|
|
|
int i;
|
|
|
|
float t;
|
|
|
|
float animLength;
|
|
|
|
float t1, t2;
|
2024-10-20 22:18:40 +02:00
|
|
|
|
|
|
|
if (!vmEntity) {
|
|
|
|
vmEntity = cgi.Cvar_Get("viewmodelanim", "1", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sNext && sNext->usageIndex == state->usageIndex) {
|
|
|
|
t1 = cg.time - cg.snap->serverTime;
|
|
|
|
t2 = cg.nextSnap->serverTime - cg.snap->serverTime;
|
2025-01-22 20:52:05 +01:00
|
|
|
t = t1 / t2;
|
2024-10-20 22:18:40 +02:00
|
|
|
|
|
|
|
model->actionWeight = (sNext->actionWeight - state->actionWeight) * t + state->actionWeight;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
if (sNext->frameInfo[i].weight) {
|
|
|
|
model->frameInfo[i].index = sNext->frameInfo[i].index;
|
|
|
|
if (sNext->frameInfo[i].index == state->frameInfo[i].index && state->frameInfo[i].weight) {
|
|
|
|
model->frameInfo[i].weight =
|
|
|
|
(sNext->frameInfo[i].weight - state->frameInfo[i].weight) * t + state->frameInfo[i].weight;
|
|
|
|
|
|
|
|
if (sNext->frameInfo[i].time >= state->frameInfo[i].time) {
|
2025-01-22 20:52:05 +01:00
|
|
|
model->frameInfo[i].time =
|
|
|
|
(sNext->frameInfo[i].time - state->frameInfo[i].time) * t + state->frameInfo[i].time;
|
2024-10-20 22:18:40 +02:00
|
|
|
} else {
|
|
|
|
animLength = cgi.Anim_Time(model->tiki, sNext->frameInfo[i].index);
|
|
|
|
if (!animLength) {
|
|
|
|
t1 = 0.0;
|
|
|
|
} else {
|
|
|
|
t1 = (animLength + sNext->frameInfo[i].time - state->frameInfo[i].time) * t
|
|
|
|
+ state->frameInfo[i].time;
|
|
|
|
}
|
|
|
|
|
|
|
|
t2 = t1;
|
|
|
|
while (t2 > animLength) {
|
|
|
|
t2 -= animLength;
|
|
|
|
|
|
|
|
if (t2 == t1) {
|
|
|
|
t2 = 1.0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
t1 = t2;
|
|
|
|
}
|
|
|
|
|
|
|
|
model->frameInfo[i].time = t2;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
animLength = cgi.Anim_Time(model->tiki, sNext->frameInfo[i].index);
|
|
|
|
if (!animLength) {
|
|
|
|
t1 = 0.0;
|
|
|
|
} else {
|
|
|
|
t1 = sNext->frameInfo[i].time - (cg.nextSnap->serverTime - cg.time) / 1000.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
model->frameInfo[i].time = Q_max(0, t1);
|
|
|
|
model->frameInfo[i].weight = sNext->frameInfo[i].weight;
|
|
|
|
}
|
|
|
|
} else if (sNext->frameInfo[i].index == state->frameInfo[i].index) {
|
|
|
|
animLength = cgi.Anim_Time(model->tiki, sNext->frameInfo[i].index);
|
|
|
|
if (!animLength) {
|
|
|
|
t1 = 0.0;
|
|
|
|
} else {
|
|
|
|
t1 = (cg.time - cg.snap->serverTime) / 1000.0 + state->frameInfo[i].time;
|
|
|
|
}
|
|
|
|
|
|
|
|
model->frameInfo[i].index = Q_clamp_int(state->frameInfo[i].index, 0, model->tiki->a->num_anims - 1);
|
|
|
|
model->frameInfo[i].time = Q_min(animLength, t1);
|
|
|
|
model->frameInfo[i].weight = (1.0 - t) * state->frameInfo[i].weight;
|
|
|
|
} else {
|
|
|
|
model->frameInfo[i].index = -1;
|
|
|
|
model->frameInfo[i].weight = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// no next state, don't blend anims
|
|
|
|
|
|
|
|
model->actionWeight = state->actionWeight;
|
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
if (state->frameInfo[i].weight) {
|
|
|
|
model->frameInfo[i].index = Q_clamp_int(state->frameInfo[i].index, 0, model->tiki->a->num_anims - 1);
|
|
|
|
model->frameInfo[i].time = state->frameInfo[i].time;
|
|
|
|
model->frameInfo[i].weight = state->frameInfo[i].weight;
|
|
|
|
} else {
|
|
|
|
model->frameInfo[i].index = -1;
|
|
|
|
model->frameInfo[i].weight = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vmEntity->integer == state->number) {
|
|
|
|
static cvar_t *curanim;
|
|
|
|
if (!curanim) {
|
|
|
|
curanim = cgi.Cvar_Get("viewmodelanimslot", "1", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
cgi.Cvar_Set("viewmodelanimclienttime", va("%0.2f", model->frameInfo[curanim->integer].time));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 17:01:40 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_CastFootShadow
|
|
|
|
|
|
|
|
Cast complex foot shadow using lights
|
|
|
|
===============
|
|
|
|
*/
|
2025-01-22 20:52:05 +01:00
|
|
|
void CG_CastFootShadow(const vec_t *vLightPos, vec_t *vLightIntensity, int iTag, refEntity_t *model)
|
2023-08-27 17:01:40 +02:00
|
|
|
{
|
2025-01-22 20:52:05 +01:00
|
|
|
int i;
|
|
|
|
float fAlpha;
|
|
|
|
float fLength;
|
|
|
|
float fWidth;
|
|
|
|
float fAlphaOfs;
|
|
|
|
float fOfs;
|
|
|
|
float fPitchCos;
|
|
|
|
vec3_t vPos;
|
|
|
|
vec3_t vEnd;
|
|
|
|
vec3_t vDelta;
|
|
|
|
vec3_t vLightAngles;
|
|
|
|
trace_t trace;
|
2023-08-27 17:01:40 +02:00
|
|
|
orientation_t oFoot;
|
|
|
|
|
|
|
|
VectorCopy(model->origin, vPos);
|
|
|
|
oFoot = cgi.TIKI_Orientation(model, iTag);
|
|
|
|
VectorMA(oFoot.origin, 2, oFoot.axis[1], vEnd);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
VectorMA(vPos, vEnd[i], model->axis[i], vPos);
|
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
if (cg_shadowdebug->integer) {
|
2023-08-27 17:01:40 +02:00
|
|
|
vec3_t vDir;
|
|
|
|
|
|
|
|
//
|
|
|
|
// show debug lines
|
|
|
|
//
|
|
|
|
memset(vDir, 0, sizeof(vDir));
|
|
|
|
for (i = 0; i < 3; ++i) {
|
|
|
|
VectorMA(vDir, oFoot.axis[0][i], model->axis[i], vDir);
|
|
|
|
}
|
|
|
|
VectorMA(vPos, 32.0, vDir, vEnd);
|
|
|
|
cgi.R_DebugLine(vPos, vEnd, 1.0, 0.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
memset(vDir, 0, sizeof(vDir));
|
|
|
|
for (i = 0; i < 3; ++i) {
|
|
|
|
VectorMA(vDir, oFoot.axis[1][i], model->axis[i], vDir);
|
|
|
|
}
|
|
|
|
VectorMA(vPos, 32.0, vDir, vEnd);
|
|
|
|
cgi.R_DebugLine(vPos, vEnd, 0.0, 1.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
memset(vDir, 0, sizeof(vDir));
|
|
|
|
for (i = 0; i < 3; ++i) {
|
|
|
|
VectorMA(vDir, oFoot.axis[2][i], model->axis[i], vDir);
|
|
|
|
}
|
|
|
|
VectorMA(vPos, 32.0, vDir, vEnd);
|
|
|
|
cgi.R_DebugLine(vPos, vEnd, 0.0, 0.0, 1.0, 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the direction
|
|
|
|
VectorSubtract(vLightPos, vPos, vDelta);
|
|
|
|
VectorNormalizeFast(vDelta);
|
|
|
|
vectoangles(vDelta, vLightAngles);
|
|
|
|
|
|
|
|
// normalize to 180 degrees
|
|
|
|
if (vLightAngles[0] > 180) {
|
|
|
|
vLightAngles[0] -= 360;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vLightAngles[0] > -5.7319679) {
|
|
|
|
// FIXME: what is -5.7319679?
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fPitchCos = cos(DEG2RAD(vLightAngles[0]));
|
|
|
|
if (fPitchCos > 0.955) {
|
|
|
|
fAlpha = 1.0 - (fPitchCos - 0.955) * 25;
|
|
|
|
} else {
|
|
|
|
fAlpha = 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fLength = fPitchCos * fPitchCos * 32.0 + fPitchCos * 8.0 + 10.0;
|
2025-01-22 20:52:05 +01:00
|
|
|
fOfs = 0.5 - (-4.1 / tan(DEG2RAD(vLightAngles[0])) + 4.0 - fLength) / fLength * 0.5;
|
2023-08-27 17:01:40 +02:00
|
|
|
VectorMA(vPos, -96.0, vDelta, vEnd);
|
|
|
|
CG_Trace(&trace, vPos, vec3_origin, vec3_origin, vEnd, 0, MASK_FOOTSHADOW, qfalse, qtrue, "CG_CastFootShadow");
|
2025-01-22 20:52:05 +01:00
|
|
|
|
|
|
|
if (cg_shadowdebug->integer) {
|
2023-08-27 17:01:40 +02:00
|
|
|
cgi.R_DebugLine(vPos, vLightPos, 0.75, 0.75, 0.5, 1.0);
|
|
|
|
cgi.R_DebugLine(vPos, vEnd, 1.0, 1.0, 1.0, 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (trace.fraction == 1.0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace.fraction -= 0.0427f;
|
|
|
|
if (trace.fraction < 0) {
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
fWidth = 10.f - (1.f - trace.fraction) * 6.f;
|
2023-08-27 17:01:40 +02:00
|
|
|
fAlphaOfs = (1.f - trace.fraction) * fAlpha;
|
|
|
|
|
|
|
|
fAlpha = Q_max(vLightIntensity[0], Q_max(vLightIntensity[1], vLightIntensity[2]));
|
|
|
|
|
|
|
|
if (fAlpha < 0.1) {
|
|
|
|
vLightIntensity[0] *= 0.1 / fAlpha * fAlphaOfs;
|
|
|
|
vLightIntensity[1] *= 0.1 / fAlpha * fAlphaOfs;
|
|
|
|
vLightIntensity[2] *= 0.1 / fAlpha * fAlphaOfs;
|
|
|
|
} else {
|
|
|
|
vLightIntensity[0] *= fAlphaOfs;
|
|
|
|
vLightIntensity[1] *= fAlphaOfs;
|
|
|
|
vLightIntensity[2] *= fAlphaOfs;
|
|
|
|
}
|
|
|
|
|
|
|
|
fAlpha = Q_max(vLightIntensity[0], Q_max(vLightIntensity[1], vLightIntensity[2]));
|
|
|
|
if (fAlpha > 0.6) {
|
|
|
|
vLightIntensity[0] *= 0.6 / fAlpha;
|
|
|
|
vLightIntensity[1] *= 0.6 / fAlpha;
|
|
|
|
vLightIntensity[2] *= 0.6 / fAlpha;
|
|
|
|
}
|
|
|
|
|
2023-08-27 17:47:49 +02:00
|
|
|
if (vLightIntensity[0] <= 0.01 && vLightIntensity[1] <= 0.01 && vLightIntensity[2] <= 0.01) {
|
2023-08-27 17:01:40 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CG_ImpactMark(
|
|
|
|
cgs.media.footShadowMarkShader,
|
|
|
|
trace.endpos,
|
|
|
|
trace.plane.normal,
|
|
|
|
vLightAngles[1],
|
|
|
|
fWidth,
|
|
|
|
fLength,
|
|
|
|
vLightIntensity[0],
|
|
|
|
vLightIntensity[1],
|
|
|
|
vLightIntensity[2],
|
|
|
|
1.0,
|
|
|
|
qfalse,
|
|
|
|
qtrue,
|
|
|
|
qfalse,
|
|
|
|
qfalse,
|
|
|
|
0.5,
|
|
|
|
fOfs
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_CastSimpleFeetShadow
|
|
|
|
|
|
|
|
Cast basic feet shadow
|
|
|
|
===============
|
|
|
|
*/
|
2025-01-22 20:52:05 +01:00
|
|
|
void CG_CastSimpleFeetShadow(
|
|
|
|
const trace_t *pTrace,
|
|
|
|
float fWidth,
|
|
|
|
float fAlpha,
|
|
|
|
int iRightTag,
|
|
|
|
int iLeftTag,
|
|
|
|
const dtiki_t *tiki,
|
|
|
|
refEntity_t *model
|
|
|
|
)
|
2023-08-27 17:01:40 +02:00
|
|
|
{
|
2025-01-22 20:52:05 +01:00
|
|
|
int i;
|
|
|
|
float fShadowYaw;
|
|
|
|
float fLength;
|
|
|
|
vec3_t vPos, vRightPos, vLeftPos;
|
|
|
|
vec3_t vDelta;
|
2023-08-27 17:01:40 +02:00
|
|
|
orientation_t oFoot;
|
|
|
|
|
|
|
|
//
|
|
|
|
// right foot
|
|
|
|
//
|
|
|
|
VectorCopy(pTrace->endpos, vRightPos);
|
|
|
|
oFoot = cgi.TIKI_Orientation(model, iRightTag);
|
|
|
|
VectorMA(oFoot.origin, 3, oFoot.axis[1], vPos);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
VectorMA(vRightPos, vPos[i], model->axis[i], vRightPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorMA(vRightPos, -2, oFoot.axis[1], vRightPos);
|
|
|
|
|
|
|
|
//
|
|
|
|
// left foot
|
|
|
|
//
|
|
|
|
VectorCopy(pTrace->endpos, vLeftPos);
|
|
|
|
oFoot = cgi.TIKI_Orientation(model, iLeftTag);
|
|
|
|
VectorMA(oFoot.origin, 3, oFoot.axis[1], vPos);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
VectorMA(vLeftPos, vPos[i], model->axis[i], vLeftPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorAdd(vRightPos, vLeftPos, vPos);
|
|
|
|
VectorScale(vPos, 0.5, vPos);
|
|
|
|
VectorSubtract(vRightPos, vLeftPos, vDelta);
|
|
|
|
VectorMA(vLeftPos, 0.5, vDelta, vPos);
|
|
|
|
|
|
|
|
// get the facing yaw
|
|
|
|
fShadowYaw = vectoyaw(vDelta);
|
2025-01-22 20:52:05 +01:00
|
|
|
fLength = VectorNormalize(vDelta) * 0.5 + 12;
|
2023-08-27 17:01:40 +02:00
|
|
|
if (fLength < fWidth * 0.7) {
|
|
|
|
fLength = fWidth * 0.7;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the mark
|
|
|
|
CG_ImpactMark(
|
|
|
|
cgs.media.shadowMarkShader,
|
|
|
|
vPos,
|
|
|
|
pTrace->plane.normal,
|
|
|
|
fShadowYaw,
|
|
|
|
fWidth * 0.7,
|
|
|
|
fLength,
|
|
|
|
fAlpha,
|
|
|
|
fAlpha,
|
|
|
|
fAlpha,
|
|
|
|
1.0,
|
|
|
|
qfalse,
|
|
|
|
qtrue,
|
|
|
|
qfalse,
|
|
|
|
qfalse,
|
|
|
|
0.5,
|
|
|
|
0.5
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_EntityShadow
|
|
|
|
|
|
|
|
Returns the Z component of the surface being shadowed
|
|
|
|
|
|
|
|
should it return a full plane instead of a Z?
|
|
|
|
===============
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
#define SHADOW_DISTANCE 96
|
|
|
|
|
|
|
|
qboolean CG_EntityShadow(centity_t *cent, refEntity_t *model)
|
|
|
|
{
|
2023-08-27 17:01:40 +02:00
|
|
|
int iTagL, iTagR;
|
|
|
|
float alpha;
|
|
|
|
float fWidth;
|
2023-07-05 21:24:23 +02:00
|
|
|
vec3_t end;
|
2023-08-27 17:01:40 +02:00
|
|
|
vec3_t vMins, vMaxs;
|
|
|
|
vec3_t vSize;
|
2023-07-05 21:24:23 +02:00
|
|
|
trace_t trace;
|
2023-08-27 17:01:40 +02:00
|
|
|
|
2023-09-06 00:00:50 +02:00
|
|
|
iTagR = -1;
|
2023-07-05 21:24:23 +02:00
|
|
|
|
|
|
|
if (cg_shadows->integer == 0) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (model->renderfx & RF_SKYENTITY) {
|
|
|
|
// no shadows on sky entities
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2023-08-27 17:01:40 +02:00
|
|
|
if (cg_shadows->integer == 2 && (model->renderfx & RF_SHADOW_PRECISE)) {
|
|
|
|
iTagL = cgi.Tag_NumForName(model->tiki, "Bip01 L Foot");
|
|
|
|
if (iTagL != -1) {
|
|
|
|
iTagR = cgi.Tag_NumForName(model->tiki, "Bip01 R Foot");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iTagR != -1) {
|
2025-01-22 20:52:05 +01:00
|
|
|
int iNumLights, iCurrLight;
|
2023-08-27 17:01:40 +02:00
|
|
|
vec3_t avLightPos[16], avLightIntensity[16];
|
|
|
|
|
|
|
|
iNumLights = Q_clamp(cg_shadowscount->integer, 1, 8);
|
|
|
|
iNumLights = cgi.R_GatherLightSources(model->origin, avLightPos, avLightIntensity, iNumLights);
|
2025-01-22 20:52:05 +01:00
|
|
|
if (iNumLights) {
|
|
|
|
for (iCurrLight = 0; iCurrLight < iNumLights; iCurrLight++) {
|
2023-08-27 17:01:40 +02:00
|
|
|
CG_CastFootShadow(avLightPos[iCurrLight], avLightIntensity[iCurrLight], iTagL, model);
|
|
|
|
CG_CastFootShadow(avLightPos[iCurrLight], avLightIntensity[iCurrLight], iTagR, model);
|
|
|
|
}
|
|
|
|
|
|
|
|
// shadow was casted properly
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
|
|
|
|
// send a trace down from the player to the ground
|
|
|
|
VectorCopy(model->origin, end);
|
|
|
|
end[2] -= SHADOW_DISTANCE;
|
|
|
|
|
|
|
|
cgi.CM_BoxTrace(&trace, model->origin, end, vec3_origin, vec3_origin, 0, MASK_PLAYERSOLID, qfalse);
|
|
|
|
|
|
|
|
// no shadow if too high
|
|
|
|
if (trace.fraction == 1.0) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2023-08-27 17:01:40 +02:00
|
|
|
// since 2.0: no shadow if solid
|
|
|
|
if (trace.startsolid || trace.allsolid) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((cg_shadows->integer == 3) && (model->renderfx & RF_SHADOW_PRECISE)) {
|
2023-07-05 21:24:23 +02:00
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// get the bounds of the current frame
|
|
|
|
//
|
2023-08-27 17:01:40 +02:00
|
|
|
fWidth = model->scale * cgi.R_ModelRadius(model->hModel);
|
|
|
|
if (fWidth < 1) {
|
2023-07-05 21:24:23 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fade the shadow out with height
|
2023-08-27 17:01:40 +02:00
|
|
|
alpha = (1.0 - trace.fraction) * 0.65f;
|
2023-07-05 21:24:23 +02:00
|
|
|
|
2023-08-27 17:01:40 +02:00
|
|
|
if (model->renderfx & RF_SHADOW_PRECISE) {
|
|
|
|
iTagL = cgi.Tag_NumForName(model->tiki, "Bip01 L Foot");
|
|
|
|
if (iTagL != -1) {
|
|
|
|
iTagR = cgi.Tag_NumForName(model->tiki, "Bip01 R Foot");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iTagR != -1) {
|
|
|
|
if (cg_shadows->integer == 2) {
|
|
|
|
alpha *= 0.6f;
|
|
|
|
}
|
|
|
|
|
|
|
|
CG_CastSimpleFeetShadow(&trace, fWidth, alpha, iTagR, iTagL, model->tiki, model);
|
|
|
|
return qtrue;
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 17:01:40 +02:00
|
|
|
cgi.R_ModelBounds(model->hModel, vMins, vMaxs);
|
|
|
|
VectorSubtract(vMaxs, vMins, vSize);
|
|
|
|
VectorScale(vSize, 0.6f, vSize);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
// add the mark as a temporary, so it goes directly to the renderer
|
|
|
|
// without taking a spot in the cg_marks array
|
|
|
|
CG_ImpactMark(
|
|
|
|
cgs.media.shadowMarkShader,
|
|
|
|
trace.endpos,
|
|
|
|
trace.plane.normal,
|
|
|
|
cent->lerpAngles[YAW],
|
2023-08-27 17:01:40 +02:00
|
|
|
vSize[1],
|
|
|
|
vSize[0],
|
2023-07-05 21:24:23 +02:00
|
|
|
alpha,
|
|
|
|
alpha,
|
|
|
|
alpha,
|
|
|
|
1,
|
|
|
|
qfalse,
|
|
|
|
qtrue,
|
2023-08-27 17:01:40 +02:00
|
|
|
qfalse,
|
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
|
|
|
);
|
|
|
|
|
|
|
|
return qtrue;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// NEW ANIMATION AND THREE PART MODEL SYSTEM
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
//=================
|
|
|
|
//CG_AnimationDebugMessage
|
|
|
|
//=================
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_AnimationDebugMessage(int number, const char *fmt, ...)
|
|
|
|
{
|
2023-04-30 00:02:16 +02:00
|
|
|
#ifndef NDEBUG
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg_debugAnim->integer) {
|
|
|
|
va_list argptr;
|
|
|
|
char msg[1024];
|
|
|
|
|
|
|
|
va_start(argptr, fmt);
|
2024-09-20 21:53:48 +02:00
|
|
|
Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
|
2023-07-05 21:24:23 +02:00
|
|
|
va_end(argptr);
|
|
|
|
|
|
|
|
if ((!cg_debugAnimWatch->integer) || ((cg_debugAnimWatch->integer - 1) == number)) {
|
|
|
|
if (cg_debugAnim->integer == 2) {
|
|
|
|
cgi.DebugPrintf(msg);
|
|
|
|
} else {
|
|
|
|
cgi.Printf(msg);
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
#endif
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
/*
|
|
|
|
======================
|
|
|
|
CG_AttachEntity
|
|
|
|
|
|
|
|
Modifies the entities position and axis by the given
|
|
|
|
tag location
|
|
|
|
======================
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_AttachEntity(
|
|
|
|
refEntity_t *entity, refEntity_t *parent, dtiki_t *tiki, int tagnum, qboolean use_angles, vec3_t attach_offset
|
|
|
|
)
|
2023-04-30 01:42:57 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
int i;
|
|
|
|
orientation_t or ;
|
|
|
|
vec3_t tempAxis[3];
|
|
|
|
vec3_t vOrigin;
|
2023-11-06 19:58:36 +01:00
|
|
|
vec3_t vDeltaLightOrg;
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-05-02 20:24:24 +02:00
|
|
|
or = cgi.TIKI_Orientation(parent, tagnum);
|
2023-04-30 01:42:57 +02:00
|
|
|
//cgi.Printf( "th = %d %.2f %.2f %.2f\n", tikihandle, or.origin[ 0 ], or.origin[ 1 ], or.origin[ 2 ] );
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-11-06 19:58:36 +01:00
|
|
|
VectorSubtract(entity->lightingOrigin, entity->origin, vDeltaLightOrg);
|
2023-04-30 01:42:57 +02:00
|
|
|
VectorCopy(parent->origin, entity->origin);
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < 3; i++) {
|
2023-05-02 20:24:24 +02:00
|
|
|
VectorMA(entity->origin, or.origin[i], parent->axis[i], entity->origin);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (attach_offset[0] || attach_offset[1] || attach_offset[2]) {
|
2023-05-02 20:24:24 +02:00
|
|
|
MatrixMultiply(or.axis, parent->axis, tempAxis);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
VectorMA(entity->origin, attach_offset[i], tempAxis[i], entity->origin);
|
|
|
|
}
|
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-05-02 20:24:24 +02:00
|
|
|
VectorCopy(entity->origin, entity->oldorigin);
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (use_angles) {
|
2023-11-06 19:58:36 +01:00
|
|
|
MatrixMultiply(entity->axis, or.axis, tempAxis);
|
|
|
|
MatrixMultiply(tempAxis, parent->axis, entity->axis);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 01:42:57 +02:00
|
|
|
entity->scale *= parent->scale;
|
|
|
|
entity->renderfx |= (parent->renderfx & ~(RF_FLAGS_NOT_INHERITED | RF_LIGHTING_ORIGIN));
|
2023-05-02 20:24:24 +02:00
|
|
|
|
2023-11-06 19:58:36 +01:00
|
|
|
MatrixTransformVectorRight(entity->axis, vDeltaLightOrg, vOrigin);
|
2023-05-03 23:27:20 +02:00
|
|
|
VectorAdd(entity->origin, vOrigin, entity->lightingOrigin);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_AttachEyeEntity
|
|
|
|
===============
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_AttachEyeEntity(
|
|
|
|
refEntity_t *entity, refEntity_t *parent, dtiki_t *tiki, int tagnum, qboolean use_angles, vec_t *attach_offset
|
|
|
|
)
|
2023-05-01 19:56:43 +02:00
|
|
|
{
|
2023-07-21 23:43:57 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
VectorCopy(cg.refdef.vieworg, entity->origin);
|
|
|
|
|
|
|
|
if (use_angles) {
|
|
|
|
AnglesToAxis(cg.refdefViewAngles, entity->axis);
|
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
if (attach_offset[0] || attach_offset[1] || attach_offset[2]) {
|
2023-07-21 23:43:57 +02:00
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
VectorMA(entity->origin, attach_offset[i], entity->axis[i], entity->origin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorCopy(entity->origin, entity->oldorigin);
|
|
|
|
entity->scale *= parent->scale;
|
2023-07-22 15:23:03 +02:00
|
|
|
entity->renderfx |= (parent->renderfx & ~(RF_FLAGS_NOT_INHERITED | RF_LIGHTING_ORIGIN));
|
2023-07-21 23:43:57 +02:00
|
|
|
VectorCopy(parent->lightingOrigin, entity->lightingOrigin);
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_IsValidServerModel
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
qboolean CG_IsValidServerModel(const char *modelpath)
|
|
|
|
{
|
|
|
|
const char *str;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 1; i < MAX_MODELS; i++) {
|
|
|
|
str = CG_ConfigString(CS_MODELS + i);
|
|
|
|
if (!Q_stricmp(str, modelpath)) {
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_CheckValidModels
|
|
|
|
|
|
|
|
This verifies the allied player model and the german player model:
|
|
|
|
- If they don't exist on the client, reset to the default allied player model
|
|
|
|
- If they don't exist on the server, don't allow forceModel so the client explicitly know the skin isn't supported
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CG_CheckValidModels()
|
|
|
|
{
|
|
|
|
const char *modelpath;
|
|
|
|
qboolean isDirty = qfalse;
|
|
|
|
|
|
|
|
if (dm_playermodel->modified) {
|
|
|
|
// Check for allied model
|
|
|
|
modelpath = va("models/player/%s.tik", dm_playermodel->string);
|
|
|
|
if (!cgi.R_RegisterModel(modelpath)) {
|
|
|
|
cgi.Printf(
|
|
|
|
"Allied model '%s' is invalid, resetting to '%s'\n", dm_playermodel->string, dm_playermodel->resetString
|
|
|
|
);
|
|
|
|
|
|
|
|
cgi.Cvar_Set("dm_playermodel", dm_playermodel->resetString);
|
|
|
|
modelpath = va("models/player/%s.tik", dm_playermodel->string);
|
|
|
|
}
|
|
|
|
|
|
|
|
cg.serverAlliedModelValid = CG_IsValidServerModel(modelpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dm_playergermanmodel->modified) {
|
|
|
|
// Check for axis model
|
|
|
|
modelpath = va("models/player/%s.tik", dm_playergermanmodel->string);
|
|
|
|
if (!cgi.R_RegisterModel(modelpath)) {
|
|
|
|
cgi.Printf(
|
|
|
|
"Allied model '%s' is invalid, resetting to '%s'\n",
|
|
|
|
dm_playergermanmodel->string,
|
|
|
|
dm_playergermanmodel->resetString
|
|
|
|
);
|
|
|
|
|
|
|
|
cgi.Cvar_Set("dm_playergermanmodel", dm_playergermanmodel->resetString);
|
|
|
|
modelpath = va("models/player/%s.tik", dm_playergermanmodel->string);
|
|
|
|
}
|
|
|
|
|
|
|
|
cg.serverAxisModelValid = CG_IsValidServerModel(modelpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dm_playermodel->modified || dm_playergermanmodel->modified) {
|
|
|
|
cg_forceModelAllowed = cg.serverAlliedModelValid && cg.serverAxisModelValid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_ServerModelLoaded
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CG_ServerModelLoaded(const char *name, qhandle_t handle)
|
|
|
|
{
|
|
|
|
if (!Q_stricmpn(name, "models/player/", 14) && (!cg.serverAlliedModelValid || !cg.serverAxisModelValid)) {
|
|
|
|
char modelName[MAX_QPATH];
|
|
|
|
COM_StripExtension(name + 14, modelName, sizeof(modelName));
|
|
|
|
|
|
|
|
//
|
|
|
|
// The player model has been loaded on the server
|
|
|
|
// so try again parsing
|
|
|
|
//
|
|
|
|
if (!Q_stricmp(modelName, dm_playermodel->string)) {
|
|
|
|
dm_playermodel->modified = qtrue;
|
|
|
|
}
|
|
|
|
if (!Q_stricmp(modelName, dm_playergermanmodel->string)) {
|
|
|
|
dm_playergermanmodel->modified = qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_ServerModelUnloaded
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CG_ServerModelUnloaded(qhandle_t handle)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
if (cg.serverAlliedModelValid && handle == cg.hAlliedPlayerModelHandle) {
|
|
|
|
dm_playermodel->modified = qtrue;
|
|
|
|
}
|
|
|
|
if (cg.serverAxisModelValid && handle == cg.hAxisPlayerModelHandle) {
|
|
|
|
dm_playergermanmodel->modified = qtrue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_UpdateForceModels
|
|
|
|
===============
|
|
|
|
*/
|
2023-05-01 19:56:43 +02:00
|
|
|
void CG_UpdateForceModels()
|
|
|
|
{
|
2023-07-21 23:43:57 +02:00
|
|
|
qhandle_t hModel;
|
2025-01-22 20:52:05 +01:00
|
|
|
char *pszAlliesPartial;
|
|
|
|
char *pszAxisPartial;
|
|
|
|
char szAlliesModel[256];
|
|
|
|
char szAxisModel[256];
|
|
|
|
qboolean isDirty;
|
|
|
|
|
|
|
|
isDirty = dm_playermodel->modified || dm_playergermanmodel->modified || cg_forceModel->modified;
|
|
|
|
|
|
|
|
if (!cg_forceModelAllowed) {
|
|
|
|
if (isDirty) {
|
|
|
|
cgi.Printf(
|
|
|
|
"One or more of the selected players model don't exist on the server or are not loaded, using the "
|
|
|
|
"default skin\n"
|
|
|
|
);
|
|
|
|
}
|
2023-07-21 23:43:57 +02:00
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cg.pAlliedPlayerModel && cg.pAxisPlayerModel && !isDirty) {
|
2023-07-21 23:43:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pszAlliesPartial = dm_playermodel->string;
|
2025-01-22 20:52:05 +01:00
|
|
|
pszAxisPartial = dm_playergermanmodel->string;
|
2023-07-21 23:43:57 +02:00
|
|
|
|
2024-09-20 21:53:48 +02:00
|
|
|
Com_sprintf(szAlliesModel, sizeof(szAlliesModel), "models/player/%s.tik", pszAlliesPartial);
|
|
|
|
Com_sprintf(szAxisModel, sizeof(szAxisModel), "models/player/%s.tik", pszAxisPartial);
|
2025-01-22 20:52:05 +01:00
|
|
|
|
|
|
|
hModel = cg.serverAlliedModelValid ? cgi.R_RegisterModel(szAlliesModel) : 0;
|
|
|
|
if (!hModel) {
|
|
|
|
Com_sprintf(szAlliesModel, sizeof(szAlliesModel), "models/player/%s.tik", dm_playermodel->resetString);
|
|
|
|
hModel = cgi.R_RegisterModel(szAlliesModel);
|
|
|
|
}
|
2023-07-21 23:43:57 +02:00
|
|
|
|
|
|
|
if (hModel) {
|
|
|
|
cg.hAlliedPlayerModelHandle = hModel;
|
2025-01-22 20:52:05 +01:00
|
|
|
cg.pAlliedPlayerModel = cgi.R_Model_GetHandle(hModel);
|
2023-07-21 23:43:57 +02:00
|
|
|
if (!cg.pAlliedPlayerModel) {
|
|
|
|
cg.hAlliedPlayerModelHandle = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cg.hAlliedPlayerModelHandle = 0;
|
2025-01-22 20:52:05 +01:00
|
|
|
cg.pAlliedPlayerModel = NULL;
|
2023-07-21 23:43:57 +02:00
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
hModel = cg.serverAxisModelValid ? cgi.R_RegisterModel(szAxisModel) : 0;
|
|
|
|
if (!hModel) {
|
|
|
|
Com_sprintf(szAxisModel, sizeof(szAxisModel), "models/player/%s.tik", dm_playergermanmodel->resetString);
|
|
|
|
hModel = cgi.R_RegisterModel(szAxisModel);
|
|
|
|
}
|
2023-07-21 23:43:57 +02:00
|
|
|
|
|
|
|
if (hModel) {
|
|
|
|
cg.hAxisPlayerModelHandle = hModel;
|
2025-01-22 20:52:05 +01:00
|
|
|
cg.pAxisPlayerModel = cgi.R_Model_GetHandle(hModel);
|
2023-07-21 23:43:57 +02:00
|
|
|
if (!cg.pAxisPlayerModel) {
|
|
|
|
cg.hAxisPlayerModelHandle = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cg.hAxisPlayerModelHandle = 0;
|
2025-01-22 20:52:05 +01:00
|
|
|
cg.pAxisPlayerModel = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear modified flag
|
|
|
|
//dm_playermodel->modified = qfalse;
|
|
|
|
//dm_playergermanmodel->modified = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_ProcessPlayerModel
|
|
|
|
|
|
|
|
Checks player models, and update force models
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CG_ProcessPlayerModel()
|
|
|
|
{
|
|
|
|
CG_CheckValidModels();
|
|
|
|
if (cg_forceModel->integer) {
|
|
|
|
CG_UpdateForceModels();
|
2023-07-21 23:43:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear modified flag
|
2025-01-22 20:52:05 +01:00
|
|
|
dm_playermodel->modified = qfalse;
|
2023-07-21 23:43:57 +02:00
|
|
|
dm_playergermanmodel->modified = qfalse;
|
2025-01-22 20:52:05 +01:00
|
|
|
cg_forceModel->modified = qfalse;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_ModelAnim
|
|
|
|
===============
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_ModelAnim(centity_t *cent, qboolean bDoShaderTime)
|
2023-04-30 01:42:57 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
entityState_t *s1;
|
|
|
|
entityState_t *sNext = NULL;
|
|
|
|
refEntity_t model;
|
|
|
|
int i;
|
|
|
|
vec3_t vMins, vMaxs, vTmp;
|
|
|
|
const char *szTagName;
|
|
|
|
int iAnimFlags;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if ((cg.snap->ps.pm_flags & PMF_INTERMISSION) && s1->number == cg.snap->ps.clientNum && !cg_3rd_person->integer) {
|
2023-05-01 19:56:43 +02:00
|
|
|
// don't render if in intermission and the client is self without 3rd person
|
|
|
|
return;
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
|
|
|
memset(&model, 0, sizeof(model));
|
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
if (cent->interpolate) {
|
|
|
|
sNext = ¢->nextState;
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
|
|
|
// add loop sound only if it is not attached
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->loopSound && (s1->parent == ENTITYNUM_NONE)) {
|
2023-05-01 19:56:43 +02:00
|
|
|
cgi.S_AddLoopingSound(
|
|
|
|
cent->lerpOrigin,
|
|
|
|
vec3_origin,
|
|
|
|
cgs.sound_precache[s1->loopSound],
|
|
|
|
s1->loopSoundVolume,
|
|
|
|
s1->loopSoundMinDist,
|
|
|
|
s1->loopSoundMaxDist,
|
|
|
|
s1->loopSoundPitch,
|
|
|
|
s1->loopSoundFlags
|
|
|
|
);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cent->tikiLoopSound && (s1->parent == ENTITYNUM_NONE)) {
|
2023-05-01 19:56:43 +02:00
|
|
|
cgi.S_AddLoopingSound(
|
|
|
|
cent->lerpOrigin,
|
|
|
|
vec3_origin,
|
|
|
|
cent->tikiLoopSound,
|
|
|
|
cent->tikiLoopSoundVolume,
|
|
|
|
cent->tikiLoopSoundMinDist,
|
|
|
|
cent->tikiLoopSoundMaxDist,
|
|
|
|
cent->tikiLoopSoundPitch,
|
|
|
|
cent->tikiLoopSoundFlags
|
|
|
|
);
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-07-23 18:19:08 +02:00
|
|
|
if (s1->renderfx & RF_SKYORIGIN) {
|
|
|
|
AnglesToAxis(cent->lerpAngles, cg.sky_axis);
|
|
|
|
VectorCopy(cent->lerpOrigin, cg.sky_origin);
|
|
|
|
}
|
|
|
|
|
2023-04-30 01:42:57 +02:00
|
|
|
// if set to invisible, skip
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!s1->modelindex) {
|
2023-04-30 01:42:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
// set the entity number
|
|
|
|
model.entityNumber = s1->number;
|
|
|
|
|
2023-04-30 01:42:57 +02:00
|
|
|
// take the results of CL_InterpolateEntities
|
|
|
|
VectorCopy(cent->lerpOrigin, model.origin);
|
|
|
|
VectorCopy(cent->lerpOrigin, model.oldorigin);
|
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
IntegerToBoundingBox(s1->solid, vMins, vMaxs);
|
2023-05-02 20:03:45 +02:00
|
|
|
// calculate the light origin
|
|
|
|
VectorAdd(vMins, vMaxs, vTmp);
|
|
|
|
VectorMA(model.origin, 0.5, vTmp, model.lightingOrigin);
|
|
|
|
// calculate the radius
|
2023-05-01 19:56:43 +02:00
|
|
|
VectorSubtract(vMins, vMaxs, vTmp);
|
|
|
|
model.radius = VectorLength(vTmp) * 0.5;
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->number == cg.snap->ps.clientNum) {
|
2023-05-01 19:56:43 +02:00
|
|
|
if (!cg_3rd_person->integer) {
|
|
|
|
PmoveAdjustAngleSettings_Client(
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.refdefViewAngles, cent->lerpAngles, &cg.predicted_player_state, ¢->currentState
|
2023-05-01 19:56:43 +02:00
|
|
|
);
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
model.bone_quat = s1->bone_quat;
|
2023-07-05 21:24:23 +02:00
|
|
|
model.bone_tag = s1->bone_tag;
|
|
|
|
} else {
|
|
|
|
for (i = 0; i < NUM_BONE_CONTROLLERS; i++) {
|
|
|
|
if (s1->bone_tag[i] >= 0) {
|
|
|
|
if ((cent->interpolate) && (cent->nextState.bone_tag[i] == s1->bone_tag[i])) {
|
2023-05-01 19:56:43 +02:00
|
|
|
SlerpQuaternion(
|
2023-07-05 21:24:23 +02:00
|
|
|
s1->bone_quat[i], cent->nextState.bone_quat[i], cg.frameInterpolation, cent->bone_quat[i]
|
2023-05-01 19:56:43 +02:00
|
|
|
);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
cent->bone_quat[i][0] = s1->bone_quat[i][0];
|
|
|
|
cent->bone_quat[i][1] = s1->bone_quat[i][1];
|
|
|
|
cent->bone_quat[i][2] = s1->bone_quat[i][2];
|
|
|
|
cent->bone_quat[i][3] = s1->bone_quat[i][3];
|
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
|
|
|
model.bone_quat = cent->bone_quat;
|
2023-07-05 21:24:23 +02:00
|
|
|
model.bone_tag = s1->bone_tag;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// convert angles to axis
|
|
|
|
AnglesToAxis(cent->lerpAngles, model.axis);
|
|
|
|
|
|
|
|
// copy shader specific data
|
|
|
|
if (s1->shader_data[0]) {
|
|
|
|
model.shader_data[0] = s1->shader_data[0];
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.shader_data[0] = s1->tag_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s1->shader_data[1]) {
|
|
|
|
model.shader_data[1] = s1->shader_data[1];
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.shader_data[1] = s1->skinNum;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bDoShaderTime) {
|
2023-05-01 19:56:43 +02:00
|
|
|
if (cent->interpolate) {
|
2023-07-05 21:24:23 +02:00
|
|
|
model.shaderTime =
|
|
|
|
s1->shader_time + (sNext->shader_time - s1->shader_time) * cg.frameInterpolation + cg.time / 1000.0;
|
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.shaderTime = cg.time / 1000.0 + s1->shader_time;
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Interpolated state variables
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cent->interpolate) {
|
2023-04-30 01:42:57 +02:00
|
|
|
model.scale = s1->scale + cg.frameInterpolation * (cent->nextState.scale - s1->scale);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-04-30 01:42:57 +02:00
|
|
|
model.scale = s1->scale;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hOldModel = 0;
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cgi.R_Model_GetHandle(cgs.model_draw[s1->modelindex]);
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->number != cg.snap->ps.clientNum && (s1->eType == ET_PLAYER || (s1->eFlags & EF_DEAD))) {
|
2025-01-22 20:52:05 +01:00
|
|
|
if (cg_forceModel->integer && cg_forceModelAllowed) {
|
|
|
|
//CG_UpdateForceModels();
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->eFlags & EF_AXIS) {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hModel = cg.hAxisPlayerModelHandle;
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cg.pAxisPlayerModel;
|
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hModel = cg.hAlliedPlayerModelHandle;
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cg.pAlliedPlayerModel;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (model.hModel && model.tiki) {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hOldModel = cgs.model_draw[s1->modelindex];
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
// fallback to non-forced model
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cgi.R_Model_GetHandle(cgs.model_draw[s1->modelindex]);
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hModel = cgs.model_draw[s1->modelindex];
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hModel = cgs.model_draw[s1->modelindex];
|
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!model.hModel || !model.tiki) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// Use a model in case it still doesn't exist
|
|
|
|
if (s1->eFlags & EF_AXIS) {
|
2025-01-22 20:52:05 +01:00
|
|
|
model.hModel = cgi.R_RegisterModel(CG_GetPlayerModelTiki(dm_playergermanmodel->resetString));
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2025-01-22 20:52:05 +01:00
|
|
|
model.hModel = cgi.R_RegisterModel(CG_GetPlayerModelTiki(dm_playermodel->resetString));
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cgi.R_Model_GetHandle(model.hModel);
|
2023-05-02 20:03:45 +02:00
|
|
|
model.hOldModel = cgs.model_draw[s1->modelindex];
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-01 19:56:43 +02:00
|
|
|
model.hModel = cgs.model_draw[s1->modelindex];
|
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (!model.tiki) {
|
|
|
|
// still no model
|
2023-05-01 19:56:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set skin
|
|
|
|
model.skinNum = s1->skinNum;
|
|
|
|
model.renderfx |= s1->renderfx;
|
|
|
|
cgi.TIKI_SetEyeTargetPos(model.tiki, model.entityNumber, s1->eyeVector);
|
|
|
|
|
2024-10-20 22:18:40 +02:00
|
|
|
CG_InterpolateAnimParms(s1, sNext, &model);
|
2023-05-02 20:03:45 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cent->currentState.parent != ENTITYNUM_NONE) {
|
|
|
|
int iTagNum;
|
|
|
|
refEntity_t *parent;
|
|
|
|
dtiki_t *tiki;
|
2023-05-02 20:03:45 +02:00
|
|
|
|
|
|
|
parent = cgi.R_GetRenderEntity(cent->currentState.parent);
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!parent) {
|
2023-05-02 20:03:45 +02:00
|
|
|
if (developer->integer > 1) {
|
|
|
|
cgi.DPrintf("CG_ModelAnim: Could not find parent entity\n");
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
return;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->parent != cg.snap->ps.clientNum || cg_3rd_person->integer) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// attach the model to the world model
|
2023-07-05 21:24:23 +02:00
|
|
|
if (parent->hOldModel) {
|
|
|
|
tiki = cgi.R_Model_GetHandle(parent->hOldModel);
|
2023-05-02 20:03:45 +02:00
|
|
|
szTagName = cgi.Tag_NameForNum(tiki, s1->tag_num & TAG_MASK);
|
2023-07-05 21:24:23 +02:00
|
|
|
tiki = cgi.R_Model_GetHandle(parent->hModel);
|
|
|
|
iTagNum = cgi.Tag_NumForName(tiki, szTagName);
|
|
|
|
} else {
|
|
|
|
tiki = cgi.R_Model_GetHandle(parent->hModel);
|
2023-05-02 20:03:45 +02:00
|
|
|
iTagNum = s1->tag_num;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
CG_AttachEntity(&model, parent, tiki, iTagNum & TAG_MASK, s1->attach_use_angles, s1->attach_offset);
|
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
tiki = cg.pPlayerFPSModel;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// attach to the first person model
|
|
|
|
if (cg.pLastPlayerWorldModel) {
|
|
|
|
szTagName = cgi.Tag_NameForNum(cg.pLastPlayerWorldModel, s1->tag_num & TAG_MASK);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
szTagName = cgi.Tag_NameForNum(tiki, s1->tag_num & TAG_MASK);
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!Q_stricmp(szTagName, "eyes bone")) {
|
2023-05-02 20:03:45 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(tiki, szTagName);
|
2023-07-05 21:24:23 +02:00
|
|
|
CG_AttachEyeEntity(&model, parent, tiki, iTagNum & TAG_MASK, s1->attach_use_angles, s1->attach_offset);
|
|
|
|
} else if (!Q_stricmp(szTagName, "tag_weapon_right") || !Q_stricmp(szTagName, "tag_weapon_left")) {
|
2023-05-02 20:03:45 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(tiki, szTagName);
|
2023-07-05 21:24:23 +02:00
|
|
|
CG_AttachEntity(&model, parent, tiki, iTagNum & TAG_MASK, s1->attach_use_angles, s1->attach_offset);
|
2023-07-21 23:43:57 +02:00
|
|
|
} else {
|
|
|
|
// Don't show the model at all
|
|
|
|
return;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->loopSound) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cgi.S_AddLoopingSound(
|
|
|
|
model.origin,
|
|
|
|
vec3_origin,
|
|
|
|
cgs.sound_precache[s1->loopSound],
|
|
|
|
s1->loopSoundVolume,
|
|
|
|
s1->loopSoundMinDist,
|
|
|
|
s1->loopSoundMaxDist,
|
|
|
|
s1->loopSoundPitch,
|
|
|
|
s1->loopSoundFlags
|
|
|
|
);
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cent->tikiLoopSound) {
|
|
|
|
cgi.S_AddLoopingSound(
|
|
|
|
cent->lerpOrigin,
|
|
|
|
vec3_origin,
|
|
|
|
cent->tikiLoopSound,
|
|
|
|
cent->tikiLoopSoundVolume,
|
|
|
|
cent->tikiLoopSoundMinDist,
|
|
|
|
cent->tikiLoopSoundMaxDist,
|
|
|
|
cent->tikiLoopSoundPitch,
|
|
|
|
cent->tikiLoopSoundFlags
|
|
|
|
);
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// set the attached model to have the same render FX
|
|
|
|
model.renderfx &= ~(RF_THIRD_PERSON | RF_THIRD_PERSON | RF_DEPTHHACK);
|
|
|
|
model.renderfx |= parent->renderfx & (RF_THIRD_PERSON | RF_THIRD_PERSON | RF_DEPTHHACK);
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < 3; i++) {
|
2023-05-02 20:03:45 +02:00
|
|
|
model.shaderRGBA[i] = cent->color[i] * 255;
|
|
|
|
}
|
|
|
|
model.shaderRGBA[3] = s1->alpha * 255;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// set surfaces
|
|
|
|
memcpy(model.surfaces, s1->surfaces, MAX_MODEL_SURFACES);
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!(s1->renderfx & RF_VIEWMODEL) && s1->parent != ENTITYNUM_NONE && s1->parent == cg.snap->ps.clientNum
|
|
|
|
&& ((!cg_drawviewmodel->integer && !cg_3rd_person->integer) || cg.snap->ps.stats[STAT_INZOOM])) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// hide all surfaces while zooming or if the viewmodel shouldn't be shown
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_MODEL_SURFACES; i++) {
|
2023-05-02 20:03:45 +02:00
|
|
|
model.surfaces[i] |= MDL_SURFACE_NODRAW;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(s1->renderfx & RF_DONTDRAW) && (model.renderfx & RF_SHADOW)) {
|
|
|
|
// add the shadow
|
|
|
|
CG_EntityShadow(cent, &model);
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
iAnimFlags = 0;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// combine anim flags from all frame infos
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
if (model.frameInfo[i].weight && model.frameInfo[i].index >= 0) {
|
2023-05-02 20:03:45 +02:00
|
|
|
iAnimFlags |= cgi.Anim_Flags(model.tiki, model.frameInfo[i].index);
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-09 20:36:42 +02:00
|
|
|
if (iAnimFlags & TAF_AUTOSTEPS) {
|
2023-05-02 20:03:45 +02:00
|
|
|
int iTagNum;
|
|
|
|
// Automatically calculate the footsteps sounds
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cent->bFootOnGround_Right) {
|
2023-05-02 20:03:45 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(model.tiki, "Bip01 R Foot");
|
2023-07-05 21:24:23 +02:00
|
|
|
if (iTagNum >= 0) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Right = cgi.TIKI_IsOnGround(&model, iTagNum, 13.653847f);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Right = qtrue;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(model.tiki, "Bip01 R Foot");
|
2023-07-05 21:24:23 +02:00
|
|
|
if (iTagNum >= 0) {
|
|
|
|
if (cgi.TIKI_IsOnGround(&model, iTagNum, 13.461539f)) {
|
2023-05-04 23:26:01 +02:00
|
|
|
CG_Footstep(
|
|
|
|
"Bip01 R Foot",
|
|
|
|
cent,
|
|
|
|
&model,
|
2023-07-09 20:36:42 +02:00
|
|
|
(iAnimFlags & TAF_AUTOSTEPS_RUNNING),
|
|
|
|
(iAnimFlags & TAF_AUTOSTEPS_EQUIPMENT)
|
2023-05-04 23:26:01 +02:00
|
|
|
);
|
2023-05-01 19:56:43 +02:00
|
|
|
cent->bFootOnGround_Right = qtrue;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Right = qtrue;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cent->bFootOnGround_Left) {
|
2023-05-04 23:26:01 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(model.tiki, "Bip01 L Foot");
|
2023-07-05 21:24:23 +02:00
|
|
|
if (iTagNum >= 0) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Left = cgi.TIKI_IsOnGround(&model, iTagNum, 13.653847f);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Left = qtrue;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-04 23:26:01 +02:00
|
|
|
iTagNum = cgi.Tag_NumForName(model.tiki, "Bip01 L Foot");
|
2023-07-05 21:24:23 +02:00
|
|
|
if (iTagNum >= 0) {
|
|
|
|
if (cgi.TIKI_IsOnGround(&model, iTagNum, 13.461539f)) {
|
2023-05-04 23:26:01 +02:00
|
|
|
CG_Footstep(
|
|
|
|
"Bip01 L Foot",
|
|
|
|
cent,
|
|
|
|
&model,
|
2023-07-09 20:36:42 +02:00
|
|
|
(iAnimFlags & TAF_AUTOSTEPS_RUNNING),
|
|
|
|
(iAnimFlags & TAF_AUTOSTEPS_EQUIPMENT)
|
2023-05-04 23:26:01 +02:00
|
|
|
);
|
|
|
|
|
2023-05-01 19:56:43 +02:00
|
|
|
cent->bFootOnGround_Left = qtrue;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Left = qtrue;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
|
|
|
cent->bFootOnGround_Left = qtrue;
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->bFootOnGround_Right = qtrue;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cent->currentState.eType == ET_PLAYER && !(cent->currentState.eFlags & EF_DEAD)) {
|
2023-05-29 15:25:04 +02:00
|
|
|
CG_PlayerTeamIcon(&model, ¢->currentState);
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->number == cg.snap->ps.clientNum) {
|
|
|
|
if ((!cg.bFPSModelLastFrame && !cg_3rd_person->integer) || (cg.bFPSModelLastFrame && cg_3rd_person->integer)) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// reset the animations when toggling 3rd person
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->animLast[i] = -1;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->animLastWeight = 0;
|
|
|
|
cent->usageIndexLast = 0;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.bFPSModelLastFrame = !cg_3rd_person->integer;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// player footsteps, walking/falling
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.bFPSOnGround != cg.predicted_player_state.walking) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.bFPSOnGround = cg.predicted_player_state.walking;
|
|
|
|
if (cg.predicted_player_state.walking) {
|
|
|
|
CG_LandingSound(cent, &model, 1.0, 1);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cent->iNextLandTime < cg.time) {
|
|
|
|
CG_Footstep(0, cent, &model, 1, 1);
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
|
|
|
|
cent->iNextLandTime = cg.time + 200;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!cg_3rd_person->integer) {
|
2023-05-01 19:56:43 +02:00
|
|
|
// first person view
|
2023-05-02 20:03:45 +02:00
|
|
|
|
|
|
|
if (!(cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW)
|
2023-07-05 21:24:23 +02:00
|
|
|
&& (cg.snap->ps.stats[STAT_HEALTH] <= 0 || cg_animationviewmodel->integer)) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// use world position for this case
|
|
|
|
CG_OffsetFirstPersonView(&model, qtrue);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!cg.pLastPlayerWorldModel || cg.pLastPlayerWorldModel != model.tiki) {
|
2023-05-02 20:03:45 +02:00
|
|
|
qhandle_t hModel;
|
2023-07-05 21:24:23 +02:00
|
|
|
char fpsname[128];
|
2024-12-30 20:24:49 +01:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
COM_StripExtension(model.tiki->a->name, fpsname, sizeof(fpsname));
|
2024-12-30 20:24:49 +01:00
|
|
|
Q_strcat(fpsname, sizeof(fpsname), "_fps.tik");
|
2023-05-02 20:03:45 +02:00
|
|
|
|
|
|
|
hModel = cgi.R_RegisterModel(fpsname);
|
2023-07-05 21:24:23 +02:00
|
|
|
if (hModel) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.hPlayerFPSModelHandle = hModel;
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.pPlayerFPSModel = cgi.R_Model_GetHandle(hModel);
|
2023-05-02 20:03:45 +02:00
|
|
|
if (!cg.pPlayerFPSModel) {
|
|
|
|
cg.pPlayerFPSModel = model.tiki;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cg.snap->ps.stats[STAT_TEAM] == TEAM_AXIS) {
|
2025-01-22 20:52:05 +01:00
|
|
|
hModel = cgi.R_RegisterModel(CG_GetPlayerLocalModelTiki(dm_playergermanmodel->resetString));
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2025-01-22 20:52:05 +01:00
|
|
|
hModel = cgi.R_RegisterModel(CG_GetPlayerLocalModelTiki(dm_playermodel->resetString));
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (hModel) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.hPlayerFPSModelHandle = hModel;
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.pPlayerFPSModel = cgi.R_Model_GetHandle(hModel);
|
2023-05-02 20:03:45 +02:00
|
|
|
|
|
|
|
if (!cg.pPlayerFPSModel) {
|
|
|
|
cg.pPlayerFPSModel = model.tiki;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.hPlayerFPSModelHandle = cgs.model_draw[s1->modelindex];
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.pPlayerFPSModel = model.tiki;
|
2023-05-02 20:03:45 +02:00
|
|
|
}
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
cg.pLastPlayerWorldModel = model.tiki;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
model.tiki = cg.pPlayerFPSModel;
|
2023-05-02 20:03:45 +02:00
|
|
|
model.hModel = cg.hPlayerFPSModelHandle;
|
|
|
|
memset(model.surfaces, 0, sizeof(model.surfaces));
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
CG_ViewModelAnimation(&model);
|
2024-10-24 21:08:04 +02:00
|
|
|
model.renderfx |= RF_FRAMELERP;
|
2023-05-02 20:03:45 +02:00
|
|
|
cgi.ForceUpdatePose(&model);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if ((cent->currentState.eFlags & EF_UNARMED) || cg_drawviewmodel->integer <= 1
|
|
|
|
|| cg.snap->ps.stats[STAT_INZOOM] || cg.snap->ps.stats[STAT_HEALTH] <= 0) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// unarmed or zooming, hide the arms
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_MODEL_SURFACES; i++) {
|
2023-05-02 20:03:45 +02:00
|
|
|
model.surfaces[i] |= MDL_SURFACE_NODRAW;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
// show/hide the garand hand depending if it's a rifle or not
|
|
|
|
// so the hand can hold the rifle correctly
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
const char *weaponstring = "";
|
|
|
|
int iSurfaceNum;
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cg.snap->ps.activeItems[1] >= 0) {
|
|
|
|
weaponstring = CG_ConfigString(CS_WEAPONS + cg.snap->ps.activeItems[1]);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!Q_stricmp(weaponstring, "M1 Garand") || !Q_stricmp(weaponstring, "Springfield '03 Sniper")
|
|
|
|
|| !Q_stricmp(weaponstring, "Mauser KAR 98K") || !Q_stricmp(weaponstring, "KAR98 - Sniper")) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// show the garand hands
|
|
|
|
|
|
|
|
iSurfaceNum = cgi.Surface_NameToNum(model.tiki, "lefthand");
|
|
|
|
if (iSurfaceNum >= 0) {
|
|
|
|
model.surfaces[iSurfaceNum] |= MDL_SURFACE_NODRAW;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
iSurfaceNum = cgi.Surface_NameToNum(model.tiki, "garandhand");
|
|
|
|
if (iSurfaceNum >= 0) {
|
|
|
|
model.surfaces[iSurfaceNum] &= ~MDL_SURFACE_NODRAW;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
// hide the garand hands
|
2023-05-01 19:56:43 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
iSurfaceNum = cgi.Surface_NameToNum(model.tiki, "garandhand");
|
|
|
|
if (iSurfaceNum >= 0) {
|
|
|
|
model.surfaces[iSurfaceNum] |= MDL_SURFACE_NODRAW;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
iSurfaceNum = cgi.Surface_NameToNum(model.tiki, "lefthand");
|
|
|
|
if (iSurfaceNum >= 0) {
|
|
|
|
model.surfaces[iSurfaceNum] &= ~MDL_SURFACE_NODRAW;
|
|
|
|
}
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (!(s1->eFlags & EF_CLIMBWALL)) {
|
|
|
|
// when the player is not climbing ladders show the entity
|
|
|
|
model.renderfx |= RF_DEPTHHACK;
|
2023-05-01 19:56:43 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!(cg.predicted_player_state.pm_flags & PMF_CAMERA_VIEW)) {
|
2023-05-02 20:03:45 +02:00
|
|
|
if (cg.snap->ps.stats[STAT_HEALTH] > 0 && !cg_animationviewmodel->integer) {
|
|
|
|
CG_OffsetFirstPersonView(&model, qfalse);
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
AnglesToAxis(cg.refdefViewAngles, cg.refdef.viewaxis);
|
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
model.renderfx &= ~(RF_FIRST_PERSON | RF_THIRD_PERSON);
|
|
|
|
// set the first person render flag
|
|
|
|
model.renderfx |= RF_FIRST_PERSON;
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-05-19 16:59:44 +02:00
|
|
|
model.reType = RT_MODEL;
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!(s1->renderfx & RF_DONTDRAW)) {
|
2023-05-02 20:03:45 +02:00
|
|
|
cgi.R_Model_GetHandle(model.hModel);
|
2023-07-05 21:24:23 +02:00
|
|
|
if (VectorCompare(model.origin, vec3_origin)) {
|
2023-05-03 20:10:52 +02:00
|
|
|
VectorCopy(s1->origin, model.origin);
|
2023-05-02 20:03:45 +02:00
|
|
|
AngleVectors(s1->angles, model.axis[0], model.axis[1], model.axis[2]);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
// add to refresh list
|
|
|
|
cgi.R_AddRefEntityToScene(&model, s1->parent);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
CG_UpdateEntityEmitters(s1->number, &model, cent);
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (s1->usageIndex == cent->usageIndexLast) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// process the exit commands of the last animations
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
if ((cent->animLastWeight >> i) & 1) {
|
|
|
|
if (!model.frameInfo[i].weight || model.frameInfo[i].index != cent->animLast[i]) {
|
2023-05-02 20:03:45 +02:00
|
|
|
CG_ProcessEntityCommands(TIKI_FRAME_EXIT, cent->animLast[i], s1->number, &model, cent);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++) {
|
2023-05-02 20:03:45 +02:00
|
|
|
// process the entry commands of the current anim
|
2023-07-05 21:24:23 +02:00
|
|
|
if (model.frameInfo[i].weight) {
|
|
|
|
if (!((cent->animLastWeight >> i) & 1) || model.frameInfo[i].index != cent->animLast[i]) {
|
2023-05-02 20:03:45 +02:00
|
|
|
CG_ProcessEntityCommands(TIKI_FRAME_ENTRY, model.frameInfo[i].index, s1->number, &model, cent);
|
2024-08-05 23:41:17 +02:00
|
|
|
if (cent->animLastTimes[i] == -1) {
|
2025-01-22 20:52:05 +01:00
|
|
|
cent->animLast[i] = model.frameInfo[i].index;
|
2024-08-05 23:41:17 +02:00
|
|
|
cent->animLastTimes[i] = model.frameInfo[i].time;
|
|
|
|
} else {
|
|
|
|
cent->animLastTimes[i] = 0;
|
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
CG_ClientCommands(&model, cent, i);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->animLastTimes[i] = model.frameInfo[i].time;
|
2023-07-05 21:24:23 +02:00
|
|
|
cent->animLast[i] = model.frameInfo[i].index;
|
2023-04-30 01:42:57 +02:00
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
if (model.frameInfo[i].weight) {
|
|
|
|
cent->animLastWeight |= 1 << i;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->animLastWeight &= ~(1 << i);
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 20:03:45 +02:00
|
|
|
cent->usageIndexLast = cent->currentState.usageIndex;
|
2023-04-30 01:42:57 +02:00
|
|
|
}
|