openmohaa/code/cgame/cg_tempmodels.cpp

1602 lines
50 KiB
C++
Raw Permalink Normal View History

2023-04-30 14:15:14 +02:00
/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// DESCRIPTION:
// Temporary models effects
#include "cg_local.h"
#include "cg_commands.h"
#include "tiki.h"
2023-07-05 21:24:23 +02:00
cvar_t *cg_showtempmodels;
cvar_t *cg_max_tempmodels;
cvar_t *cg_reserve_tempmodels;
2023-07-05 21:24:23 +02:00
cvar_t *cg_detail;
cvar_t *cg_effectdetail;
cvar_t *cg_effect_physicsrate;
2023-07-05 21:24:23 +02:00
extern refEntity_t *current_entity;
extern int current_entity_number;
extern centity_t *current_centity;
extern float current_scale;
extern dtiki_t *current_tiki;
extern Event EV_Client_Swipe;
extern Event EV_Client_SwipeOn;
extern Event EV_Client_SwipeOff;
2023-07-05 21:24:23 +02:00
//=============
// AllocateTempModel
//=============
2023-07-05 21:24:23 +02:00
ctempmodel_t *ClientGameCommandManager::AllocateTempModel(void)
{
2023-07-05 21:24:23 +02:00
ctempmodel_t *p;
p = m_free_tempmodels;
if (!p) {
// no free entities
return NULL;
}
m_free_tempmodels = m_free_tempmodels->next;
// link into the active list
2023-07-05 21:24:23 +02:00
p->next = m_active_tempmodels.next;
p->prev = &m_active_tempmodels;
m_active_tempmodels.next->prev = p;
2023-07-05 21:24:23 +02:00
m_active_tempmodels.next = p;
return p;
}
//===============
// FreeTempModel
//===============
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::FreeTempModel(ctempmodel_t *p)
{
if (!p->prev) {
cgi.Error(ERR_DROP, "CCM::FreeTempModel: not active");
}
RemoveClientEntity(p->number, p->cgd.tiki, NULL, p);
if (m_active_tempmodels.next == p) {
// use the next active temp model
m_active_tempmodels.next = p->next;
}
// remove from the doubly linked active list
p->prev->next = p->next;
p->next->prev = p->prev;
// the free list is only singly linked
2023-07-05 21:24:23 +02:00
p->next = m_free_tempmodels;
m_free_tempmodels = p;
if (p->m_spawnthing) {
p->m_spawnthing->numtempmodels--;
// delete unused spawnthings
if (!p->m_spawnthing->numtempmodels) {
m_emitters.RemoveObject(p->m_spawnthing);
if (p->m_spawnthing == m_spawnthing) {
m_spawnthing = NULL;
}
delete p->m_spawnthing;
}
p->m_spawnthing = NULL;
}
}
//===============
// FreeAllTempModels
//===============
void ClientGameCommandManager::FreeAllTempModels(void)
{
ctempmodel_t *p, *next;
// Go through all the temp models and run the physics if necessary,
// then add them to the ref
p = m_active_tempmodels.prev;
for (; p != &m_active_tempmodels; p = next) {
// grab next now, so if the local entity is freed we still have it
next = p->prev;
FreeTempModel(p);
}
}
//===============
// FreeSomeTempModels
//===============
void ClientGameCommandManager::FreeSomeTempModels(void)
{
ctempmodel_t *model;
int count = 0;
unsigned int i;
unsigned int numToFree;
if (!m_free_tempmodels) {
return;
}
for (model = m_active_tempmodels.prev; model != &m_active_tempmodels; model = model->prev) {
count++;
}
if (cg_reserve_tempmodels->integer <= (cg_max_tempmodels->integer - count)) {
// nothing to free
return;
}
numToFree = cg_reserve_tempmodels->integer - (cg_max_tempmodels->integer - count);
for (i = 0; i < numToFree; i++) {
FreeTempModel(m_active_tempmodels.prev);
}
}
//===============
// FreeSpawnthing
//===============
void ClientGameCommandManager::FreeSpawnthing(spawnthing_t *sp)
{
ctempmodel_t *model;
ctempmodel_t *prev;
if (sp->numtempmodels) {
for (model = m_active_tempmodels.prev; model != &m_active_tempmodels; model = prev) {
prev = model->prev;
if (model->m_spawnthing == sp) {
FreeTempModel(model);
}
}
} else {
m_emitters.RemoveObject(sp);
if (sp == m_spawnthing) {
m_spawnthing = NULL;
}
delete sp;
}
}
//===============
// ResetTempModels
//===============
void ClientGameCommandManager::ResetTempModels(void)
{
// Go through all the active tempmodels and free them
2023-07-05 21:24:23 +02:00
ctempmodel_t *p, *next;
p = m_active_tempmodels.prev;
for (; p != &m_active_tempmodels; p = next) {
next = p->prev;
FreeTempModel(p);
}
}
static int lastTempModelFrameTime = 0;
int next_tempmodel_warning = 0;
void CG_ResetTempModels(void)
{
commandManager.ResetTempModels();
lastTempModelFrameTime = cg.time;
}
//=============
// InitializeTempModels
//=============
void ClientGameCommandManager::InitializeTempModels(void)
{
int i;
int numtempmodels = MAX_TEMPMODELS;
m_active_tempmodels.next = &m_active_tempmodels;
m_active_tempmodels.prev = &m_active_tempmodels;
m_free_tempmodels = &m_tempmodels[0];
for (i = 0; i < numtempmodels - 1; i++) {
m_tempmodels[i].next = &m_tempmodels[i + 1];
}
m_tempmodels[numtempmodels - 1].next = NULL;
}
void ClientGameCommandManager::InitializeTempModelCvars(void)
{
cg_showtempmodels = cgi.Cvar_Get("cg_showtempmodels", "0", 0);
cg_detail = cgi.Cvar_Get("detail", "1", CVAR_ARCHIVE);
cg_effectdetail = cgi.Cvar_Get("cg_effectdetail", "0.2", CVAR_ARCHIVE);
cgi.Cvar_CheckRange(cg_effectdetail, 0.2, 1.0, qfalse);
cg_effect_physicsrate = cgi.Cvar_Get("cg_effect_physicsrate", "10", CVAR_ARCHIVE);
cg_max_tempmodels = cgi.Cvar_Get("cg_max_tempmodels", "1100", CVAR_ARCHIVE);
cgi.Cvar_CheckRange(cg_max_tempmodels, 200, MAX_TEMPMODELS, qtrue);
cg_reserve_tempmodels = cgi.Cvar_Get("cg_reserve_tempmodels", "200", CVAR_ARCHIVE);
if (cg_max_tempmodels->integer > MAX_TEMPMODELS) {
// 2.40 sets the integer value directly rather than calling Cvar_Set()
//cg_max_tempmodels->integer = MAX_TEMPMODELS
cgi.Cvar_Set("cg_max_tempmodels", va("%i", MAX_TEMPMODELS));
}
if (cg_reserve_tempmodels->integer * 5 > cg_max_tempmodels->integer) {
// 2.40 sets the integer value directly rather than calling Cvar_Set()
//cg_reserve_tempmodels->integer = cg_max_tempmodels->integer / 5;
cgi.Cvar_Set("cg_reserve_tempmodels", va("%i", cg_max_tempmodels->integer / 5));
}
}
//===============
// AnimateTempModel - animate temp models
//===============
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::AnimateTempModel(ctempmodel_t *p, Vector origin, refEntity_t *newEnt)
{
int numframes;
int deltatime;
int frametime;
2023-10-02 13:41:00 +02:00
float prev;
// This code is for animating tempmodels that are spawned from the client
// side
if (!p->cgd.tiki) {
return;
}
// Calc frame stuff
frametime = 1000.0f * cgi.Anim_Frametime(p->cgd.tiki, p->ent.frameInfo[0].index);
deltatime = cg.time - p->lastAnimTime;
numframes = cgi.Anim_NumFrames(p->cgd.tiki, p->ent.frameInfo[0].index);
if (!p->addedOnce) {
// Process entry commands
2023-07-05 21:24:23 +02:00
CG_ProcessEntityCommands(TIKI_FRAME_ENTRY, p->ent.frameInfo[0].index, -1, &p->ent, NULL);
}
if (numframes < 2) {
return;
}
// Go through all the frames, and process any commands associated with the
// tempmodel as well
2023-10-02 13:41:00 +02:00
prev = deltatime;
while (deltatime >= frametime) {
2023-10-02 13:41:00 +02:00
deltatime -= frametime;
p->lastAnimTime += frametime;
p->ent.wasframe = (p->ent.wasframe + 1) % numframes;
2023-07-05 21:24:23 +02:00
CG_ProcessEntityCommands(p->ent.wasframe, p->ent.frameInfo[0].index, -1, &p->ent, NULL);
2023-10-02 13:41:00 +02:00
if (deltatime == prev) {
break;
}
prev = deltatime;
}
}
//===============
// UpdateSwarm
//===============
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::UpdateSwarm(ctempmodel_t *p)
{
if (p->cgd.swarmfreq == 0) {
return;
}
// If the frequency is hit, set a new velocity
if (!(rand() % p->cgd.swarmfreq)) {
p->cgd.velocity.x = crandom() * p->cgd.swarmmaxspeed;
p->cgd.velocity.y = crandom() * p->cgd.swarmmaxspeed;
p->cgd.velocity.z = crandom() * p->cgd.swarmmaxspeed;
}
// Try to move toward the origin by the specified delta
if (p->cgd.origin.x < p->cgd.parentOrigin.x) {
p->cgd.velocity.x += p->cgd.swarmdelta;
} else {
p->cgd.velocity.x -= p->cgd.swarmdelta;
}
if (p->cgd.origin.y < p->cgd.parentOrigin.y) {
p->cgd.velocity.y += p->cgd.swarmdelta;
} else {
p->cgd.velocity.y -= p->cgd.swarmdelta;
}
if (p->cgd.origin.z < p->cgd.parentOrigin.z) {
p->cgd.velocity.z += p->cgd.swarmdelta;
} else {
p->cgd.velocity.z -= p->cgd.swarmdelta;
}
}
2023-10-02 13:41:00 +02:00
qboolean ClientGameCommandManager::TempModelRealtimeEffects(ctempmodel_t *p, float ftime, float scale)
{
float fade, fadein;
2023-10-02 13:41:00 +02:00
float dtime;
2023-07-05 21:24:23 +02:00
byte tempColor[4];
if (p->cgd.flags & (T_FADE | T_SCALEUPDOWN)) {
2023-07-05 21:24:23 +02:00
fade = 1.0f - (float)(p->aliveTime - p->cgd.fadedelay) / (float)(p->cgd.life - p->cgd.fadedelay);
// Clamp the fade
if (fade > 1) {
fade = 1;
}
if (fade < 0) {
fade = 0;
}
2023-07-05 21:24:23 +02:00
} else {
fade = 1.0f;
}
dtime = (cg.time - p->cgd.createTime);
// Calculate fade in value
if (p->cgd.flags & T_FADEIN) {
fadein = dtime / (float)p->cgd.fadeintime;
2023-07-05 21:24:23 +02:00
} else {
fadein = 0;
}
// Convert dtime to seconds
dtime *= 0.001f;
// Do the scale animation
if (ftime && p->cgd.scaleRate) {
p->ent.scale += p->cgd.scale * (p->cgd.scaleRate * ftime);
2023-07-05 21:24:23 +02:00
} else if (p->cgd.flags & T_DLIGHT) {
p->cgd.lightIntensity += p->cgd.scaleRate * ftime * p->cgd.lightIntensity;
if (p->cgd.lightIntensity < 0.0f) {
return qfalse;
}
}
if (p->cgd.flags & T_SCALEUPDOWN) {
p->ent.scale = p->cgd.scale * sin((fade)*M_PI);
if (p->ent.scale < p->cgd.scalemin) {
p->ent.scale = p->cgd.scalemin;
}
if (p->ent.scale > p->cgd.scalemax) {
p->ent.scale = p->cgd.scalemax;
}
}
if (p->cgd.lightstyle >= 0) {
2023-07-05 21:24:23 +02:00
int i;
float color[4];
CG_LightStyleColor(p->cgd.lightstyle, dtime * 1000, color);
for (i = 0; i < 4; i++) {
tempColor[i] = (byte)(color[i] * 255.0f);
}
2023-07-05 21:24:23 +02:00
} else {
if (p->cgd.flags2 & T2_COLOR_AVEL) {
p->cgd.color[0] += p->cgd.avelocity.x * ftime;
p->cgd.color[1] += p->cgd.avelocity.y * ftime;
p->cgd.color[2] += p->cgd.avelocity.z * ftime;
2023-07-05 21:24:23 +02:00
if (p->cgd.color[0] < 0.0f) {
p->cgd.color[0] = 0.0f;
}
if (p->cgd.color[1] < 0.0f) {
p->cgd.color[1] = 0.0f;
}
if (p->cgd.color[2] < 0.0f) {
p->cgd.color[2] = 0.0f;
}
}
tempColor[0] = (int)(p->cgd.color[0] * 255.0f);
tempColor[1] = (int)(p->cgd.color[1] * 255.0f);
tempColor[2] = (int)(p->cgd.color[2] * 255.0f);
tempColor[3] = (int)(p->cgd.color[3] * 255.0f);
}
if (p->cgd.flags & T_TWINKLE) {
// See if we should toggle the twinkle
if (cg.time > p->twinkleTime) {
// If off, turn it on
if (p->cgd.flags & T_TWINKLE_OFF) {
p->cgd.flags &= ~T_TWINKLE_OFF;
2023-07-05 21:24:23 +02:00
p->twinkleTime = cg.time + p->cgd.min_twinkletimeon + random() * p->cgd.max_twinkletimeon;
} else {
p->cgd.flags |= T_TWINKLE_OFF;
2023-07-05 21:24:23 +02:00
p->twinkleTime = cg.time + p->cgd.min_twinkletimeoff + random() * p->cgd.max_twinkletimeoff;
}
}
if (p->cgd.flags & T_TWINKLE_OFF) {
memset(tempColor, 0, sizeof(tempColor));
}
}
if (p->cgd.flags & T_COLLISION) {
vec3_t vLighting;
cgi.R_GetLightingForSmoke(vLighting, p->ent.origin);
p->ent.shaderRGBA[0] = (int)((float)tempColor[0] * vLighting[0]);
p->ent.shaderRGBA[1] = (int)((float)tempColor[1] * vLighting[1]);
p->ent.shaderRGBA[2] = (int)((float)tempColor[2] * vLighting[2]);
2023-07-05 21:24:23 +02:00
} else {
p->ent.shaderRGBA[0] = tempColor[0];
p->ent.shaderRGBA[1] = tempColor[1];
p->ent.shaderRGBA[2] = tempColor[2];
}
if (p->cgd.flags & T_FADEIN && (fadein < 1)) // Do the fadein effect
{
p->ent.shaderRGBA[3] = (int)((float)tempColor[3] * (fadein * p->cgd.alpha));
2023-07-05 21:24:23 +02:00
} else if (p->cgd.flags & T_FADE) // Do a fadeout effect
{
p->ent.shaderRGBA[3] = (int)((float)tempColor[3] * (fade * p->cgd.alpha));
2023-07-05 21:24:23 +02:00
} else {
p->ent.shaderRGBA[3] = (int)((float)tempColor[3] * p->cgd.alpha);
}
if (p->cgd.flags & T_FLICKERALPHA) {
float random = random();
if (p->cgd.flags & (T_FADE | T_FADEIN)) {
p->ent.shaderRGBA[3] *= random;
2023-07-05 21:24:23 +02:00
} else {
p->ent.shaderRGBA[3] = p->cgd.color[3] * random;
}
}
// Check for completely faded out model
if (fade <= 0 && p->addedOnce) {
return false;
}
// Check for completely scaled out model
2023-07-05 21:24:23 +02:00
if ((p->ent.scale <= 0 && p->addedOnce) && !(p->cgd.flags & T_SCALEUPDOWN)) {
return false;
}
// Do swarming flies effects
if (p->cgd.flags & T_SWARM) {
UpdateSwarm(p);
}
return true;
}
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::OtherTempModelEffects(ctempmodel_t *p, Vector origin, refEntity_t *newEnt)
{
vec3_t axis[3];
if (p->number != -1) {
2023-10-02 13:41:00 +02:00
refEntity_t *old_entity;
dtiki_t *old_tiki;
int oldnum;
float oldscale;
2023-10-02 13:41:00 +02:00
// Set the axis
AnglesToAxis(p->cgd.angles, axis);
2023-10-02 13:41:00 +02:00
old_entity = current_entity;
old_tiki = current_tiki;
oldnum = current_entity_number;
oldscale = current_scale;
2023-10-02 13:41:00 +02:00
2023-07-05 21:24:23 +02:00
current_scale = newEnt->scale;
current_entity = newEnt;
current_tiki = p->cgd.tiki;
current_entity_number = p->number;
// Update any emitters that are active on this tempmodel
2023-07-05 21:24:23 +02:00
UpdateEmitter(p->cgd.tiki, axis, p->number, p->cgd.parent, origin);
// Add in trails for this tempmodel
if (p->cgd.flags2 & T2_TRAIL) {
2023-07-05 21:24:23 +02:00
Event *ev = new Event(EV_Client_Swipe);
ev->AddVector(origin);
commandManager.ProcessEvent(ev);
}
2023-10-02 13:41:00 +02:00
current_entity_number = oldnum;
current_tiki = old_tiki;
current_entity = old_entity;
current_scale = oldscale;
}
if (p->cgd.flags2 & T2_ALIGNSTRETCH) {
Vector vDelta;
float fScale;
2023-10-02 13:41:00 +02:00
vDelta = p->cgd.origin - p->cgd.oldorigin;
fScale = vDelta.length() * p->cgd.scale2;
VectorScale(newEnt->axis[0], fScale, newEnt->axis[0]);
}
}
2023-10-02 13:41:00 +02:00
qboolean ClientGameCommandManager::TempModelPhysics(ctempmodel_t *p, float ftime, float scale)
{
2023-07-05 21:24:23 +02:00
int dtime;
Vector parentOrigin(0, 0, 0);
Vector parentAngles(0, 0, 0);
Vector tempangles;
trace_t trace;
2023-07-05 21:24:23 +02:00
float dot;
2024-11-02 19:01:15 +01:00
int i;
VectorCopy(p->ent.origin, p->lastEnt.origin);
AxisCopy(p->ent.axis, p->lastEnt.axis);
dtime = (cg.time - p->cgd.createTime);
// Save oldorigin
p->cgd.oldorigin = p->cgd.origin;
// Update based on swarm
if (p->cgd.flags & T_SWARM) {
2023-10-02 13:41:00 +02:00
p->cgd.origin += p->cgd.velocity * ftime * scale;
}
// Update the orign and the angles based on velocities first
2023-10-02 13:41:00 +02:00
else if (p->cgd.flags2 & (T2_MOVE | T2_ACCEL)) {
p->cgd.origin += p->cgd.velocity * ftime * scale;
}
// If linked to the parent or hardlinked, get the parent's origin
2023-07-05 21:24:23 +02:00
if ((p->cgd.flags & (T_PARENTLINK | T_HARDLINK)) && (p->cgd.parent != ENTITYNUM_NONE)) {
centity_t *pc;
refEntity_t *e;
2023-10-03 17:02:07 +02:00
pc = &cg_entities[p->cgd.parent];
if (!pc->currentValid) {
return false;
}
2023-10-03 17:02:07 +02:00
e = cgi.R_GetRenderEntity(p->cgd.parent);
if (!e) {
return false;
}
2023-10-03 17:02:07 +02:00
parentOrigin = e->origin;
vectoangles(e->axis[0], parentAngles);
} else if (p->cgd.flags & T_SWARM) {
p->cgd.parentOrigin = p->cgd.velocity + p->cgd.accel * ftime * scale;
}
2023-10-02 13:41:00 +02:00
if (p->cgd.flags2 & T2_WATERONLY) {
if (!(cgi.CM_PointContents(p->cgd.origin, 0) & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))) {
return false;
}
}
// Align the object along it's traveling vector
if (p->cgd.flags & T_ALIGN) {
p->cgd.angles = p->cgd.velocity.toAngles();
2023-07-05 21:24:23 +02:00
parentAngles = vec_zero;
}
if (p->cgd.flags & T_RANDOMROLL) {
p->cgd.angles[ROLL] = random() * 360;
}
// Update the angles based on angular velocity
if (p->cgd.flags2 & T2_AMOVE) {
p->cgd.angles = p->cgd.angles + (ftime * p->cgd.avelocity);
}
// Mod the angles if needed
p->cgd.angles[0] = AngleMod(p->cgd.angles[0]);
p->cgd.angles[1] = AngleMod(p->cgd.angles[1]);
p->cgd.angles[2] = AngleMod(p->cgd.angles[2]);
// Convert to axis
2023-07-05 21:24:23 +02:00
if ((p->cgd.flags & (T_ALIGN | T_RANDOMROLL | T_PARENTLINK | T_HARDLINK | T_ANGLES))
|| (p->cgd.flags2 & T2_AMOVE)) {
2023-10-02 13:41:00 +02:00
// Add in parent angles
tempangles = p->cgd.angles + parentAngles;
AnglesToAxis(tempangles, p->ent.axis);
}
// Only do real collision if necessary
if (p->cgd.flags & T_COLLISION) {
// trace a line from previous position to new position
2023-07-05 21:24:23 +02:00
CG_Trace(
&trace,
p->cgd.oldorigin,
vec3_origin,
vec3_origin,
p->cgd.origin,
-1,
p->cgd.collisionmask,
qfalse,
qfalse,
"Collision"
);
} else {
// Fake it out so it never collides
trace.fraction = 1.0;
}
// Check for collision
if (trace.fraction == 1.0) {
// Acceleration of velocity
if (p->cgd.flags2 & T2_ACCEL) {
p->cgd.velocity = p->cgd.velocity + ftime * p->cgd.accel;
}
if (p->cgd.flags2 & T2_FRICTION) {
float fFriction = 1.0f - ftime * p->cgd.friction;
if (fFriction > 0.0f) {
p->cgd.velocity *= fFriction;
2023-07-05 21:24:23 +02:00
} else {
p->cgd.velocity = vec_zero;
}
}
if (p->cgd.flags2 & T2_CLAMP_VEL) {
2023-10-02 13:41:00 +02:00
p->cgd.velocity.x = Q_clamp_float(p->cgd.velocity.x, p->cgd.minVel.x, p->cgd.maxVel.x);
p->cgd.velocity.y = Q_clamp_float(p->cgd.velocity.y, p->cgd.minVel.y, p->cgd.maxVel.y);
p->cgd.velocity.z = Q_clamp_float(p->cgd.velocity.z, p->cgd.minVel.z, p->cgd.maxVel.z);
2024-11-02 19:01:15 +01:00
} else if (p->cgd.flags2 & T2_CLAMP_VEL_AXIS) {
Vector localVelocity;
localVelocity.x = DotProduct(p->cgd.velocity, p->ent.axis[0]);
localVelocity.y = DotProduct(p->cgd.velocity, p->ent.axis[1]);
localVelocity.z = DotProduct(p->cgd.velocity, p->ent.axis[2]);
2023-10-02 13:41:00 +02:00
localVelocity.x = Q_clamp_float(localVelocity.x, p->cgd.minVel.x, p->cgd.maxVel.x);
localVelocity.y = Q_clamp_float(localVelocity.y, p->cgd.minVel.y, p->cgd.maxVel.y);
localVelocity.z = Q_clamp_float(localVelocity.z, p->cgd.minVel.z, p->cgd.maxVel.z);
p->cgd.velocity.x = DotProduct(localVelocity, p->ent.axis[0]);
p->cgd.velocity.y = DotProduct(localVelocity, p->ent.axis[1]);
p->cgd.velocity.z = DotProduct(localVelocity, p->ent.axis[2]);
}
2024-11-02 19:01:15 +01:00
if (p->cgd.flags2 & T2_WIND_AFFECT) {
for (i = 0; i < 3; i++) {
float fWind;
switch (i) {
case 0:
fWind = vss_wind_x->value;
break;
case 1:
fWind = vss_wind_y->value;
break;
case 2:
fWind = vss_wind_z->value;
break;
}
if (fWind < 0) {
if (p->cgd.velocity[i] > fWind) {
p->cgd.velocity[i] -= ftime * vss_wind_strength->value;
if (p->cgd.velocity[i] > fWind) {
p->cgd.velocity[i] = fWind;
}
} else if (p->cgd.velocity[i] < fWind) {
p->cgd.velocity[i] += ftime * vss_movement_dampen->value;
if (p->cgd.velocity[i] < fWind) {
p->cgd.velocity[i] = fWind;
}
}
} else {
if (p->cgd.velocity[i] < fWind) {
p->cgd.velocity[i] += ftime * vss_wind_strength->value;
if (p->cgd.velocity[i] > fWind) {
p->cgd.velocity[i] = fWind;
}
} else if (p->cgd.velocity[i] > fWind) {
p->cgd.velocity[i] -= ftime * vss_wind_strength->value;
if (p->cgd.velocity[i] < fWind) {
p->cgd.velocity[i] = fWind;
}
}
}
}
}
2023-07-05 21:24:23 +02:00
} else {
Vector normal;
// Set the origin
p->cgd.origin = trace.endpos;
2023-07-05 21:24:23 +02:00
if ((p->cgd.flags2 & T2_BOUNCE_DECAL) && (p->cgd.bouncecount < p->cgd.maxbouncecount)) {
// Put down a bounce decal
qhandle_t shader = cgi.R_RegisterShader(p->cgd.shadername);
2023-07-05 21:24:23 +02:00
CG_ImpactMarkSimple(
shader,
trace.endpos,
trace.plane.normal,
p->cgd.decal_orientation,
2023-10-02 13:41:00 +02:00
p->cgd.decal_radius,
p->cgd.color[0],
p->cgd.color[1],
2023-07-05 21:24:23 +02:00
p->cgd.color[2],
p->cgd.alpha,
p->cgd.flags & T_FADE,
p->cgd.flags2 & T2_TEMPORARY_DECAL,
2023-10-02 13:41:00 +02:00
qtrue,
2023-07-05 21:24:23 +02:00
p->cgd.flags & T_FADEIN
);
p->cgd.bouncecount++;
}
if (p->cgd.flags & T_DIETOUCH) {
return false;
}
// calculate the bounce
normal = trace.plane.normal;
// reflect the velocity on the trace plane
if (p->cgd.flags2 & T2_ACCEL) {
2023-07-05 21:24:23 +02:00
p->cgd.velocity = p->cgd.velocity + ftime * trace.fraction * p->cgd.accel;
}
2023-07-05 21:24:23 +02:00
dot = p->cgd.velocity * normal;
p->cgd.velocity = p->cgd.velocity + ((-2 * dot) * normal);
p->cgd.velocity *= p->cgd.bouncefactor;
2023-10-02 13:41:00 +02:00
p->cgd.avelocity *= -p->cgd.bouncefactor;
// check for stop
2023-10-02 13:41:00 +02:00
if (trace.plane.normal[2] > 0 && p->cgd.velocity[2] < 45) {
2023-07-05 21:24:23 +02:00
p->cgd.velocity = Vector(0, 0, 0);
p->cgd.avelocity = Vector(0, 0, 0);
p->cgd.flags &= ~T_WAVE;
2023-07-05 21:24:23 +02:00
} else {
if (p->cgd.flags & T_BOUNCESOUNDONCE) {
vec3_t org;
VectorCopy(p->cgd.origin, org);
PlaySound(p->cgd.bouncesound, org);
p->cgd.flags &= ~(T_BOUNCESOUNDONCE | T_BOUNCESOUND);
2023-07-05 21:24:23 +02:00
} else if ((p->cgd.flags & T_BOUNCESOUND) && (p->next_bouncesound_time < cg.time)) {
vec3_t org;
VectorCopy(p->cgd.origin, org);
PlaySound(p->cgd.bouncesound, org);
p->next_bouncesound_time = cg.time + p->cgd.bouncesound_delay;
}
}
}
// copy over origin
VectorCopy(p->cgd.origin, p->ent.origin);
// Add in parent's origin if linked
if (p->cgd.flags & (T_PARENTLINK | T_HARDLINK)) {
VectorAdd(p->ent.origin, parentOrigin, p->ent.origin);
}
if (!p->lastEntValid) {
// Make the lastEnt valid, by setting it to p->ent and setting the
// origin to the tempmodel's oldorigin
p->lastEnt = p->ent;
VectorCopy(p->cgd.oldorigin, p->lastEnt.origin);
p->lastEntValid = true;
}
return true;
}
2023-07-05 21:24:23 +02:00
qboolean ClientGameCommandManager::LerpTempModel(refEntity_t *newEnt, ctempmodel_t *p, float frac)
{
int i, j;
// If the tempmodel is parentlinked, then we need to get the origin of the
// parent and add it to the tempmodel's origin
if (p->cgd.flags & (T_PARENTLINK | T_HARDLINK)) {
2023-07-05 21:24:23 +02:00
centity_t *pc;
Vector parentOrigin;
// Lerp the tempmodel's local origin
for (i = 0; i < 3; i++) {
2023-07-05 21:24:23 +02:00
newEnt->origin[i] = p->cgd.oldorigin[i] + frac * (p->cgd.origin[i] - p->cgd.oldorigin[i]);
}
// Find the parent entity
pc = &cg_entities[p->cgd.parent];
if (pc->currentValid) {
2023-07-05 21:24:23 +02:00
refEntity_t *e;
e = cgi.R_GetRenderEntity(p->cgd.parent);
if (!e) {
return false;
}
parentOrigin = e->origin;
2023-07-05 21:24:23 +02:00
} else {
return false;
}
// Add the parent ent's origin to the local origin
VectorAdd(newEnt->origin, parentOrigin, newEnt->origin);
2023-07-05 21:24:23 +02:00
} else {
2023-10-03 17:02:07 +02:00
if (p->cgd.flags2 & (T2_MOVE | T2_ACCEL) || (p->cgd.flags & T_SWARM)) {
// Lerp the ent's origin
for (i = 0; i < 3; i++) {
2023-07-05 21:24:23 +02:00
newEnt->origin[i] = p->lastEnt.origin[i] + frac * (p->ent.origin[i] - p->lastEnt.origin[i]);
}
}
}
if (p->cgd.flags2 & T2_PARALLEL) {
Vector v1 = p->cgd.origin - cg.refdef.vieworg;
vectoangles(v1, p->cgd.angles);
AnglesToAxis(p->cgd.angles, newEnt->axis);
2023-07-05 21:24:23 +02:00
} else if ((p->cgd.flags & (T_ALIGN | T_RANDOMROLL | T_PARENTLINK | T_HARDLINK)) || (p->cgd.flags2 & T2_AMOVE)) {
// Lerp axis
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
2023-07-05 21:24:23 +02:00
newEnt->axis[i][j] = p->lastEnt.axis[i][j] + frac * (p->ent.axis[i][j] - p->lastEnt.axis[i][j]);
}
}
}
return true;
}
//===============
// CG_AddTempModels
//===============
2023-07-05 21:24:23 +02:00
void CG_AddTempModels(void)
{
commandManager.AddTempModels();
}
//===============
// AddTempModels - Update and add tempmodels to the ref
//===============
#define TOO_MUCH_TIME_PASSED 500
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::AddTempModels(void)
{
2023-07-05 21:24:23 +02:00
ctempmodel_t *p, *next;
int count = 0; // Tempmodel count
int frameTime;
float effectTime, effectTime2;
int mstime = 0;
float ftime = 0;
float time2 = 0;
float scale = 1.0f;
float lerpfrac = 0;
int physics_rate = 0;
qboolean ret;
refEntity_t newEnt;
dtiki_t *old_tiki;
int old_num;
refEntity_t *old_ent;
// To counteract cg.time going backwards
if (lastTempModelFrameTime
&& ((cg.time < lastTempModelFrameTime) || (cg.time - lastTempModelFrameTime > TOO_MUCH_TIME_PASSED))) {
p = m_active_tempmodels.prev;
for (; p != &m_active_tempmodels; p = next) {
next = p->prev;
p->lastPhysicsTime = cg.time;
}
lastTempModelFrameTime = cg.time;
return;
}
if (lastTempModelFrameTime) {
frameTime = cg.time - lastTempModelFrameTime;
2023-07-05 21:24:23 +02:00
} else {
frameTime = 0;
}
if (paused->integer) {
lastTempModelFrameTime = 0;
2023-07-05 21:24:23 +02:00
} else {
lastTempModelFrameTime = cg.time;
}
memset(&newEnt, 0, sizeof(newEnt));
newEnt.parentEntity = ENTITYNUM_NONE;
// Set this frame time for the next one
2023-07-05 21:24:23 +02:00
effectTime = (float)frameTime / 1000.0f;
effectTime2 = effectTime * effectTime;
// If there is a current entity, it's scale is used as a factor
if (current_entity) {
scale = current_entity->scale;
}
// Go through all the temp models and run the physics if necessary,
// then add them to the ref
2023-07-05 21:24:23 +02:00
old_ent = current_entity;
old_tiki = current_tiki;
2023-07-05 21:24:23 +02:00
old_num = current_entity_number;
p = m_active_tempmodels.prev;
for (; p != &m_active_tempmodels; p = next) {
// grab next now, so if the local entity is freed we still have it
next = p->prev;
if ((p->cgd.flags & T_DETAIL) && !cg_detail->integer) {
FreeTempModel(p);
continue;
}
2023-07-05 21:24:23 +02:00
p->ent.tiki = p->cgd.tiki;
current_entity = &p->ent;
current_tiki = p->cgd.tiki;
current_entity_number = p->number;
2023-10-02 13:41:00 +02:00
TempModelRealtimeEffects(p, effectTime, scale);
if (p->lastPhysicsTime) {
mstime = cg.time - p->lastPhysicsTime;
// Check for physics
2023-07-05 21:24:23 +02:00
physics_rate = 1000 / p->cgd.physicsRate; // Physics rate in milliseconds
// Avoid large jumps in time
if (mstime > physics_rate * 2) {
mstime = physics_rate;
}
2023-07-05 21:24:23 +02:00
if ((mstime >= physics_rate) || (p->cgd.flags2 & T2_PHYSICS_EVERYFRAME)) {
ftime = mstime / 1000.0f;
2023-10-02 13:41:00 +02:00
ret = TempModelPhysics(p, ftime, scale);
if (!ret) {
FreeTempModel(p);
continue;
}
p->lastPhysicsTime = cg.time;
}
}
// Calculate the lerp value based on the time passed since last physics
// time of this tempmodel
lerpfrac = (float)(cg.time - p->lastPhysicsTime) / (float)physics_rate;
// Clamp
if (lerpfrac > 1 || (p->cgd.flags2 & T2_PHYSICS_EVERYFRAME)) {
lerpfrac = 1;
}
if (lerpfrac < 0) {
lerpfrac = 0;
}
// Increment the time this tempmodel has been alive
p->aliveTime += frameTime;
// Dead, and free up the tempmodel
if (p->aliveTime >= p->cgd.life && p->addedOnce) {
FreeTempModel(p);
continue;
}
// Run physics if the lastEnt is not valid to get a valid lerp
if (!p->lastEntValid) {
2023-10-02 13:41:00 +02:00
float t;
t = physics_rate / 1000.0f;
2023-10-02 13:41:00 +02:00
ret = TempModelPhysics(p, t, scale);
if (!ret) {
FreeTempModel(p);
continue;
}
2023-07-05 21:24:23 +02:00
lerpfrac = 0;
p->lastPhysicsTime = cg.time;
}
// clear out the new entity and initialize it
// this will become the current_entity if anything is spawned off it
newEnt.scale = p->ent.scale;
memcpy(newEnt.shaderRGBA, p->ent.shaderRGBA, 4);
AxisCopy(p->ent.axis, newEnt.axis);
VectorCopy(p->ent.origin, newEnt.origin);
// Lerp the tempmodel
if (!LerpTempModel(&newEnt, p, lerpfrac)) {
FreeTempModel(p);
continue;
}
if (p->cgd.flags & T_WAVE) {
2023-10-03 17:02:07 +02:00
vec3_t origin;
2023-07-05 21:24:23 +02:00
float axis[3][3];
2023-10-03 17:02:07 +02:00
VectorMA(p->m_spawnthing->linked_origin, newEnt.origin[0], p->m_spawnthing->linked_axis[0], origin);
2023-10-02 13:41:00 +02:00
VectorMA(origin, newEnt.origin[1], p->m_spawnthing->linked_axis[1], origin);
VectorMA(origin, newEnt.origin[2], p->m_spawnthing->linked_axis[2], origin);
VectorCopy(origin, newEnt.origin);
MatrixMultiply(newEnt.axis, p->m_spawnthing->linked_axis, axis);
AxisCopy(axis, newEnt.axis);
}
// Animate and do trails (swipes)
2023-07-05 21:24:23 +02:00
newEnt.renderfx = p->ent.renderfx;
newEnt.hModel = p->ent.hModel;
newEnt.reType = p->ent.reType;
newEnt.shaderTime = p->ent.shaderTime;
2023-10-02 13:41:00 +02:00
newEnt.frameInfo[0].index = p->ent.frameInfo[0].index;
newEnt.frameInfo[0].weight = 1.0;
newEnt.frameInfo[0].time = 0.0;
2023-07-05 21:24:23 +02:00
newEnt.wasframe = p->ent.wasframe;
newEnt.actionWeight = 1.0;
newEnt.entityNumber = ENTITYNUM_NONE;
2023-07-05 21:24:23 +02:00
newEnt.tiki = p->ent.tiki;
AnimateTempModel(p, newEnt.origin, &newEnt);
OtherTempModelEffects(p, newEnt.origin, &newEnt);
2023-10-02 13:41:00 +02:00
VectorCopy(newEnt.origin, newEnt.lightingOrigin);
newEnt.radius = 4.0;
// Add to the ref
if (p->cgd.flags & T_DLIGHT) {
// Tempmodel is a Dynamic Light
2023-07-05 21:24:23 +02:00
cgi.R_AddLightToScene(
p->cgd.origin,
p->cgd.lightIntensity * scale,
p->cgd.color[0],
p->cgd.color[1],
p->cgd.color[2],
2023-07-05 21:24:23 +02:00
p->cgd.lightType
);
} else if (p->ent.reType == RT_SPRITE) {
vec3_t vTestAngles;
cgi.R_AddRefSpriteToScene(&newEnt); // Sprite
MatrixToEulerAngles(newEnt.axis, vTestAngles);
2023-07-05 21:24:23 +02:00
} else {
cgi.R_AddRefEntityToScene(&newEnt, ENTITYNUM_NONE); // Model
}
// Set the added once flag so we can delete it later
p->addedOnce = qtrue;
// Local tempmodel count stat
count++;
}
2023-07-05 21:24:23 +02:00
current_entity = old_ent;
current_tiki = old_tiki;
current_entity_number = old_num;
// stats
if (cg_showtempmodels->integer) {
cgi.DPrintf("TC:%i\n", count);
}
}
//=================
// SpawnTempModel
//=================
2023-07-05 21:24:23 +02:00
void ClientGameCommandManager::SpawnTempModel(int count, spawnthing_t *sp)
{
m_spawnthing = sp;
SpawnTempModel(count);
}
//=================
// SpawnTempModel
//=================
void ClientGameCommandManager::SpawnTempModel(int mcount)
{
2023-07-05 21:24:23 +02:00
int i;
ctempmodel_t *p;
refEntity_t ent;
int count;
float current_entity_scale = 1.0f;
Vector newForward;
Vector delta;
Vector start;
Vector vForward, vLeft, vUp;
float fDist;
delta = m_spawnthing->cgd.origin - cg.refdef.vieworg;
fDist = delta * delta * (cg.refdef.fov_x * cg.refdef.fov_x / 6400.0);
if (fDist >= m_spawnthing->fMaxRangeSquared || fDist < m_spawnthing->fMinRangeSquared) {
// don't draw above the distance
return;
}
if (current_entity) {
current_entity_scale = current_entity->scale;
2023-07-05 21:24:23 +02:00
} else {
current_entity_scale = 1.0f;
}
if (current_scale > 0) {
current_entity_scale *= current_scale;
}
if (mcount > 1) {
mcount *= cg_effectdetail->value;
}
if (mcount < 1) {
mcount = 1;
}
if (m_spawnthing->cgd.flags2 & T2_SPIN) {
float cosa, sina;
float fAngle;
fAngle = (cg.time - cgs.levelStartTime) * m_spawnthing->cgd.spin_rotation / 160.0f;
2023-07-05 21:24:23 +02:00
cosa = cos(fAngle);
sina = sin(fAngle);
vForward = cosa * Vector(m_spawnthing->axis[0]);
VectorMA(vForward, -sina, m_spawnthing->axis[1], vForward);
vLeft = sina * Vector(m_spawnthing->axis[0]);
VectorMA(vLeft, cosa, m_spawnthing->axis[1], vLeft);
2023-07-05 21:24:23 +02:00
} else {
vForward = m_spawnthing->axis[0];
2023-07-05 21:24:23 +02:00
vLeft = m_spawnthing->axis[1];
}
vUp = m_spawnthing->axis[2];
for (count = 0; count < mcount; count++) {
p = AllocateTempModel();
if (!p) {
2023-10-02 18:46:45 +02:00
if (cgi.Milliseconds() >= next_tempmodel_warning) {
// make sure to not spam the console with this message
cgi.DPrintf("Out of tempmodels\n");
next_tempmodel_warning = cgi.Milliseconds() + 1000;
}
return;
}
memset(&ent, 0, sizeof(refEntity_t));
memset(&p->lastEnt, 0, sizeof(refEntity_t));
if (m_spawnthing->cgd.flags & T_WAVE) {
p->m_spawnthing = m_spawnthing;
m_spawnthing->numtempmodels++;
start = Vector(0, 0, 0);
2023-07-05 21:24:23 +02:00
} else {
p->m_spawnthing = NULL;
2023-07-05 21:24:23 +02:00
start = m_spawnthing->cgd.origin;
}
// Copy over the common data block
p->cgd = m_spawnthing->cgd;
// newForward may be changed by spehere or circle
newForward = vForward;
// Set up the origin of the tempmodel
if (m_spawnthing->cgd.flags & T_SPHERE) {
2023-10-02 18:46:45 +02:00
do {
// Create a random forward vector so the particles burst out in a
// sphere
newForward = Vector(crandom(), crandom(), crandom());
} while (newForward * newForward > 1.0);
2023-07-05 21:24:23 +02:00
} else if (m_spawnthing->cgd.flags & T_CIRCLE) {
if (m_spawnthing->sphereRadius != 0) // Offset by the radius
{
Vector dst;
// Create a circular shaped burst around the up vector
float angle;
if (mcount == 1) {
angle = random() * 360;
} else {
angle = ((float)count / (float)m_spawnthing->count) * 360; // * M_PI * 2;
}
2023-07-05 21:24:23 +02:00
Vector end = Vector(vLeft) * m_spawnthing->sphereRadius * current_entity_scale;
RotatePointAroundVector(dst, Vector(vForward), end, angle);
VectorAdd(dst, m_spawnthing->cgd.origin, p->cgd.origin);
newForward = p->cgd.origin - m_spawnthing->cgd.origin;
newForward.normalize();
}
} else if (m_spawnthing->cgd.flags & T_INWARDSPHERE) {
// Project the origin along a random ray, and set the forward
// vector pointing back to the origin
Vector dir, end;
2023-10-02 18:46:45 +02:00
do {
dir = Vector(crandom(), crandom(), crandom());
} while (dir * dir > 1.0);
2023-07-05 21:24:23 +02:00
end = m_spawnthing->cgd.origin + dir * m_spawnthing->sphereRadius * current_entity_scale;
VectorCopy(end, p->cgd.origin);
newForward = dir * -1;
2023-07-05 21:24:23 +02:00
} else if (m_spawnthing->cgd.flags2 & T2_CONE) {
float fHeight;
float fRadius;
float fAngle;
float sina;
float cosa;
fHeight = random();
fRadius = random();
if (fHeight < fRadius) {
float fTemp = fHeight;
2023-07-05 21:24:23 +02:00
fHeight = fRadius;
fRadius = fTemp;
}
fHeight *= m_spawnthing->coneHeight;
fRadius = m_spawnthing->sphereRadius;
fAngle = random() * M_PI * 2;
2023-07-05 21:24:23 +02:00
sina = sin(fAngle);
cosa = cos(fAngle);
p->cgd.origin = start + vForward * fHeight;
2024-01-01 22:33:05 +01:00
p->cgd.origin += vLeft * (cosa * fRadius);
p->cgd.origin += vUp * (sina * fRadius);
} else if (m_spawnthing->sphereRadius != 0) { // Offset in a spherical pattern
Vector dir, end;
dir = Vector(crandom(), crandom(), crandom());
dir.normalize();
2023-07-05 21:24:23 +02:00
end = m_spawnthing->cgd.origin + dir * m_spawnthing->sphereRadius * current_entity_scale;
VectorCopy(end, p->cgd.origin);
newForward = dir;
2023-07-05 21:24:23 +02:00
} else {
2023-10-03 17:02:07 +02:00
VectorCopy(start, p->cgd.origin);
}
if (m_spawnthing->cgd.flags & T_SWARM && !(m_spawnthing->cgd.flags & (T_HARDLINK | T_PARENTLINK))) {
p->cgd.parentOrigin = p->cgd.origin;
}
// Randomize the origin based on offsets
for (i = 0; i < 3; i++) {
2023-07-05 21:24:23 +02:00
p->cgd.origin[i] +=
(random() * m_spawnthing->origin_offset_amplitude[i] + m_spawnthing->origin_offset_base[i])
* current_entity_scale;
}
p->cgd.oldorigin = p->cgd.origin;
2023-07-05 21:24:23 +02:00
p->modelname = m_spawnthing->GetModel();
if (!(p->cgd.flags & T_DLIGHT) && !p->modelname.length()) {
FreeTempModel(p);
continue;
}
2023-07-05 21:24:23 +02:00
p->addedOnce = qfalse;
p->lastEntValid = qfalse;
if (p->modelname.length()) {
ent.hModel = cgi.R_RegisterModel(p->modelname.c_str());
}
// Initialize the refEntity
ent.shaderTime = cg.time / 1000.0f;
// Get the tikihandle
p->cgd.tiki = cgi.R_Model_GetHandle(ent.hModel);
// Set the reftype based on the modelname
if (p->modelname == "*beam") {
2023-07-05 21:24:23 +02:00
ent.reType = RT_BEAM;
ent.customShader = cgi.R_RegisterShader("beamshader");
2023-07-05 21:24:23 +02:00
} else if (strstr(p->modelname, ".spr")) {
ent.reType = RT_SPRITE;
2023-07-05 21:24:23 +02:00
} else {
ent.reType = RT_MODEL;
}
// Set the animation
if (m_spawnthing->animName.length() && p->cgd.tiki) {
2023-07-05 21:24:23 +02:00
ent.frameInfo[0].index = cgi.Anim_NumForName(p->cgd.tiki, m_spawnthing->animName);
if (ent.frameInfo[0].index < 0) {
ent.frameInfo[0].index = 0;
}
ent.frameInfo[0].weight = 1.0f;
2023-07-05 21:24:23 +02:00
ent.frameInfo[0].time = 0.0f;
ent.actionWeight = 1.0f;
} else if (ent.reType == RT_MODEL && p->cgd.tiki) {
ent.frameInfo[0].index = cgi.Anim_NumForName(p->cgd.tiki, "idle");
if (ent.frameInfo[0].index < 0) {
ent.frameInfo[0].index = 0;
}
ent.frameInfo[0].weight = 1.0f;
2023-07-05 21:24:23 +02:00
ent.frameInfo[0].time = 0.0f;
ent.actionWeight = 1.0f;
}
// Randomize the scale
if (m_spawnthing->cgd.flags & T_RANDSCALE) // Check for random scale
{
2023-07-05 21:24:23 +02:00
ent.scale = RandomizeRange(m_spawnthing->cgd.scalemin, m_spawnthing->cgd.scalemax);
p->cgd.scale = ent.scale;
2023-07-05 21:24:23 +02:00
} else {
ent.scale = m_spawnthing->cgd.scale;
}
ent.scale *= current_entity_scale;
// CURRENT ENTITY INFLUENCES ON THE TEMPMODELS HAPPEN HERE
// copy over the renderfx from the current_entity, but only the flags we
// want
if (current_entity) {
// explicitly add RF_LIGHTING ORIGIN and RF_SHADOWPLANE because we
// don't want those on dynamic objects
2023-07-05 21:24:23 +02:00
ent.renderfx |= (current_entity->renderfx & ~(RF_FLAGS_NOT_INHERITED | RF_LIGHTING_ORIGIN));
}
// Set up modulation for constant color
2023-07-05 21:24:23 +02:00
for (i = 0; i < 4; i++) {
p->cgd.color[i] = m_spawnthing->cgd.color[i];
ent.shaderRGBA[i] = (byte)(p->cgd.color[i] * 255.0);
}
if (p->cgd.flags2 & T2_VARYCOLOR) {
for (i = 0; i < 3; i++) {
p->cgd.color[i] *= 0.8f + random() * 0.2f;
}
}
// Apply the alpha from the current_entity to the tempmodel
if (current_entity) {
if (current_entity->shaderRGBA[3] != 255) {
// pre-multiply the alpha from the entity
2023-07-05 21:24:23 +02:00
for (i = 0; i < 4; i++) {
p->cgd.color[3] *= current_entity->shaderRGBA[3] / 255.0;
ent.shaderRGBA[3] = current_entity->shaderRGBA[3];
}
}
if (m_spawnthing->cgd.color[3] < 1.0f) {
p->cgd.color[3] *= m_spawnthing->cgd.color[3];
ent.shaderRGBA[3] = (int)(p->cgd.color[3] * 255.0f);
}
}
2023-07-05 21:24:23 +02:00
p->ent = ent;
p->lastEnt = ent;
2023-07-05 21:24:23 +02:00
p->number = -1;
// If createTime is specified, the use it. Otherwise use the createTime
// from the spawnthing.
p->aliveTime = 0;
// If animateonce is set, set the life = to the length of the anim
2023-07-05 21:24:23 +02:00
if ((m_spawnthing->cgd.flags & T_ANIMATEONCE) && (p->ent.frameInfo[0].index > 0)) {
p->cgd.life = cgi.Anim_Time(p->cgd.tiki, p->ent.frameInfo[0].index) * 1000.0f;
} else {
p->cgd.life = m_spawnthing->cgd.life;
if (m_spawnthing->life_random) {
p->cgd.life += m_spawnthing->life_random * random();
}
}
2023-07-05 21:24:23 +02:00
p->lastAnimTime = p->cgd.createTime;
p->lastPhysicsTime = p->cgd.createTime;
p->killTime = cg.time + p->cgd.life; // The time the tempmodel will die
p->seed = m_seed++; // For use with randomizations
p->cgd.velocity = Vector(0, 0, 0); // Zero out the velocity
p->next_bouncesound_time = 0; // Init the next bouncesound time
if (p->cgd.flags & T_TWINKLE) {
if (random() > 0.5f) {
p->cgd.flags |= T_TWINKLE_OFF;
}
}
if (p->cgd.flags2 & T2_TRAIL) {
// Assign a global number to this entity from the
// command_time_manager Tempmodels with trails need their own unique
// number.
p->number = m_command_time_manager.AssignNumber();
p->cgd.flags |= T_ASSIGNED_NUMBER;
2023-07-05 21:24:23 +02:00
int oldnum = current_entity_number;
centity_t *oldcent = current_centity;
current_centity = NULL;
current_entity_number = p->number;
2023-07-05 21:24:23 +02:00
Event *swipeEv = new Event(EV_Client_SwipeOn);
swipeEv->AddString(p->cgd.swipe_shader);
swipeEv->AddString(p->cgd.swipe_tag_start);
swipeEv->AddString(p->cgd.swipe_tag_end);
swipeEv->AddFloat(p->cgd.swipe_life);
commandManager.ProcessEvent(swipeEv);
2023-07-05 21:24:23 +02:00
current_centity = oldcent;
current_entity_number = oldnum;
}
// Check to see if this tiki has any emitters bound to it and update
// it's number. This is used for updating emitters that are attached to
// tempmodels.
if (p->cgd.tiki) {
for (i = 1; i <= m_emitters.NumObjects(); i++) {
2023-07-05 21:24:23 +02:00
spawnthing_t *st = m_emitters.ObjectAt(i);
if (st->cgd.tiki == p->cgd.tiki) {
// Assign this tempmodel a number if he doesn't already have
// one
if (p->number == -1) {
p->number = st->AssignNumber();
}
st->GetEmitTime(p->number);
}
}
}
for (i = 0; i < 3; i++) {
// Randomize avelocity or set absolute
2023-07-05 21:24:23 +02:00
p->cgd.avelocity[i] = m_spawnthing->avelocity_amplitude[i] * random() + m_spawnthing->avelocity_base[i];
// Randomize angles or set absolute
p->cgd.angles[i] = m_spawnthing->angles_amplitude[i] * random() + m_spawnthing->cgd.angles[i];
}
// If forward velocity is set, just use that otherwise use random
// variation of the velocity
if (m_spawnthing->forwardVelocity != 0) {
for (i = 0; i < 3; i++) {
2023-07-05 21:24:23 +02:00
p->cgd.velocity[i] = newForward[i] * m_spawnthing->forwardVelocity * current_entity_scale;
}
}
if (p->cgd.flags2 & T2_PARALLEL) {
Vector v1 = p->cgd.origin - cg.refdef.vieworg;
vectoangles(v1, p->cgd.angles);
}
AnglesToAxis(p->cgd.angles, m_spawnthing->axis);
AxisCopy(m_spawnthing->axis, p->ent.axis);
// Random offsets along axis
for (i = 0; i < 3; i++) {
if (p->cgd.flags2 & T2_PARALLEL) {
2023-07-05 21:24:23 +02:00
p->cgd.origin +=
Vector(m_spawnthing->axis[i])
* ((m_spawnthing->axis_offset_amplitude[i] * random() + m_spawnthing->axis_offset_base[i])
2023-07-05 21:24:23 +02:00
* current_entity_scale);
} else {
p->cgd.origin +=
Vector(m_spawnthing->tag_axis[i])
* ((m_spawnthing->axis_offset_amplitude[i] * random() + m_spawnthing->axis_offset_base[i])
2023-07-05 21:24:23 +02:00
* current_entity_scale);
}
}
if (m_spawnthing->cgd.flags2 & T2_RADIALVELOCITY) {
float fLength;
p->cgd.velocity = p->cgd.origin - start;
fLength = p->cgd.velocity.length();
2023-07-05 21:24:23 +02:00
if (fLength) {
float fVel = m_spawnthing->cgd.velocity[0]
+ (m_spawnthing->cgd.velocity[1] + m_spawnthing->cgd.velocity[2] * random()) / fLength;
p->cgd.velocity *= fVel;
}
}
for (i = 0; i < 3; i++) {
2023-07-05 21:24:23 +02:00
float fVel =
(m_spawnthing->randvel_base[i] + m_spawnthing->randvel_amplitude[i] * random()) * current_entity_scale;
if (m_spawnthing->cgd.flags & T_RANDVELAXIS) {
if (p->cgd.flags2 & T2_NOTAGAXIS) {
p->cgd.velocity += fVel * Vector(m_spawnthing->axis[i]);
} else {
p->cgd.velocity += fVel * Vector(m_spawnthing->tag_axis[i]);
}
2023-07-05 21:24:23 +02:00
} else {
p->cgd.velocity[i] += fVel;
}
}
if (p->cgd.flags & (T_ALIGN | T_DETAIL)) {
p->cgd.angles = p->cgd.velocity.toAngles();
}
p->cgd.origin += p->cgd.velocity * (p->aliveTime / 1000.0) * current_entity_scale;
if (p->cgd.flags & T_AUTOCALCLIFE) {
2023-07-05 21:24:23 +02:00
Vector end, delta;
float length, speed;
vec3_t vForward;
trace_t trace;
AngleVectorsLeft(p->cgd.angles, vForward, NULL, NULL);
end = p->cgd.origin + Vector(vForward) * MAP_SIZE;
2023-07-05 21:24:23 +02:00
CG_Trace(
&trace,
p->cgd.origin,
vec_zero,
vec_zero,
end,
ENTITYNUM_NONE,
CONTENTS_SOLID | CONTENTS_WATER,
qfalse,
qfalse,
"AutoCalcLife"
);
delta = trace.endpos - p->cgd.origin;
length = delta.length();
2023-07-05 21:24:23 +02:00
speed = p->cgd.velocity.length();
p->cgd.life = (length / speed) * 1000.0f;
}
// global fading is based on the number of animations in the
// current_entity's animation
if (current_entity) {
if (m_spawnthing->cgd.flags & (T_GLOBALFADEIN | T_GLOBALFADEOUT)) {
2023-07-05 21:24:23 +02:00
int numframes = cgi.Anim_NumFrames(current_tiki, current_entity->frameInfo[0].index);
p->cgd.alpha = (float)current_entity->wasframe / (float)numframes;
if (m_spawnthing->cgd.flags & T_GLOBALFADEOUT) {
p->cgd.alpha = 1.0f - p->cgd.alpha;
}
}
}
// Make sure to not spawn the world model as a tempmodel
// Of course it would become extremely confusing...
2023-07-05 21:24:23 +02:00
if (!(p->cgd.flags & T_DLIGHT) && p->ent.reType == RT_MODEL && !p->ent.hModel) {
Com_Printf("^~^~^ not spawning tempmodel because it is using the world as a brush model\n");
FreeTempModel(p);
break;
}
if (m_spawnthing->cgd.flags2 & T2_RELATIVEANGLES) {
float mat[3][3];
MatrixMultiply(m_spawnthing->axis, p->ent.axis, mat);
VectorCopy(mat[0], p->ent.axis[0]);
VectorCopy(mat[1], p->ent.axis[1]);
VectorCopy(mat[2], p->ent.axis[2]);
MatrixToEulerAngles(p->ent.axis, p->cgd.angles);
}
}
}