openmohaa/code/cgame/cg_ents.c

650 lines
18 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"
/*
==========================================================================
FUNCTIONS CALLED EACH FRAME
==========================================================================
*/
/*
======================
CG_SetEntitySoundPosition
======================
*/
2023-04-30 00:02:16 +02:00
void CG_SetEntitySoundPosition( centity_t *cent )
{
vec3_t origin;
if ( cent->currentState.solid == SOLID_BMODEL )
{
float *v;
vec3_t vel;
2016-03-27 11:49:47 +02:00
v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
VectorAdd( cent->lerpOrigin, v, origin );
2023-04-30 00:02:16 +02:00
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-04-30 00:02:16 +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;
2016-03-27 11:49:47 +02:00
cl = cent->currentState.constantLight;
2023-04-30 00:02:16 +02:00
style = ( cl & 255 );
r = ( float )style / 255.0f;
g = ( float )( ( cl >> 8 ) & 255 ) / 255.0f;
b = ( float )( ( cl >> 16 ) & 255 ) / 255.0f;
2016-03-27 11:49:47 +02:00
i = ( ( cl >> 24 ) & 255 ) * CONSTANTLIGHT_RADIUS_SCALE;
2023-04-30 00:02:16 +02:00
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;
}
}
2016-03-27 11:49:47 +02:00
/*
==================
CG_General
==================
*/
2023-04-30 00:02:16 +02:00
void CG_General( centity_t *cent ) {
2016-03-27 11:49:47 +02:00
refEntity_t ent;
entityState_t *s1;
2023-04-30 01:42:57 +02:00
int i;
vec3_t vMins, vMaxs, vTmp;
2016-03-27 11:49:47 +02:00
s1 = &cent->currentState;
2023-04-30 00:02:16 +02:00
// add loop sound
if ( s1->loopSound )
{
2023-04-30 01:42:57 +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 00:02:16 +02:00
}
if ( cent->tikiLoopSound )
2023-04-30 01:42:57 +02:00
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
2016-03-27 11:49:47 +02:00
// if set to invisible, skip
if (!s1->modelindex) {
return;
}
memset (&ent, 0, sizeof(ent));
2023-04-30 00:02:16 +02:00
// set frame
2023-04-30 01:42:57 +02:00
ent.wasframe = s1->wasframe;
2023-04-30 00:02:16 +02:00
2016-03-27 11:49:47 +02:00
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
2023-04-30 00:02:16 +02:00
// set skin
2023-04-30 01:42:57 +02:00
IntegerToBoundingBox(s1->solid, vMins, vMaxs);
VectorSubtract(vMins, vMaxs, vTmp);
ent.lightingOrigin[0] = ent.origin[0] + (vMins[0] + vMaxs[0]) * 0.5;
ent.lightingOrigin[1] = ent.origin[1] + (vMins[1] + vMaxs[1]) * 0.5;
ent.lightingOrigin[2] = ent.origin[2] + (vMins[2] + vMaxs[2]) * 0.5;
ent.radius = VectorLength(vTmp) * 0.5;
ent.skinNum = s1->skinNum;
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
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;
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
// 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
2016-03-27 11:49:47 +02:00
AnglesToAxis( cent->lerpAngles, ent.axis );
2023-04-30 00:02:16 +02:00
// 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;
if ( ent.renderfx & RF_SKYORIGIN )
{
memcpy( cg.sky_axis, ent.axis, sizeof( cg.sky_axis ) );
VectorCopy( ent.origin, cg.sky_origin );
}
2023-04-30 01:42:57 +02:00
ent.tiki = cgi.R_Model_GetHandle(cgs.model_draw[s1->modelindex]);
ent.frameInfo[0].index = s1->frameInfo[0].index;
ent.frameInfo[0].time = s1->frameInfo[0].time;
ent.frameInfo[0].weight = s1->frameInfo[0].weight;
ent.actionWeight = 1.0;
2023-04-30 00:02:16 +02:00
2016-03-27 11:49:47 +02:00
// add to refresh list
2023-04-30 00:02:16 +02:00
cgi.R_AddRefEntityToScene (&ent);
2023-04-30 01:42:57 +02:00
if (ent.tiki >= 0)
{
// 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
==================
*/
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;
2023-04-30 01:42:57 +02:00
cent->miscTime = cg.time + cent->currentState.wasframe * 100 + cent->currentState.clientNum * 100 * crandom();
2023-04-30 00:02:16 +02:00
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_Mover
===============
*/
2023-04-30 00:02:16 +02:00
void CG_Mover( centity_t *cent ) {
2016-03-27 11:49:47 +02:00
refEntity_t ent;
entityState_t *s1;
s1 = &cent->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 ) {
2023-04-30 00:02:16 +02:00
ent.hModel = cgs.inlineDrawModel[s1->modelindex];
2016-03-27 11:49:47 +02:00
} else {
2023-04-30 00:02:16 +02:00
ent.hModel = cgs.model_draw[s1->modelindex];
2016-03-27 11:49:47 +02:00
}
// add to refresh list
cgi.R_AddRefEntityToScene(&ent);
}
2023-04-30 00:02:16 +02:00
/*
===============
CG_Beam
===============
*/
void CG_Beam( centity_t *cent ) {
2016-03-27 11:49:47 +02:00
entityState_t *s1;
2023-04-30 00:02:16 +02:00
vec3_t vz={0,0,0},origin={0,0,0};
byte modulate[4];
int i;
2016-03-27 11:49:47 +02:00
s1 = &cent->currentState;
2023-04-30 00:02:16 +02:00
for ( i=0;i<4;i++ )
modulate[i] = cent->color[i] * 255;
2016-03-27 11:49:47 +02:00
2023-04-30 01:42:57 +02:00
if ( s1->beam_entnum != ENTITYNUM_NONE )
2023-04-30 00:02:16 +02:00
{
refEntity_t *parent;
2023-04-30 01:42:57 +02:00
parent = cgi.R_GetRenderEntity( s1->beam_entnum);
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
if ( !parent )
{
cgi.DPrintf( "CG_Beam: Could not find parent entity\n" );
return;
}
VectorAdd( s1->origin, parent->origin, origin );
}
else
{
VectorCopy( s1->origin, origin );
}
2016-03-27 11:49:47 +02:00
2023-04-30 00:02:16 +02:00
CG_CreateBeam( origin, // start
2016-03-27 11:49:47 +02:00
vz, // dir ( auto calculated by using origin2-origin )
s1->number, // owner number
2023-04-30 00:02:16 +02:00
cgs.model_draw[s1->modelindex], //hModel
2016-03-27 11:49:47 +02:00
s1->alpha, // alpha
s1->scale, // scale
s1->skinNum, // flags
0, // length ( auto calculated )
2023-04-30 00:02:16 +02:00
PKT_TO_BEAM_PARM( s1->surfaces[0] ) * 1000, // life
2016-03-27 11:49:47 +02:00
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
2023-04-30 00:02:16 +02:00
PKT_TO_BEAM_PARM( s1->surfaces[3] ), // overlap
2016-03-27 11:49:47 +02:00
s1->surfaces[4], // subdivisions
2023-04-30 00:02:16 +02:00
PKT_TO_BEAM_PARM( s1->surfaces[5] ) * 1000, // delay
2016-03-27 11:49:47 +02:00
CG_ConfigString( CS_IMAGES + s1->tag_num ), // index for shader configstring
modulate, // modulate color
s1->surfaces[6], // num sphere beams
2023-04-30 00:02:16 +02:00
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
2016-03-27 11:49:47 +02:00
s1->renderfx,
""
2023-04-30 00:02:16 +02:00
);
2016-03-27 11:49:47 +02:00
}
2023-04-30 00:02:16 +02:00
void CG_Decal
(
centity_t *cent
)
{
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],
cent->color[0],
cent->color[1],
cent->color[2],
cent->color[3],
qtrue,
s1->scale,
qfalse,
-1,
qfalse);
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_Portal
===============
*/
2023-04-30 01:42:57 +02:00
void CG_Portal(centity_t* cent)
{
2016-03-27 11:49:47 +02:00
}
/*
===============
CG_CalcEntityLerpPositions
===============
*/
2023-04-30 01:42:57 +02:00
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);
}
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++) {
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(&cent->currentState.pos, cg.snap->serverTime, current);
BG_EvaluateTrajectory(&cent->nextState.pos, cg.nextSnap->serverTime, 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);
}
// Lerp legs, torso, and head angles
for (i = 0; i < 3; i++) {
cent->lerpAngles[i] = LerpAngle(current[i], next[i], f);
}
}
else {
// just use the current frame and evaluate as best we can
BG_EvaluateTrajectory(&cent->currentState.pos, cg.time, cent->lerpOrigin);
VectorCopy(cent->currentState.angles, cent->lerpAngles);
//BG_EvaluateTrajectory(&cent->currentState.apos, cg.time, cent->lerpAngles);
}
2016-03-27 11:49:47 +02:00
}
/*
===============
CG_AddCEntity
===============
*/
2023-04-30 20:36:40 +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;
case ET_PLAYER:
CG_Player(cent);
// intentional fallthrough
case ET_MODELANIM:
CG_Splash(cent);
CG_ModelAnim(cent, qfalse);
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_ROPE: // skip
CG_Rope(cent);
break;
case ET_MULTIBEAM: // skip
break;
case ET_PORTAL:
CG_Portal(cent);
break;
case ET_RAIN:
case ET_EMITTER:
CG_Emitter(cent);
break;
case ET_DECAL:
CG_Decal(cent);
break;
}
}
2016-03-27 11:49:47 +02:00
/*
===============
CG_AddPacketEntities
===============
*/
void CG_AddPacketEntities( void ) {
int num;
centity_t *cent;
2023-04-30 00:02:16 +02:00
// playerState_t *ps;
2016-03-27 11:49:47 +02:00
// 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;
2023-04-30 00:02:16 +02:00
cg.autoAnglesSlow[0] = 0;
cg.autoAnglesSlow[1] = ( cg.time & 4095 ) * 360 / 4096.0f;
cg.autoAnglesSlow[2] = 0;
2016-03-27 11:49:47 +02:00
cg.autoAnglesFast[0] = 0;
cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f;
cg.autoAnglesFast[2] = 0;
AnglesToAxis( cg.autoAngles, cg.autoAxis );
2023-04-30 00:02:16 +02:00
AnglesToAxis( cg.autoAnglesSlow, cg.autoAxisSlow );
2016-03-27 11:49:47 +02:00
AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
// generate and add the entity from the playerstate
2023-04-30 00:02:16 +02:00
//ps = &cg.predicted_player_state;
//PlayerStateToEntityState( ps, &cg_entities[ ps->clientNum ].currentState );
//CG_AddCEntity( &cg_entities[ ps->clientNum ] );
2016-03-27 11:49:47 +02:00
// add each entity sent over by the server
2023-04-30 00:02:16 +02:00
for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
2016-03-27 11:49:47 +02:00
cent = &cg_entities[ cg.snap->entities[ num ].number ];
CG_AddCEntity( cent );
}
2023-04-30 00:02:16 +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-04-30 01:42:57 +02:00
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)
{
cgi.DPrintf("CG_GetOrigin: Could not find parent entity\n");
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);
}
}
}