/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * ======================================================================= * * This file implements all temporary (dynamic created) entities * * ======================================================================= */ #include "header/client.h" typedef enum { ex_free, ex_explosion, ex_misc, ex_flash, ex_mflash, ex_poly, ex_poly2 } exptype_t; typedef struct { exptype_t type; entity_t ent; int frames; float light; vec3_t lightcolor; float start; int baseframe; } explosion_t; #define MAX_EXPLOSIONS 64 #define MAX_BEAMS 64 #define MAX_LASERS 64 explosion_t cl_explosions[MAX_EXPLOSIONS]; typedef struct { int entity; int dest_entity; struct model_s *model; int endtime; vec3_t offset; vec3_t start, end; } beam_t; beam_t cl_beams[MAX_BEAMS]; beam_t cl_playerbeams[MAX_BEAMS]; typedef struct { entity_t ent; int endtime; } laser_t; laser_t cl_lasers[MAX_LASERS]; cl_sustain_t cl_sustains[MAX_SUSTAINS]; extern void CL_TeleportParticles(vec3_t org); void CL_BlasterParticles(vec3_t org, vec3_t dir); void CL_BFGExplosionParticles(vec3_t org); void CL_BlueBlasterParticles(vec3_t org, vec3_t dir); void CL_ExplosionParticles(vec3_t org); void CL_Explosion_Particle(vec3_t org, float size, qboolean large, qboolean rocket); #define EXPLOSION_PARTICLES(x) CL_ExplosionParticles((x)); struct sfx_s *cl_sfx_ric1; struct sfx_s *cl_sfx_ric2; struct sfx_s *cl_sfx_ric3; struct sfx_s *cl_sfx_lashit; struct sfx_s *cl_sfx_spark5; struct sfx_s *cl_sfx_spark6; struct sfx_s *cl_sfx_spark7; struct sfx_s *cl_sfx_railg; struct sfx_s *cl_sfx_rockexp; struct sfx_s *cl_sfx_grenexp; struct sfx_s *cl_sfx_watrexp; struct sfx_s *cl_sfx_plasexp; struct sfx_s *cl_sfx_footsteps[4]; struct model_s *cl_mod_explode; struct model_s *cl_mod_smoke; struct model_s *cl_mod_flash; struct model_s *cl_mod_parasite_segment; struct model_s *cl_mod_grapple_cable; struct model_s *cl_mod_parasite_tip; struct model_s *cl_mod_explo4; struct model_s *cl_mod_bfg_explo; struct model_s *cl_mod_powerscreen; struct model_s *cl_mod_plasmaexplo; struct sfx_s *cl_sfx_lightning; struct sfx_s *cl_sfx_disrexp; struct model_s *cl_mod_lightning; struct model_s *cl_mod_heatbeam; struct model_s *cl_mod_monster_heatbeam; struct model_s *cl_mod_explo4_big; void CL_RegisterTEntSounds(void) { int i; char name[MAX_QPATH]; cl_sfx_ric1 = S_RegisterSound("world/ric1.wav"); cl_sfx_ric2 = S_RegisterSound("world/ric2.wav"); cl_sfx_ric3 = S_RegisterSound("world/ric3.wav"); cl_sfx_lashit = S_RegisterSound("weapons/lashit.wav"); cl_sfx_spark5 = S_RegisterSound("world/spark5.wav"); cl_sfx_spark6 = S_RegisterSound("world/spark6.wav"); cl_sfx_spark7 = S_RegisterSound("world/spark7.wav"); cl_sfx_railg = S_RegisterSound("weapons/railgf1a.wav"); cl_sfx_rockexp = S_RegisterSound("weapons/rocklx1a.wav"); cl_sfx_grenexp = S_RegisterSound("weapons/grenlx1a.wav"); cl_sfx_watrexp = S_RegisterSound("weapons/xpld_wat.wav"); S_RegisterSound("player/land1.wav"); S_RegisterSound("player/fall2.wav"); S_RegisterSound("player/fall1.wav"); for (i = 0; i < 4; i++) { Com_sprintf(name, sizeof(name), "player/step%i.wav", i + 1); cl_sfx_footsteps[i] = S_RegisterSound(name); } cl_sfx_lightning = S_RegisterSound("weapons/tesla.wav"); cl_sfx_disrexp = S_RegisterSound("weapons/disrupthit.wav"); } void CL_RegisterTEntModels(void) { cl_mod_explode = re.RegisterModel("models/objects/explode/tris.md2"); cl_mod_smoke = re.RegisterModel("models/objects/smoke/tris.md2"); cl_mod_flash = re.RegisterModel("models/objects/flash/tris.md2"); cl_mod_parasite_segment = re.RegisterModel("models/monsters/parasite/segment/tris.md2"); cl_mod_grapple_cable = re.RegisterModel("models/ctf/segment/tris.md2"); cl_mod_parasite_tip = re.RegisterModel("models/monsters/parasite/tip/tris.md2"); cl_mod_explo4 = re.RegisterModel("models/objects/r_explode/tris.md2"); cl_mod_bfg_explo = re.RegisterModel("sprites/s_bfg2.sp2"); cl_mod_powerscreen = re.RegisterModel("models/items/armor/effect/tris.md2"); re.RegisterModel("models/objects/laser/tris.md2"); re.RegisterModel("models/objects/grenade2/tris.md2"); re.RegisterModel("models/weapons/v_machn/tris.md2"); re.RegisterModel("models/weapons/v_handgr/tris.md2"); re.RegisterModel("models/weapons/v_shotg2/tris.md2"); re.RegisterModel("models/objects/gibs/bone/tris.md2"); re.RegisterModel("models/objects/gibs/sm_meat/tris.md2"); re.RegisterModel("models/objects/gibs/bone2/tris.md2"); re.RegisterPic("w_machinegun"); re.RegisterPic("a_bullets"); re.RegisterPic("i_health"); re.RegisterPic("a_grenades"); cl_mod_explo4_big = re.RegisterModel("models/objects/r_explode2/tris.md2"); cl_mod_lightning = re.RegisterModel("models/proj/lightning/tris.md2"); cl_mod_heatbeam = re.RegisterModel("models/proj/beam/tris.md2"); cl_mod_monster_heatbeam = re.RegisterModel("models/proj/widowbeam/tris.md2"); } void CL_ClearTEnts(void) { memset(cl_beams, 0, sizeof(cl_beams)); memset(cl_explosions, 0, sizeof(cl_explosions)); memset(cl_lasers, 0, sizeof(cl_lasers)); memset(cl_playerbeams, 0, sizeof(cl_playerbeams)); memset(cl_sustains, 0, sizeof(cl_sustains)); } explosion_t * CL_AllocExplosion(void) { int i; float time; int index; for (i = 0; i < MAX_EXPLOSIONS; i++) { if (cl_explosions[i].type == ex_free) { memset(&cl_explosions[i], 0, sizeof(cl_explosions[i])); return &cl_explosions[i]; } } /* find the oldest explosion */ time = (float)cl.time; index = 0; for (i = 0; i < MAX_EXPLOSIONS; i++) { if (cl_explosions[i].start < time) { time = cl_explosions[i].start; index = i; } } memset(&cl_explosions[index], 0, sizeof(cl_explosions[index])); return &cl_explosions[index]; } void CL_SmokeAndFlash(vec3_t origin) { explosion_t *ex; ex = CL_AllocExplosion(); VectorCopy(origin, ex->ent.origin); ex->type = ex_misc; ex->frames = 4; ex->ent.flags = RF_TRANSLUCENT; ex->start = cl.frame.servertime - 100.0f; ex->ent.model = cl_mod_smoke; ex = CL_AllocExplosion(); VectorCopy(origin, ex->ent.origin); ex->type = ex_flash; ex->ent.flags = RF_FULLBRIGHT; ex->frames = 2; ex->start = cl.frame.servertime - 100.0f; ex->ent.model = cl_mod_flash; } void CL_ParseParticles(void) { int color, count; vec3_t pos, dir; MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); color = MSG_ReadByte(&net_message); count = MSG_ReadByte(&net_message); CL_ParticleEffect(pos, dir, color, count); } void CL_ParseBeam(struct model_s *model) { int ent; vec3_t start, end; beam_t *b; int i; ent = MSG_ReadShort(&net_message); MSG_ReadPos(&net_message, start); MSG_ReadPos(&net_message, end); /* override any beam with the same entity */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (b->entity == ent) { b->entity = ent; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorClear(b->offset); return; } } /* find a free beam */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (!b->model || (b->endtime < cl.time)) { b->entity = ent; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorClear(b->offset); return; } } Com_Printf("beam list overflow!\n"); return; } void CL_ParseBeam2(struct model_s *model) { int ent; vec3_t start, end, offset; beam_t *b; int i; ent = MSG_ReadShort(&net_message); MSG_ReadPos(&net_message, start); MSG_ReadPos(&net_message, end); MSG_ReadPos(&net_message, offset); /* override any beam with the same entity */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (b->entity == ent) { b->entity = ent; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorCopy(offset, b->offset); return; } } /* find a free beam */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (!b->model || (b->endtime < cl.time)) { b->entity = ent; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorCopy(offset, b->offset); return; } } Com_Printf("beam list overflow!\n"); return; } /* * adds to the cl_playerbeam array instead of the cl_beams array */ void CL_ParsePlayerBeam(struct model_s *model) { int ent; vec3_t start, end, offset; beam_t *b; int i; ent = MSG_ReadShort(&net_message); MSG_ReadPos(&net_message, start); MSG_ReadPos(&net_message, end); /* network optimization */ if (model == cl_mod_heatbeam) { VectorSet(offset, 2, 7, -3); } else if (model == cl_mod_monster_heatbeam) { model = cl_mod_heatbeam; VectorSet(offset, 0, 0, 0); } else { MSG_ReadPos(&net_message, offset); } /* Override any beam with the same entity For player beams, we only want one per player (entity) so... */ for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) { if (b->entity == ent) { b->entity = ent; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorCopy(offset, b->offset); return; } } /* find a free beam */ for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) { if (!b->model || (b->endtime < cl.time)) { b->entity = ent; b->model = model; b->endtime = cl.time + 100; /* this needs to be 100 to prevent multiple heatbeams */ VectorCopy(start, b->start); VectorCopy(end, b->end); VectorCopy(offset, b->offset); return; } } Com_Printf("beam list overflow!\n"); return; } int CL_ParseLightning(struct model_s *model) { int srcEnt, destEnt; vec3_t start, end; beam_t *b; int i; srcEnt = MSG_ReadShort(&net_message); destEnt = MSG_ReadShort(&net_message); MSG_ReadPos(&net_message, start); MSG_ReadPos(&net_message, end); /* override any beam with the same source AND destination entities */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if ((b->entity == srcEnt) && (b->dest_entity == destEnt)) { b->entity = srcEnt; b->dest_entity = destEnt; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorClear(b->offset); return srcEnt; } } /* find a free beam */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (!b->model || (b->endtime < cl.time)) { b->entity = srcEnt; b->dest_entity = destEnt; b->model = model; b->endtime = cl.time + 200; VectorCopy(start, b->start); VectorCopy(end, b->end); VectorClear(b->offset); return srcEnt; } } Com_Printf("beam list overflow!\n"); return srcEnt; } void CL_ParseLaser(int colors) { vec3_t start; vec3_t end; laser_t *l; int i; MSG_ReadPos(&net_message, start); MSG_ReadPos(&net_message, end); for (i = 0, l = cl_lasers; i < MAX_LASERS; i++, l++) { if (l->endtime < cl.time) { l->ent.flags = RF_TRANSLUCENT | RF_BEAM; VectorCopy(start, l->ent.origin); VectorCopy(end, l->ent.oldorigin); l->ent.alpha = 0.30f; l->ent.skinnum = (colors >> ((randk() % 4) * 8)) & 0xff; l->ent.model = NULL; l->ent.frame = 4; l->endtime = cl.time + 100; return; } } } void CL_ParseSteam(void) { vec3_t pos, dir; int id, i; int r; int cnt; int color; int magnitude; cl_sustain_t *s, *free_sustain; id = MSG_ReadShort(&net_message); /* an id of -1 is an instant effect */ if (id != -1) /* sustains */ { free_sustain = NULL; for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) { if (s->id == 0) { free_sustain = s; break; } } if (free_sustain) { s->id = id; s->count = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, s->org); MSG_ReadDir(&net_message, s->dir); r = MSG_ReadByte(&net_message); s->color = r & 0xff; s->magnitude = MSG_ReadShort(&net_message); s->endtime = cl.time + MSG_ReadLong(&net_message); s->think = CL_ParticleSteamEffect2; s->thinkinterval = 100; s->nextthink = cl.time; } else { MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); MSG_ReadByte(&net_message); MSG_ReadShort(&net_message); MSG_ReadLong(&net_message); /* really interval */ } } else { /* instant */ cnt = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); r = MSG_ReadByte(&net_message); magnitude = MSG_ReadShort(&net_message); color = r & 0xff; CL_ParticleSteamEffect(pos, dir, color, cnt, magnitude); } } void CL_ParseWidow(void) { vec3_t pos; int id, i; cl_sustain_t *s, *free_sustain; id = MSG_ReadShort(&net_message); free_sustain = NULL; for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) { if (s->id == 0) { free_sustain = s; break; } } if (free_sustain) { s->id = id; MSG_ReadPos(&net_message, s->org); s->endtime = cl.time + 2100; s->think = CL_Widowbeamout; s->thinkinterval = 1; s->nextthink = cl.time; } else { /* no free sustains */ MSG_ReadPos(&net_message, pos); } } void CL_ParseNuke(void) { vec3_t pos; int i; cl_sustain_t *s, *free_sustain; free_sustain = NULL; for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) { if (s->id == 0) { free_sustain = s; break; } } if (free_sustain) { s->id = 21000; MSG_ReadPos(&net_message, s->org); s->endtime = cl.time + 1000; s->think = CL_Nukeblast; s->thinkinterval = 1; s->nextthink = cl.time; } else { /* no free sustains */ MSG_ReadPos(&net_message, pos); } } static byte splash_color[] = {0x00, 0xe0, 0xb0, 0x50, 0xd0, 0xe0, 0xe8}; void CL_ParseTEnt(void) { int type; vec3_t pos, pos2, dir; explosion_t *ex; int cnt; int color; int r; int ent; int magnitude; type = MSG_ReadByte(&net_message); switch (type) { case TE_BLOOD: /* bullet hitting flesh */ MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_ParticleEffect(pos, dir, 0xe8, 60); break; case TE_GUNSHOT: /* bullet hitting wall */ case TE_SPARKS: case TE_BULLET_SPARKS: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); if (type == TE_GUNSHOT) { CL_ParticleEffect(pos, dir, 0, 40); } else { CL_ParticleEffect(pos, dir, 0xe0, 6); } if (type != TE_SPARKS) { CL_SmokeAndFlash(pos); /* impact sound */ cnt = randk() & 15; if (cnt == 1) { S_StartSound(pos, 0, 0, cl_sfx_ric1, 1, ATTN_NORM, 0); } else if (cnt == 2) { S_StartSound(pos, 0, 0, cl_sfx_ric2, 1, ATTN_NORM, 0); } else if (cnt == 3) { S_StartSound(pos, 0, 0, cl_sfx_ric3, 1, ATTN_NORM, 0); } } break; case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); if (type == TE_SCREEN_SPARKS) { CL_ParticleEffect(pos, dir, 0xd0, 40); } else { CL_ParticleEffect(pos, dir, 0xb0, 40); } S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_SHOTGUN: /* bullet hitting wall */ MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_ParticleEffect(pos, dir, 0, 20); CL_SmokeAndFlash(pos); break; case TE_SPLASH: /* bullet hitting water */ cnt = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); r = MSG_ReadByte(&net_message); if (r > 6) { color = 0x00; } else { color = splash_color[r]; } CL_ParticleEffect(pos, dir, color, cnt); if (r == SPLASH_SPARKS) { r = randk() & 3; if (r == 0) { S_StartSound(pos, 0, 0, cl_sfx_spark5, 1, ATTN_STATIC, 0); } else if (r == 1) { S_StartSound(pos, 0, 0, cl_sfx_spark6, 1, ATTN_STATIC, 0); } else { S_StartSound(pos, 0, 0, cl_sfx_spark7, 1, ATTN_STATIC, 0); } } break; case TE_LASER_SPARKS: cnt = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); color = MSG_ReadByte(&net_message); CL_ParticleEffect2(pos, dir, color, cnt); break; case TE_BLUEHYPERBLASTER: MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, dir); CL_BlasterParticles(pos, dir); break; case TE_BLASTER: /* blaster hitting wall */ MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_BlasterParticles(pos, dir); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->ent.angles[0] = (float)acos(dir[2]) / M_PI * 180; if (dir[0]) { ex->ent.angles[1] = (float)atan2(dir[1], dir[0]) / M_PI * 180; } else if (dir[1] > 0) { ex->ent.angles[1] = 90; } else if (dir[1] < 0) { ex->ent.angles[1] = 270; } else { ex->ent.angles[1] = 0; } ex->type = ex_misc; ex->ent.flags = 0; ex->start = cl.frame.servertime - 100.0f; ex->light = 150; ex->lightcolor[0] = 1; ex->lightcolor[1] = 1; ex->ent.model = cl_mod_explode; ex->frames = 4; S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_RAILTRAIL: /* railgun effect */ MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, pos2); CL_RailTrail(pos, pos2); S_StartSound(pos2, 0, 0, cl_sfx_railg, 1, ATTN_NORM, 0); break; case TE_EXPLOSION2: case TE_GRENADE_EXPLOSION: case TE_GRENADE_EXPLOSION_WATER: MSG_ReadPos(&net_message, pos); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_poly; ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW; ex->start = cl.frame.servertime - 100.0f; ex->light = 350; ex->lightcolor[0] = 1.0; ex->lightcolor[1] = 0.5; ex->lightcolor[2] = 0.5; ex->ent.model = cl_mod_explo4; ex->frames = 19; ex->baseframe = 30; ex->ent.angles[1] = (float)(randk() % 360); EXPLOSION_PARTICLES(pos); if (type == TE_GRENADE_EXPLOSION_WATER) { S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); } else { S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0); } break; case TE_PLASMA_EXPLOSION: MSG_ReadPos(&net_message, pos); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_poly; ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW; ex->start = cl.frame.servertime - 100.0f; ex->light = 350; ex->lightcolor[0] = 1.0; ex->lightcolor[1] = 0.5; ex->lightcolor[2] = 0.5; ex->ent.angles[1] = (float)(randk() % 360); ex->ent.model = cl_mod_explo4; if (frandk() < 0.5) { ex->baseframe = 15; } ex->frames = 15; EXPLOSION_PARTICLES(pos); S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); break; case TE_EXPLOSION1_BIG: case TE_EXPLOSION1_NP: case TE_EXPLOSION1: case TE_ROCKET_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: MSG_ReadPos(&net_message, pos); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_poly; ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW; ex->start = cl.frame.servertime - 100.0f; ex->light = 350; ex->lightcolor[0] = 1.0; ex->lightcolor[1] = 0.5; ex->lightcolor[2] = 0.5; ex->ent.angles[1] = (float)(randk() % 360); if (type != TE_EXPLOSION1_BIG) { ex->ent.model = cl_mod_explo4; } else { ex->ent.model = cl_mod_explo4_big; } if (frandk() < 0.5) { ex->baseframe = 15; } ex->frames = 15; if ((type != TE_EXPLOSION1_BIG) && (type != TE_EXPLOSION1_NP)) { EXPLOSION_PARTICLES(pos); } if (type == TE_ROCKET_EXPLOSION_WATER) { S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); } else { S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); } break; case TE_BFG_EXPLOSION: MSG_ReadPos(&net_message, pos); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_poly; ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW; ex->start = cl.frame.servertime - 100.0f; ex->light = 350; ex->lightcolor[0] = 0.0; ex->lightcolor[1] = 1.0; ex->lightcolor[2] = 0.0; ex->ent.model = cl_mod_bfg_explo; ex->ent.flags |= RF_TRANSLUCENT; ex->ent.alpha = 0.30f; ex->frames = 4; break; case TE_BFG_BIGEXPLOSION: MSG_ReadPos(&net_message, pos); CL_BFGExplosionParticles(pos); break; case TE_BFG_LASER: CL_ParseLaser(0xd0d1d2d3); break; case TE_BUBBLETRAIL: MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, pos2); CL_BubbleTrail(pos, pos2); break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: CL_ParseBeam(cl_mod_parasite_segment); break; case TE_BOSSTPORT: /* boss teleporting to station */ MSG_ReadPos(&net_message, pos); CL_BigTeleportParticles(pos); S_StartSound(pos, 0, 0, S_RegisterSound( "misc/bigtele.wav"), 1, ATTN_NONE, 0); break; case TE_GRAPPLE_CABLE: CL_ParseBeam2(cl_mod_grapple_cable); break; case TE_WELDING_SPARKS: cnt = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); color = MSG_ReadByte(&net_message); CL_ParticleEffect2(pos, dir, color, cnt); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_flash; ex->ent.flags = RF_BEAM; ex->start = cl.frame.servertime - 0.1f; ex->light = 100 + (float)(randk() % 75); ex->lightcolor[0] = 1.0f; ex->lightcolor[1] = 1.0f; ex->lightcolor[2] = 0.3f; ex->ent.model = cl_mod_flash; ex->frames = 2; break; case TE_GREENBLOOD: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_ParticleEffect2(pos, dir, 0xdf, 30); break; case TE_TUNNEL_SPARKS: cnt = MSG_ReadByte(&net_message); MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); color = MSG_ReadByte(&net_message); CL_ParticleEffect3(pos, dir, color, cnt); break; case TE_BLASTER2: case TE_FLECHETTE: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); if (type == TE_BLASTER2) { CL_BlasterParticles2(pos, dir, 0xd0); } else { CL_BlasterParticles2(pos, dir, 0x6f); } ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->ent.angles[0] = (float)acos(dir[2]) / M_PI * 180; if (dir[0]) { ex->ent.angles[1] = (float)atan2(dir[1], dir[0]) / M_PI * 180; } else if (dir[1] > 0) { ex->ent.angles[1] = 90; } else if (dir[1] < 0) { ex->ent.angles[1] = 270; } else { ex->ent.angles[1] = 0; } ex->type = ex_misc; ex->ent.flags = RF_FULLBRIGHT | RF_TRANSLUCENT; if (type == TE_BLASTER2) { ex->ent.skinnum = 1; } else /* flechette */ { ex->ent.skinnum = 2; } ex->start = cl.frame.servertime - 100.0f; ex->light = 150; if (type == TE_BLASTER2) { ex->lightcolor[1] = 1; } else { /* flechette */ ex->lightcolor[0] = 0.19f; ex->lightcolor[1] = 0.41f; ex->lightcolor[2] = 0.75f; } ex->ent.model = cl_mod_explode; ex->frames = 4; S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_LIGHTNING: ent = CL_ParseLightning(cl_mod_lightning); S_StartSound(NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, ATTN_NORM, 0); break; case TE_DEBUGTRAIL: MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, pos2); CL_DebugTrail(pos, pos2); break; case TE_PLAIN_EXPLOSION: MSG_ReadPos(&net_message, pos); ex = CL_AllocExplosion(); VectorCopy(pos, ex->ent.origin); ex->type = ex_poly; ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW; ex->start = cl.frame.servertime - 100.0f; ex->light = 350; ex->lightcolor[0] = 1.0; ex->lightcolor[1] = 0.5; ex->lightcolor[2] = 0.5; ex->ent.angles[1] = randk() % 360; ex->ent.model = cl_mod_explo4; if (frandk() < 0.5) { ex->baseframe = 15; } ex->frames = 15; if (type == TE_ROCKET_EXPLOSION_WATER) { S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); } else { S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); } break; case TE_FLASHLIGHT: MSG_ReadPos(&net_message, pos); ent = MSG_ReadShort(&net_message); CL_Flashlight(ent, pos); break; case TE_FORCEWALL: MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, pos2); color = MSG_ReadByte(&net_message); CL_ForceWall(pos, pos2, color); break; case TE_HEATBEAM: CL_ParsePlayerBeam(cl_mod_heatbeam); break; case TE_MONSTER_HEATBEAM: CL_ParsePlayerBeam(cl_mod_monster_heatbeam); break; case TE_HEATBEAM_SPARKS: cnt = 50; MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); r = 8; magnitude = 60; color = r & 0xff; CL_ParticleSteamEffect(pos, dir, color, cnt, magnitude); S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_HEATBEAM_STEAM: cnt = 20; MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); color = 0xe0; magnitude = 60; CL_ParticleSteamEffect(pos, dir, color, cnt, magnitude); S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_STEAM: CL_ParseSteam(); break; case TE_BUBBLETRAIL2: MSG_ReadPos(&net_message, pos); MSG_ReadPos(&net_message, pos2); CL_BubbleTrail2(pos, pos2, 8); S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_MOREBLOOD: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_ParticleEffect(pos, dir, 0xe8, 250); break; case TE_CHAINFIST_SMOKE: dir[0] = 0; dir[1] = 0; dir[2] = 1; MSG_ReadPos(&net_message, pos); CL_ParticleSmokeEffect(pos, dir, 0, 20, 20); break; case TE_ELECTRIC_SPARKS: MSG_ReadPos(&net_message, pos); MSG_ReadDir(&net_message, dir); CL_ParticleEffect(pos, dir, 0x75, 40); S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); break; case TE_TRACKER_EXPLOSION: MSG_ReadPos(&net_message, pos); CL_ColorFlash(pos, 0, 150, -1, -1, -1); CL_ColorExplosionParticles(pos, 0, 1); S_StartSound(pos, 0, 0, cl_sfx_disrexp, 1, ATTN_NORM, 0); break; case TE_TELEPORT_EFFECT: case TE_DBALL_GOAL: MSG_ReadPos(&net_message, pos); CL_TeleportParticles(pos); break; case TE_WIDOWBEAMOUT: CL_ParseWidow(); break; case TE_NUKEBLAST: CL_ParseNuke(); break; case TE_WIDOWSPLASH: MSG_ReadPos(&net_message, pos); CL_WidowSplash(pos); break; default: Com_Error(ERR_DROP, "CL_ParseTEnt: bad type"); } } void CL_AddBeams(void) { int i, j; beam_t *b; vec3_t dist, org; float d; entity_t ent; float yaw, pitch; float forward; float len, steps; float model_length; /* update beams */ for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) { if (!b->model || (b->endtime < cl.time)) { continue; } /* if coming from the player, update the start position */ if (b->entity == cl.playernum + 1) /* entity 0 is the world */ { VectorCopy(cl.refdef.vieworg, b->start); b->start[2] -= 22; /* adjust for view height */ } VectorAdd(b->start, b->offset, org); /* calculate pitch and yaw */ VectorSubtract(b->end, org, dist); if ((dist[1] == 0) && (dist[0] == 0)) { yaw = 0; if (dist[2] > 0) { pitch = 90; } else { pitch = 270; } } else { if (dist[0]) { yaw = ((float)atan2(dist[1], dist[0]) * 180 / M_PI); } else if (dist[1] > 0) { yaw = 90; } else { yaw = 270; } if (yaw < 0) { yaw += 360; } forward = (float)sqrt(dist[0] * dist[0] + dist[1] * dist[1]); pitch = ((float)atan2(dist[2], forward) * -180.0 / M_PI); if (pitch < 0) { pitch += 360.0; } } /* add new entities for the beams */ d = VectorNormalize(dist); memset(&ent, 0, sizeof(ent)); if (b->model == cl_mod_lightning) { model_length = 35.0; d -= 20.0; /* correction so it doesn't end in middle of tesla */ } else { model_length = 30.0; } steps = (float)ceil(d / model_length); len = (d - model_length) / (steps - 1); /* special case for lightning model .. if the real length is shorter than the model, flip it around & draw it from the end to the start. This prevents the model from going through the tesla mine (instead it goes through the target) */ if ((b->model == cl_mod_lightning) && (d <= model_length)) { VectorCopy(b->end, ent.origin); ent.model = b->model; ent.flags = RF_FULLBRIGHT; ent.angles[0] = pitch; ent.angles[1] = yaw; ent.angles[2] = (float)(randk() % 360); V_AddEntity(&ent); return; } while (d > 0) { VectorCopy(org, ent.origin); ent.model = b->model; if (b->model == cl_mod_lightning) { ent.flags = RF_FULLBRIGHT; ent.angles[0] = -pitch; ent.angles[1] = yaw + 180.0f; ent.angles[2] = (float)(randk() % 360); } else { ent.angles[0] = pitch; ent.angles[1] = yaw; ent.angles[2] = (float)(randk() % 360); } V_AddEntity(&ent); for (j = 0; j < 3; j++) { org[j] += dist[j] * len; } d -= model_length; } } } extern cvar_t *hand; void CL_AddPlayerBeams(void) { int i, j; beam_t *b; vec3_t dist, org; float d; entity_t ent; float yaw, pitch; float forward; float len, steps; int framenum; float model_length; float hand_multiplier; frame_t *oldframe; player_state_t *ps, *ops; framenum = 0; if (hand) { if (hand->value == 2) { hand_multiplier = 0; } else if (hand->value == 1) { hand_multiplier = -1; } else { hand_multiplier = 1; } } else { hand_multiplier = 1; } /* update beams */ for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) { vec3_t f, r, u; if (!b->model || (b->endtime < cl.time)) { continue; } if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) { /* if coming from the player, update the start position */ if (b->entity == cl.playernum + 1) { /* set up gun position */ ps = &cl.frame.playerstate; j = (cl.frame.serverframe - 1) & UPDATE_MASK; oldframe = &cl.frames[j]; if ((oldframe->serverframe != cl.frame.serverframe - 1) || !oldframe->valid) { oldframe = &cl.frame; /* previous frame was dropped or invalid */ } ops = &oldframe->playerstate; for (j = 0; j < 3; j++) { b->start[j] = cl.refdef.vieworg[j] + ops->gunoffset[j] + cl.lerpfrac * (ps->gunoffset[j] - ops->gunoffset[j]); } VectorMA(b->start, (hand_multiplier * b->offset[0]), cl.v_right, org); VectorMA(org, b->offset[1], cl.v_forward, org); VectorMA(org, b->offset[2], cl.v_up, org); if ((hand) && (hand->value == 2)) { VectorMA(org, -1, cl.v_up, org); } VectorCopy(cl.v_right, r); VectorCopy(cl.v_forward, f); VectorCopy(cl.v_up, u); } else { VectorCopy(b->start, org); } } else { /* if coming from the player, update the start position */ if (b->entity == cl.playernum + 1) /* entity 0 is the world */ { VectorCopy(cl.refdef.vieworg, b->start); b->start[2] -= 22; /* adjust for view height */ } VectorAdd(b->start, b->offset, org); } /* calculate pitch and yaw */ VectorSubtract(b->end, org, dist); if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum + 1)) { vec_t len; len = VectorLength(dist); VectorScale(f, len, dist); VectorMA(dist, (hand_multiplier * b->offset[0]), r, dist); VectorMA(dist, b->offset[1], f, dist); VectorMA(dist, b->offset[2], u, dist); if ((hand) && (hand->value == 2)) { VectorMA(org, -1, cl.v_up, org); } } if ((dist[1] == 0) && (dist[0] == 0)) { yaw = 0; if (dist[2] > 0) { pitch = 90; } else { pitch = 270; } } else { if (dist[0]) { yaw = ((float)atan2(dist[1], dist[0]) * 180 / M_PI); } else if (dist[1] > 0) { yaw = 90; } else { yaw = 270; } if (yaw < 0) { yaw += 360; } forward = sqrt(dist[0] * dist[0] + dist[1] * dist[1]); pitch = ((float)atan2(dist[2], forward) * -180.0 / M_PI); if (pitch < 0) { pitch += 360.0; } } if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) { if (b->entity != cl.playernum + 1) { framenum = 2; ent.angles[0] = -pitch; ent.angles[1] = yaw + 180.0f; ent.angles[2] = 0; AngleVectors(ent.angles, f, r, u); /* if it's a non-origin offset, it's a player, so use the hardcoded player offset */ if (!VectorCompare(b->offset, vec3_origin)) { VectorMA(org, -(b->offset[0]) + 1, r, org); VectorMA(org, -(b->offset[1]), f, org); VectorMA(org, -(b->offset[2]) - 10, u, org); } else { /* if it's a monster, do the particle effect */ CL_MonsterPlasma_Shell(b->start); } } else { framenum = 1; } } /* if it's the heatbeam, draw the particle effect */ if ((cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum + 1))) { CL_Heatbeam(org, dist); } /* add new entities for the beams */ d = VectorNormalize(dist); memset(&ent, 0, sizeof(ent)); if (b->model == cl_mod_heatbeam) { model_length = 32.0; } else if (b->model == cl_mod_lightning) { model_length = 35.0; d -= 20.0; /* correction so it doesn't end in middle of tesla */ } else { model_length = 30.0; } steps = ceil(d / model_length); len = (d - model_length) / (steps - 1); /* special case for lightning model .. if the real length is shorter than the model, flip it around & draw it from the end to the start. This prevents the model from going through the tesla mine (instead it goes through the target) */ if ((b->model == cl_mod_lightning) && (d <= model_length)) { VectorCopy(b->end, ent.origin); ent.model = b->model; ent.flags = RF_FULLBRIGHT; ent.angles[0] = pitch; ent.angles[1] = yaw; ent.angles[2] = (float)(randk() % 360); V_AddEntity(&ent); return; } while (d > 0) { VectorCopy(org, ent.origin); ent.model = b->model; if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) { ent.flags = RF_FULLBRIGHT; ent.angles[0] = -pitch; ent.angles[1] = yaw + 180.0f; ent.angles[2] = (float)((cl.time) % 360); ent.frame = framenum; } else if (b->model == cl_mod_lightning) { ent.flags = RF_FULLBRIGHT; ent.angles[0] = -pitch; ent.angles[1] = yaw + 180.0f; ent.angles[2] = (float)(randk() % 360); } else { ent.angles[0] = pitch; ent.angles[1] = yaw; ent.angles[2] = (float)(randk() % 360); } V_AddEntity(&ent); for (j = 0; j < 3; j++) { org[j] += dist[j] * len; } d -= model_length; } } } void CL_AddExplosions(void) { entity_t *ent; int i; explosion_t *ex; float frac; int f; memset(&ent, 0, sizeof(ent)); for (i = 0, ex = cl_explosions; i < MAX_EXPLOSIONS; i++, ex++) { if (ex->type == ex_free) { continue; } frac = (cl.time - ex->start) / 100.0; f = (int)floor(frac); ent = &ex->ent; switch (ex->type) { case ex_mflash: if (f >= ex->frames - 1) { ex->type = ex_free; } break; case ex_misc: if (f >= ex->frames - 1) { ex->type = ex_free; break; } ent->alpha = 1.0f - frac / (ex->frames - 1); break; case ex_flash: if (f >= 1) { ex->type = ex_free; break; } ent->alpha = 1.0; break; case ex_poly: if (f >= ex->frames - 1) { ex->type = ex_free; break; } ent->alpha = (16.0f - (float)f) / 16.0f; if (f < 10) { ent->skinnum = (f >> 1); if (ent->skinnum < 0) { ent->skinnum = 0; } } else { ent->flags |= RF_TRANSLUCENT; if (f < 13) { ent->skinnum = 5; } else { ent->skinnum = 6; } } break; case ex_poly2: if (f >= ex->frames - 1) { ex->type = ex_free; break; } ent->alpha = (5.0 - (float)f) / 5.0; ent->skinnum = 0; ent->flags |= RF_TRANSLUCENT; break; default: break; } if (ex->type == ex_free) { continue; } if (ex->light) { V_AddLight(ent->origin, ex->light * ent->alpha, ex->lightcolor[0], ex->lightcolor[1], ex->lightcolor[2]); } VectorCopy(ent->origin, ent->oldorigin); if (f < 0) { f = 0; } ent->frame = ex->baseframe + f + 1; ent->oldframe = ex->baseframe + f; ent->backlerp = 1.0f - cl.lerpfrac; V_AddEntity(ent); } } void CL_AddLasers(void) { laser_t *l; int i; for (i = 0, l = cl_lasers; i < MAX_LASERS; i++, l++) { if (l->endtime >= cl.time) { V_AddEntity(&l->ent); } } } void CL_ProcessSustain() { cl_sustain_t *s; int i; for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) { if (s->id) { if ((s->endtime >= cl.time) && (cl.time >= s->nextthink)) { s->think(s); } else if (s->endtime < cl.time) { s->id = 0; } } } } void CL_AddTEnts(void) { CL_AddBeams(); CL_AddPlayerBeams(); CL_AddExplosions(); CL_AddLasers(); CL_ProcessSustain(); }