mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-25 05:11:35 +00:00
4b7a1e3fb0
Co-authored-by: Peter0x44 <peter0x44@disroot.org>
3351 lines
86 KiB
C
3351 lines
86 KiB
C
/*
|
|
Copyright (C) 1996-1997 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.
|
|
|
|
*/
|
|
// cl_tent.c -- client side temporary entities
|
|
|
|
#include "quakedef.h"
|
|
#include "particles.h"
|
|
entity_state_t *CL_FindPacketEntity(int num);
|
|
|
|
#ifdef Q2CLIENT
|
|
static const char *q2efnames[] =
|
|
{
|
|
"TEQ2_GUNSHOT",
|
|
"TEQ2_BLOOD",
|
|
"TEQ2_BLASTER",
|
|
"TEQ2_RAILTRAIL",
|
|
"TEQ2_SHOTGUN",
|
|
"TEQ2_EXPLOSION1",
|
|
"TEQ2_EXPLOSION2",
|
|
"TEQ2_ROCKET_EXPLOSION",
|
|
"TEQ2_GRENADE_EXPLOSION",
|
|
"TEQ2_SPARKS",
|
|
NULL,//"TEQ2_SPLASH",
|
|
"TEQ2_BUBBLETRAIL",
|
|
"TEQ2_SCREEN_SPARKS",
|
|
"TEQ2_SHIELD_SPARKS",
|
|
"TEQ2_BULLET_SPARKS",
|
|
NULL,//"TEQ2_LASER_SPARKS",
|
|
NULL,//"TEQ2_PARASITE_ATTACK",
|
|
"TEQ2_ROCKET_EXPLOSION_WATER",
|
|
"TEQ2_GRENADE_EXPLOSION_WATER",
|
|
NULL,//"TEQ2_MEDIC_CABLE_ATTACK",
|
|
"TEQ2_BFG_EXPLOSION",
|
|
"TEQ2_BFG_BIGEXPLOSION",
|
|
"TEQ2_BOSSTPORT",
|
|
NULL,//"TEQ2_BFG_LASER",
|
|
NULL,//"TEQ2_GRAPPLE_CABLE",
|
|
"TEQ2_WELDING_SPARKS",
|
|
"TEQ2_GREENBLOOD",
|
|
"TEQ2_BLUEHYPERBLASTER",
|
|
"TEQ2_PLASMA_EXPLOSION",
|
|
"TEQ2_TUNNEL_SPARKS",
|
|
"TEQ2_BLASTER2",
|
|
"TEQ2_RAILTRAIL2", //not implemented in vanilla
|
|
NULL,//"TEQ2_FLAME", //not implemented in vanilla
|
|
NULL,//"TEQ2_LIGHTNING",
|
|
"TEQ2_DEBUGTRAIL",
|
|
"TEQ2_PLAIN_EXPLOSION",
|
|
"TEQ2_FLASHLIGHT",
|
|
"TEQ2_FORCEWALL",
|
|
NULL,//"TEQ2_HEATBEAM",
|
|
NULL,//"TEQ2_MONSTER_HEATBEAM",
|
|
NULL,//"TEQ2_STEAM",
|
|
"TEQ2_BUBBLETRAIL2",
|
|
"TEQ2_MOREBLOOD",
|
|
"TEQ2_HEATBEAM_SPARKS",
|
|
"TEQ2_HEATBEAM_STEAM",
|
|
"TEQ2_CHAINFIST_SMOKE",
|
|
"TEQ2_ELECTRIC_SPARKS",
|
|
"TEQ2_TRACKER_EXPLOSION",
|
|
"TEQ2_TELEPORT_EFFECT",
|
|
"TEQ2_DBALL_GOAL",
|
|
NULL,//"TEQ2_WIDOWBEAMOUT",
|
|
NULL,//"TEQ2_NUKEBLAST",
|
|
"TEQ2_WIDOWSPLASH",
|
|
"TEQ2_EXPLOSION1_BIG",
|
|
"TEQ2_EXPLOSION1_NP",
|
|
"TEQ2_FLECHETTE",
|
|
|
|
//RERELEASE
|
|
"TEQ2EX_BLUEHYPERBLASTER",
|
|
"TEQ2EX_BFGZAP",
|
|
"TEQ2EX_BERSERK_SLAM",
|
|
"TEQ2EX_GRAPPLE_CABLE_2",
|
|
"TEQ2EX_POWER_SPLASH",
|
|
"TEQ2EX_LIGHTNING_BEAM",
|
|
"TEQ2EX_EXPLOSION1_NL",
|
|
"TEQ2EX_EXPLOSION2_NL",
|
|
//RERELEASE
|
|
|
|
|
|
// NULL,//"TEQ2_CR_LEADERBLASTER",
|
|
// NULL,//"TEQ2_CR_BLASTER_MUZZLEFLASH",
|
|
// NULL,//"TEQ2_CR_BLUE_MUZZLEFLASH",
|
|
// NULL,//"TEQ2_CR_SMART_MUZZLEFLASH",
|
|
// NULL,//"TEQ2_CR_LEADERFIELD",
|
|
// NULL,//"TEQ2_CR_DEATHFIELD",
|
|
// NULL,//"TEQ2_CR_BLASTERBEAM",
|
|
// NULL,//"TEQ2_CR_STAIN",
|
|
NULL,//"TEQ2_CR_FIRE",
|
|
NULL,//"TEQ2_CR_CABLEGUT",
|
|
NULL,//"TEQ2_CR_SMOKE",
|
|
|
|
//the rest have no specific value meanings
|
|
|
|
//slashes block
|
|
"te_splashunknown",
|
|
"te_splashsparks",
|
|
"te_splashbluewater",
|
|
"te_splashbrownwater",
|
|
"te_splashslime",
|
|
"te_splashlava",
|
|
"te_splashblood",
|
|
"te_splashelect",
|
|
|
|
"TR_BLASTERTRAIL",
|
|
"TR_BLASTERTRAIL2",
|
|
"TRQ2_GIB",
|
|
"TRQ2_GREENGIB",
|
|
"TRQ2_ROCKET",
|
|
"TRQ2_GRENADE",
|
|
|
|
"TR_TRAP",
|
|
"TR_FLAG1",
|
|
"TR_FLAG2",
|
|
"TR_TAGTRAIL",
|
|
"TR_TRACKER",
|
|
"TR_IONRIPPER",
|
|
"TR_PLASMA",
|
|
|
|
"EF_BFGPARTICLES",
|
|
"EF_FLIES",
|
|
"EF_TRAP",
|
|
"EF_TRACKERSHELL",
|
|
|
|
|
|
"ev_item_respawn",
|
|
"ev_player_teleport",
|
|
"ev_footstep",
|
|
"ev_other_footstep",
|
|
"ev_ladder_step",
|
|
};
|
|
int pt_q2[sizeof(q2efnames)/sizeof(q2efnames[0])];
|
|
#endif
|
|
|
|
int
|
|
pt_muzzleflash=P_INVALID,
|
|
pt_gunshot=P_INVALID,
|
|
ptdp_gunshotquad=P_INVALID,
|
|
pt_spike=P_INVALID,
|
|
ptdp_spikequad=P_INVALID,
|
|
pt_superspike=P_INVALID,
|
|
ptdp_superspikequad=P_INVALID,
|
|
pt_wizspike=P_INVALID,
|
|
pt_knightspike=P_INVALID,
|
|
pt_explosion=P_INVALID,
|
|
ptdp_explosionquad=P_INVALID,
|
|
pt_tarexplosion=P_INVALID,
|
|
pt_teleportsplash=P_INVALID,
|
|
pt_lavasplash=P_INVALID,
|
|
ptdp_smallflash=P_INVALID,
|
|
ptdp_flamejet=P_INVALID,
|
|
ptdp_flame=P_INVALID,
|
|
ptdp_blood=P_INVALID,
|
|
ptdp_spark=P_INVALID,
|
|
ptdp_plasmaburn=P_INVALID,
|
|
ptdp_tei_g3=P_INVALID,
|
|
ptdp_tei_smoke=P_INVALID,
|
|
ptdp_tei_bigexplosion=P_INVALID,
|
|
ptdp_tei_plasmahit=P_INVALID,
|
|
ptdp_stardust=P_INVALID,
|
|
rt_rocket=P_INVALID,
|
|
rt_grenade=P_INVALID,
|
|
rt_blood=P_INVALID,
|
|
rt_wizspike=P_INVALID,
|
|
rt_slightblood=P_INVALID,
|
|
rt_knightspike=P_INVALID,
|
|
rt_vorespike=P_INVALID,
|
|
rtdp_nexuizplasma=P_INVALID,
|
|
rtdp_glowtrail=P_INVALID,
|
|
|
|
ptqw_gunshot=P_INVALID,
|
|
ptqw_blood=P_INVALID,
|
|
ptqw_lightningblood=P_INVALID,
|
|
|
|
rtqw_railtrail=P_INVALID,
|
|
ptfte_bullet=P_INVALID,
|
|
ptfte_superbullet=P_INVALID;
|
|
|
|
typedef struct tentmodels_s {
|
|
/*static stuff*/
|
|
char *modelname;
|
|
char *beamparticles;
|
|
char *beamimpactparticle;
|
|
char *beamimpactmodel;
|
|
float impactscale;
|
|
struct tentmodels_s *partner;
|
|
int bflags;
|
|
|
|
/*cached stuff*/
|
|
model_t *model;
|
|
model_t *impactmodel;
|
|
int ef_beam;
|
|
int ef_impact;
|
|
} tentmodels_t;
|
|
|
|
struct beam_s {
|
|
tentmodels_t *info;
|
|
int entity;
|
|
short tag;
|
|
// short pad;
|
|
// qbyte pad;
|
|
qbyte bflags;
|
|
qbyte type;
|
|
qbyte skin;
|
|
unsigned int rflags;
|
|
|
|
float endtime;
|
|
float alpha;
|
|
vec3_t start, end;
|
|
vec3_t offset; //when attached, this is the offset from the owning entity. probably only z is meaningful.
|
|
// int particlecolour; //some effects have specific colours. which is weird.
|
|
trailkey_t trailstate;
|
|
trailkey_t emitstate;
|
|
};
|
|
|
|
beam_t *cl_beams;
|
|
int cl_beams_max;
|
|
|
|
typedef struct
|
|
{
|
|
vec3_t origin;
|
|
vec3_t oldorigin;
|
|
vec3_t velocity;
|
|
|
|
int firstframe;
|
|
int numframes;
|
|
|
|
int type;
|
|
vec3_t angles;
|
|
vec3_t avel;
|
|
int flags;
|
|
float gravity;
|
|
float startalpha;
|
|
float endalpha;
|
|
float scale;
|
|
float start;
|
|
float framerate;
|
|
model_t *model;
|
|
int skinnum;
|
|
int traileffect;
|
|
trailkey_t trailstate;
|
|
} explosion_t;
|
|
|
|
static explosion_t *cl_explosions;
|
|
static int cl_explosions_max;
|
|
|
|
static int explosions_running;
|
|
static int beams_running;
|
|
|
|
static tentmodels_t beamtypes[] =
|
|
{
|
|
{"models/misc/bolt.mdl", "TE_LIGHTNING1", "TE_LIGHTNING1_END"},
|
|
{"models/misc/bolt2.mdl", "TE_LIGHTNING2", "TE_LIGHTNING2_END"},
|
|
{"progs/bolt3.mdl", "TE_LIGHTNING3", "TE_LIGHTNING3_END"},
|
|
{"progs/beam.mdl", "te_beam", "te_beam_end"}, //a CTF addition, but has other potential uses, sadly.
|
|
|
|
{"models/monsters/parasite/segment/tris.md2","te_parasite_attack", "te_parasite_attack_end"},
|
|
{"models/ctf/segment/tris.md2", "te_grapple_cable", "te_grapple_cable_end"},
|
|
{"models/proj/beam/tris.md2", "te_heatbeam", "te_heatbeam_end"},
|
|
{"models/proj/lightning/tris.md2", "TE_LIGHTNING2", "TE_LIGHTNING2_END"},
|
|
|
|
{"models/stltng2.mdl", "te_stream_lightning_small", NULL},
|
|
{"models/stchain.mdl", "te_stream_chain", NULL},
|
|
{"models/stsunsf1.mdl", "te_stream_sunstaff1", NULL, "models/stsunsf3.mdl", 0.8, &beamtypes[BT_H2SUNSTAFF1_SUB]}, //the core beam
|
|
{"models/stsunsf2.mdl", NULL, NULL, "models/stsunsf4.mdl", 1.5},//the transparenty bit.
|
|
{"models/stsunsf1.mdl", "te_stream_sunstaff2", NULL},
|
|
{"models/stlghtng.mdl", "te_stream_lightning", NULL},
|
|
{"models/stclrbm.mdl", "te_stream_colorbeam", NULL},
|
|
{"models/stice.mdl", "te_stream_icechunks", NULL},
|
|
{"models/stmedgaz.mdl", "te_stream_gaze", NULL},
|
|
{"models/fambeam.mdl", "te_stream_famine", NULL},
|
|
};
|
|
|
|
|
|
static sfx_t *cl_sfx_wizhit;
|
|
sfx_t *cl_sfx_knighthit;
|
|
static sfx_t *cl_sfx_tink1;
|
|
static sfx_t *cl_sfx_ric1;
|
|
static sfx_t *cl_sfx_ric2;
|
|
static sfx_t *cl_sfx_ric3;
|
|
sfx_t *cl_sfx_r_exp3;
|
|
|
|
cvar_t cl_expsprite = CVARFD("cl_expsprite", "1", CVAR_ARCHIVE, "Display a central sprite in explosion effects. QuakeWorld typically does so, NQ mods should not (which is problematic when played with the qw protocol).");
|
|
cvar_t r_explosionlight = CVARFC("r_explosionlight", "1", CVAR_ARCHIVE, Cvar_Limiter_ZeroToOne_Callback);
|
|
static cvar_t r_explosionlight_colour = CVARFD("r_explosionlight_colour", "4.0 2.0 0.5", CVAR_ARCHIVE, "This controls the initial RGB values of EF_EXPLOSION effects.");
|
|
static cvar_t r_explosionlight_fade = CVARFD("r_explosionlight_fade", "0.784 0.92 0.48", CVAR_ARCHIVE, "This controls the per-second RGB decay values of EF_EXPLOSION effects.");
|
|
cvar_t r_dimlight_colour = CVARFD("r_dimlight_colour", "2.0 1.0 0.5 200", CVAR_ARCHIVE, "The red, green, blue, radius values for EF_DIMLIGHT effects (used for quad+pent in vanilla quake).");
|
|
cvar_t r_brightlight_colour = CVARFD("r_brightlight_colour", "2.0 1.0 0.5 400", CVAR_ARCHIVE, "The red, green, blue, radius values for EF_BRIGHTLIGHT effects (unused in vanilla quake).");
|
|
cvar_t r_redlight_colour = CVARFD("r_redlight_colour", "3.0 0.5 0.5 200", CVAR_ARCHIVE, "The red, green, blue, radius values for EF_RED effects (typically used for pentagram in quakeworld).");
|
|
cvar_t r_greenlight_colour = CVARFD("r_greenlight_colour", "0.5 3.0 0.5 200", CVAR_ARCHIVE, "The red, green, blue, radius values for EF_GREEN effects (rarely used).");
|
|
cvar_t r_bluelight_colour = CVARFD("r_bluelight_colour", "0.5 0.5 3.0 200", CVAR_ARCHIVE, "The red, green, blue, radius values for EF_BLUE effects (typically used for quad-damage in quakeworld)");
|
|
cvar_t r_rocketlight_colour = CVARFD("r_rocketlight_colour", "2.0 1.0 0.25 200", CVAR_ARCHIVE, "This controls the RGB+radius values of MF_ROCKET effects.");
|
|
cvar_t r_muzzleflash_colour = CVARFD("r_muzzleflash_colour", "1.5 1.3 1.0 200", CVAR_ARCHIVE, "This controls the initial RGB+radius of EF_MUZZLEFLASH/svc_muzzleflash effects.");
|
|
cvar_t r_muzzleflash_fade = CVARFD("r_muzzleflash_fade", "1.5 0.75 0.375 1000", CVAR_ARCHIVE, "This controls the per-second RGB+radius decay of EF_MUZZLEFLASH/svc_muzzleflash effects.");
|
|
cvar_t cl_truelightning = CVARF("cl_truelightning", "0", CVAR_SEMICHEAT);
|
|
static cvar_t cl_beam_trace = CVAR("cl_beam_trace", "0");
|
|
static cvar_t cl_legacystains = CVARD("cl_legacystains", "1", "WARNING: this cvar will default to 0 and later removed at some point"); //FIXME: do as the description says!
|
|
static cvar_t cl_shaftlight = CVAR("gl_shaftlight", "0.8");
|
|
static cvar_t cl_part_density_fade_start = CVARD("cl_part_density_fade_start", "1024", "Specifies the distance at which ssqc's pointparticles will start to get less dense.");
|
|
static cvar_t cl_part_density_fade = CVARD("cl_part_density_fade", "1024", "Specifies the distance over which ssqc pointparticles density fades from all to none. If this is set to 0 then particles will spawn at their normal density regardless of location on the map.");
|
|
|
|
typedef struct {
|
|
sfx_t **sfx;
|
|
char *efname;
|
|
} tentsfx_t;
|
|
tentsfx_t tentsfx[] =
|
|
{
|
|
{&cl_sfx_wizhit, "wizard/hit.wav"},
|
|
{&cl_sfx_knighthit, "hknight/hit.wav"},
|
|
{&cl_sfx_tink1, "weapons/tink1.wav"},
|
|
{&cl_sfx_ric1, "weapons/ric1.wav"},
|
|
{&cl_sfx_ric2, "weapons/ric2.wav"},
|
|
{&cl_sfx_ric3, "weapons/ric3.wav"},
|
|
{&cl_sfx_r_exp3, "weapons/r_exp3.wav"}
|
|
};
|
|
|
|
vec3_t playerbeam_end[MAX_SPLITS];
|
|
|
|
typedef struct associatedeffect_s
|
|
{
|
|
struct associatedeffect_s *next;
|
|
char mname[MAX_QPATH];
|
|
char pname[MAX_QPATH];
|
|
enum
|
|
{
|
|
AE_TRAIL,
|
|
AE_EMIT,
|
|
} type;
|
|
unsigned int meflags;
|
|
} associatedeffect_t;
|
|
associatedeffect_t *associatedeffect;
|
|
char part_parsenamespace[MAX_QPATH];
|
|
void CL_AssociateEffect_f(void)
|
|
{
|
|
char *modelname = Cmd_Argv(1);
|
|
char *effectname = Cmd_Argv(2);
|
|
int type, i;
|
|
unsigned int flags = 0;
|
|
struct associatedeffect_s *ae;
|
|
if (Cmd_Argc() == 1)
|
|
{
|
|
for(ae = associatedeffect; ae; ae = ae->next)
|
|
{
|
|
Con_Printf("^h%c^h %s: %s%s%s\n", (ae->type==AE_TRAIL)?'t':'e', ae->mname, ae->pname,
|
|
(ae->meflags & MDLF_EMITREPLACE)?" (replace)":"",
|
|
(ae->meflags & MDLF_EMITFORWARDS)?" (foward)":"");
|
|
}
|
|
return;
|
|
}
|
|
if (!strcmp(Cmd_Argv(0), "r_trail"))
|
|
type = AE_TRAIL;
|
|
else
|
|
{
|
|
type = AE_EMIT;
|
|
|
|
for (i = 3; i < Cmd_Argc(); i++)
|
|
{
|
|
const char *fn = Cmd_Argv(i);
|
|
if (!strcmp(fn, "replace") || !strcmp(fn, "1"))
|
|
flags |= MDLF_EMITREPLACE;
|
|
else if (!strcmp(fn, "forwards") || !strcmp(fn, "forward"))
|
|
flags |= MDLF_EMITFORWARDS;
|
|
else if (!strcmp(fn, "0"))
|
|
; //1 or 0 are legacy, meaning replace or not
|
|
else
|
|
Con_DPrintf("%s %s: unknown flag %s\n", Cmd_Argv(0), modelname, fn);
|
|
}
|
|
}
|
|
|
|
if (
|
|
strstr(modelname, "player") ||
|
|
strstr(modelname, "eyes") ||
|
|
strstr(modelname, "flag") ||
|
|
strstr(modelname, "tf_stan") ||
|
|
strstr(modelname, ".bsp") ||
|
|
strstr(modelname, "turr"))
|
|
{
|
|
Con_Printf("Sorry: Not allowed to attach effects to model \"%s\"\n", modelname);
|
|
return;
|
|
}
|
|
|
|
if (*part_parsenamespace && !strchr(effectname, '.'))
|
|
effectname = va("%s.%s", part_parsenamespace, effectname);
|
|
|
|
if (strlen (modelname) >= MAX_QPATH || strlen(effectname) >= MAX_QPATH)
|
|
return;
|
|
|
|
/*replace the old one if it exists*/
|
|
for(ae = associatedeffect; ae; ae = ae->next)
|
|
{
|
|
if (!strcmp(ae->mname, modelname))
|
|
if ((ae->type==AE_TRAIL) == (type==AE_TRAIL))
|
|
break;
|
|
}
|
|
if (!ae)
|
|
{
|
|
ae = Z_Malloc(sizeof(*ae));
|
|
strcpy(ae->mname, modelname);
|
|
ae->next = associatedeffect;
|
|
associatedeffect = ae;
|
|
}
|
|
ae->type = type;
|
|
ae->meflags = flags;
|
|
strcpy(ae->pname, effectname);
|
|
|
|
if (pe)
|
|
CL_RegisterParticles();
|
|
}
|
|
|
|
void CL_InitTEntSounds (void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(tentsfx)/sizeof(tentsfx[0]); i++)
|
|
{
|
|
if (COM_FCheckExists(va("sound/%s", tentsfx[i].efname)))
|
|
*tentsfx[i].sfx = S_PrecacheSound (tentsfx[i].efname);
|
|
else
|
|
*tentsfx[i].sfx = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ParseTEnts
|
|
=================
|
|
*/
|
|
void CL_InitTEnts (void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(tentsfx)/sizeof(tentsfx[0]); i++)
|
|
*tentsfx[i].sfx = NULL;
|
|
|
|
Cmd_AddCommand("r_effect", CL_AssociateEffect_f);
|
|
Cmd_AddCommand("r_trail", CL_AssociateEffect_f);
|
|
|
|
Cvar_Register (&cl_expsprite, "Temporary entity control");
|
|
Cvar_Register (&cl_truelightning, "Temporary entity control");
|
|
Cvar_Register (&cl_beam_trace, "Temporary entity control");
|
|
Cvar_Register (&r_explosionlight, "Temporary entity control");
|
|
Cvar_Register (&r_explosionlight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_explosionlight_fade, "Temporary entity control");
|
|
Cvar_Register (&r_muzzleflash_colour, "Temporary entity control");
|
|
Cvar_Register (&r_muzzleflash_fade, "Temporary entity control");
|
|
Cvar_Register (&r_dimlight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_redlight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_greenlight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_bluelight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_brightlight_colour, "Temporary entity control");
|
|
Cvar_Register (&r_rocketlight_colour, "Temporary entity control");
|
|
Cvar_Register (&cl_legacystains, "Temporary entity control");
|
|
Cvar_Register (&cl_shaftlight, "Temporary entity control");
|
|
|
|
Cvar_Register (&cl_part_density_fade_start, "Temporary entity control");
|
|
Cvar_Register (&cl_part_density_fade, "Temporary entity control");
|
|
}
|
|
|
|
void CL_ShutdownTEnts (void)
|
|
{
|
|
struct associatedeffect_s *ae;
|
|
while(associatedeffect)
|
|
{
|
|
ae = associatedeffect;
|
|
associatedeffect = ae->next;
|
|
BZ_Free(ae);
|
|
}
|
|
}
|
|
|
|
void CL_ClearTEntParticleState (void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < cl_beams_max; i++)
|
|
{
|
|
if (cl_beams[i].trailstate)
|
|
P_DelinkTrailstate(&(cl_beams[i].trailstate));
|
|
if (cl_beams[i].emitstate)
|
|
P_DelinkTrailstate(&(cl_beams[i].emitstate));
|
|
}
|
|
}
|
|
|
|
void P_LoadedModel(model_t *mod)
|
|
{
|
|
struct associatedeffect_s *ae;
|
|
|
|
mod->particleeffect = P_INVALID;
|
|
mod->particletrail = P_INVALID;
|
|
mod->engineflags &= ~(MDLF_EMITREPLACE|MDLF_EMITFORWARDS);
|
|
mod->engineflags |= MDLF_RECALCULATERAIN;
|
|
for(ae = associatedeffect; ae; ae = ae->next)
|
|
{
|
|
if (!strcmp(ae->mname, mod->name))
|
|
{
|
|
switch(ae->type)
|
|
{
|
|
case AE_TRAIL:
|
|
mod->particletrail = P_FindParticleType(ae->pname);
|
|
break;
|
|
case AE_EMIT:
|
|
mod->particleeffect = P_FindParticleType(ae->pname);
|
|
mod->engineflags |= ae->meflags;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mod->particletrail == P_INVALID)
|
|
P_DefaultTrail(0, mod->flags, &mod->particletrail, &mod->traildefaultindex);
|
|
}
|
|
|
|
void CL_RefreshCustomTEnts(void);
|
|
void CL_RegisterParticles(void)
|
|
{
|
|
model_t *mod;
|
|
extern model_t *mod_known;
|
|
extern int mod_numknown;
|
|
int i;
|
|
for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
|
|
{
|
|
if (mod->loadstate == MLS_LOADED)
|
|
{
|
|
P_LoadedModel(mod);
|
|
}
|
|
}
|
|
|
|
pt_muzzleflash = P_FindParticleType("TE_MUZZLEFLASH");
|
|
pt_gunshot = P_FindParticleType("TE_GUNSHOT"); /*shotgun*/
|
|
ptdp_gunshotquad = P_FindParticleType("TE_GUNSHOTQUAD"); /*DP: quadded shotgun*/
|
|
pt_spike = P_FindParticleType("TE_SPIKE"); /*nailgun*/
|
|
ptdp_spikequad = P_FindParticleType("TE_SPIKEQUAD"); /*DP: quadded nailgun*/
|
|
pt_superspike = P_FindParticleType("TE_SUPERSPIKE"); /*nailgun*/
|
|
ptdp_superspikequad = P_FindParticleType("TE_SUPERSPIKEQUAD"); /*DP: quadded nailgun*/
|
|
pt_wizspike = P_FindParticleType("TE_WIZSPIKE"); //scrag missile impact
|
|
pt_knightspike = P_FindParticleType("TE_KNIGHTSPIKE"); //hellknight missile impact
|
|
pt_explosion = P_FindParticleType("TE_EXPLOSION");/*rocket/grenade launcher impacts/far too many things*/
|
|
ptdp_explosionquad = P_FindParticleType("TE_EXPLOSIONQUAD"); /*nailgun*/
|
|
pt_tarexplosion = P_FindParticleType("TE_TAREXPLOSION");//tarbaby/spawn dying.
|
|
pt_teleportsplash = P_FindParticleType("TE_TELEPORT");/*teleporters*/
|
|
pt_lavasplash = P_FindParticleType("TE_LAVASPLASH"); //e1m7 boss dying.
|
|
ptdp_smallflash = P_FindParticleType("TE_SMALLFLASH"); //DP:
|
|
ptdp_flamejet = P_FindParticleType("TE_FLAMEJET"); //DP:
|
|
ptdp_flame = P_FindParticleType("EF_FLAME"); //DP:
|
|
ptdp_blood = P_FindParticleType("TE_BLOOD"); /*when you hit something with the shotgun/axe/nailgun - nq uses the general particle builtin*/
|
|
ptdp_spark = P_FindParticleType("TE_SPARK");//DPTE_SPARK
|
|
ptdp_plasmaburn = P_FindParticleType("TE_PLASMABURN");
|
|
ptdp_tei_g3 = P_FindParticleType("TE_TEI_G3");
|
|
ptdp_tei_smoke = P_FindParticleType("TE_TEI_SMOKE");
|
|
ptdp_tei_bigexplosion = P_FindParticleType("TE_TEI_BIGEXPLOSION");
|
|
ptdp_tei_plasmahit = P_FindParticleType("TE_TEI_PLASMAHIT");
|
|
ptdp_stardust = P_FindParticleType("EF_STARDUST");
|
|
rt_rocket = P_FindParticleType("TR_ROCKET"); /*rocket trail*/
|
|
rt_grenade = P_FindParticleType("TR_GRENADE"); /*grenade trail*/
|
|
rt_blood = P_FindParticleType("TR_BLOOD"); /*blood trail*/
|
|
rt_wizspike = P_FindParticleType("TR_WIZSPIKE");
|
|
rt_slightblood = P_FindParticleType("TR_SLIGHTBLOOD");
|
|
rt_knightspike = P_FindParticleType("TR_KNIGHTSPIKE");
|
|
rt_vorespike = P_FindParticleType("TR_VORESPIKE");
|
|
//rtdp_neharasmoke = P_FindParticleType("TR_NEHAHRASMOKE");
|
|
rtdp_nexuizplasma = P_FindParticleType("TR_NEXUIZPLASMA");
|
|
rtdp_glowtrail = P_FindParticleType("TR_GLOWTRAIL");
|
|
/*internal to psystem*/ P_FindParticleType("SVC_PARTICLE");
|
|
|
|
ptqw_gunshot = (pt_gunshot!=P_INVALID)?pt_gunshot:P_FindParticleType("TE_QWGUNSHOT"); /*shotgun*/
|
|
ptqw_blood = (ptdp_blood!=P_INVALID)?ptdp_blood:P_FindParticleType("TE_QWBLOOD");
|
|
ptqw_lightningblood = P_FindParticleType("TE_LIGHTNINGBLOOD");
|
|
|
|
#ifdef Q2CLIENT
|
|
if (cls.protocol == CP_QUAKE2)
|
|
{
|
|
for (i = 0; i < sizeof(pt_q2)/sizeof(pt_q2[0]); i++)
|
|
{
|
|
if (!q2efnames[i])
|
|
{
|
|
pt_q2[i] = P_INVALID;
|
|
continue;
|
|
}
|
|
pt_q2[i] = P_FindParticleType(va("q2part.%s", q2efnames[i]));
|
|
|
|
#ifdef _DEBUG
|
|
if (pt_q2[i] == P_INVALID && pt_q2[0] != P_INVALID)
|
|
Con_Printf("effect q2part.%s was not declared\n", q2efnames[i]);
|
|
#endif
|
|
}
|
|
|
|
/*ptq2_blood = P_FindParticleType("q2part.TEQ2_BLOOD");
|
|
rtq2_railtrail = P_FindParticleType("q2part.TR_RAILTRAIL");
|
|
rtq2_blastertrail = P_FindParticleType("q2part.TR_BLASTERTRAIL");
|
|
ptq2_blasterparticles = P_FindParticleType("q2part.TE_BLASTERPARTICLES");
|
|
rtq2_bubbletrail = P_FindParticleType("q2part.TE_BUBBLETRAIL");
|
|
rtq2_gib = P_FindParticleType("q2part.TR_GIB");
|
|
rtq2_rocket = P_FindParticleType("q2part.TR_ROCKET");
|
|
rtq2_grenade = P_FindParticleType("q2part.TR_GRENADE");
|
|
ptq2_bfgparticles = P_FindParticleType("q2part.TR_BFGPARTICLES");
|
|
ptq2_flies = P_FindParticleType("q2part.TR_FLIES");
|
|
ptq2_trap = P_FindParticleType("q2part.TR_TRAP");
|
|
ptq2_trackershell = P_FindParticleType("q2part.TR_TRACKERSHELL");*/
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < sizeof(pt_q2)/sizeof(pt_q2[0]); i++)
|
|
pt_q2[i] = P_INVALID;
|
|
}
|
|
#endif
|
|
|
|
rtqw_railtrail = P_FindParticleType("TE_RAILTRAIL");
|
|
ptfte_bullet = P_FindParticleType("TE_BULLET");
|
|
ptfte_superbullet = P_FindParticleType("TE_SUPERBULLET");
|
|
|
|
CL_RefreshCustomTEnts();
|
|
|
|
for (i = 0; i < countof(beamtypes); i++)
|
|
{
|
|
//we can normally expect the server to have precache_modeled these models, so any lookups should be just a lookup, and thus relatively cheap.
|
|
beamtypes[i].model = NULL;
|
|
beamtypes[i].impactmodel = NULL;
|
|
beamtypes[i].ef_beam = beamtypes[i].beamparticles?P_FindParticleType(beamtypes[i].beamparticles):P_INVALID;
|
|
beamtypes[i].ef_impact = beamtypes[i].beamimpactparticle?P_FindParticleType(beamtypes[i].beamimpactparticle):P_INVALID;
|
|
}
|
|
|
|
//FIXME
|
|
for (i = 0; i < cl_explosions_max; i++)
|
|
cl_explosions[i].model = NULL;
|
|
}
|
|
|
|
#ifdef Q2CLIENT
|
|
enum {
|
|
q2cl_mod_explode,
|
|
q2cl_mod_smoke,
|
|
q2cl_mod_flash,
|
|
q2cl_mod_parasite_tip,
|
|
q2cl_mod_explo4,
|
|
q2cl_mod_bfg_explo,
|
|
q2cl_mod_powerscreen,
|
|
q2cl_mod_max
|
|
};
|
|
tentmodels_t q2tentmodels[q2cl_mod_max] = {
|
|
{"models/objects/explode/tris.md2"},
|
|
{"models/objects/smoke/tris.md2"},
|
|
{"models/objects/flash/tris.md2"},
|
|
{"models/monsters/parasite/tip/tris.md2"},
|
|
{"models/objects/r_explode/tris.md2"},
|
|
{"sprites/s_bfg2.sp2"},
|
|
{"models/items/armor/effect/tris.md2"}
|
|
};
|
|
|
|
int CLQ2_RegisterTEntModels (void)
|
|
{
|
|
// int i;
|
|
// for (i = 0; i < q2cl_mod_max; i++)
|
|
// if (!CL_CheckOrDownloadFile(q2tentmodels[i].modelname, false))
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static void CL_ClearExplosion(explosion_t *exp, vec3_t org)
|
|
{
|
|
exp->endalpha = 0;
|
|
exp->startalpha = 1;
|
|
exp->scale = 1;
|
|
exp->gravity = 0;
|
|
exp->flags = 0;
|
|
exp->model = NULL;
|
|
exp->firstframe = -1;
|
|
exp->framerate = 10;
|
|
VectorClear(exp->velocity);
|
|
VectorClear(exp->angles);
|
|
VectorClear(exp->avel);
|
|
if (pe)
|
|
P_DelinkTrailstate(&(exp->trailstate));
|
|
exp->traileffect = P_INVALID;
|
|
VectorCopy(org, exp->origin);
|
|
VectorCopy(org, exp->oldorigin);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ClearTEnts
|
|
=================
|
|
*/
|
|
void CL_ClearTEnts (void)
|
|
{
|
|
int i;
|
|
CL_ClearTEntParticleState();
|
|
CL_ShutdownTEnts();
|
|
|
|
cl_beams_max = 0;
|
|
BZ_Free(cl_beams);
|
|
cl_beams = NULL;
|
|
beams_running = 0;
|
|
|
|
for (i = 0; i < cl_explosions_max; i++)
|
|
CL_ClearExplosion(cl_explosions+i, vec3_origin);
|
|
cl_explosions_max = 0;
|
|
BZ_Free(cl_explosions);
|
|
cl_explosions = NULL;
|
|
explosions_running = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_AllocExplosion
|
|
=================
|
|
*/
|
|
explosion_t *CL_AllocExplosion (vec3_t org)
|
|
{
|
|
int i;
|
|
float time;
|
|
int index;
|
|
|
|
for (i=0; i < explosions_running; i++)
|
|
{
|
|
if (!cl_explosions[i].model)
|
|
{
|
|
CL_ClearExplosion(&cl_explosions[i], org);
|
|
return &cl_explosions[i];
|
|
}
|
|
}
|
|
|
|
// if (i == explosions_running && i < cl_maxexplosions.ival)
|
|
{
|
|
if (i == cl_explosions_max)
|
|
{
|
|
cl_explosions_max = (i+1)*2;
|
|
cl_explosions = BZ_Realloc(cl_explosions, sizeof(*cl_explosions)*cl_explosions_max);
|
|
memset(cl_explosions + i, 0, sizeof(*cl_explosions)*(cl_explosions_max-i));
|
|
}
|
|
|
|
explosions_running++;
|
|
CL_ClearExplosion(&cl_explosions[i], org);
|
|
return &cl_explosions[i];
|
|
}
|
|
|
|
// find the oldest explosion
|
|
time = cl.time;
|
|
index = 0;
|
|
|
|
for (i=0 ; i<cl_explosions_max ; i++)
|
|
if (cl_explosions[i].start < time)
|
|
{
|
|
time = cl_explosions[i].start;
|
|
index = i;
|
|
}
|
|
CL_ClearExplosion(&cl_explosions[index], org);
|
|
return &cl_explosions[index];
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ParseBeam
|
|
=================
|
|
*/
|
|
beam_t *CL_NewBeam (int entity, int tag, tentmodels_t *btype)
|
|
{
|
|
beam_t *b;
|
|
int i;
|
|
|
|
// override any beam with the same entity (unless they used world)
|
|
if (entity)
|
|
{
|
|
for (i=0, b=cl_beams; i < beams_running; i++, b++)
|
|
if (b->entity == entity && b->tag == tag)
|
|
goto found;
|
|
}
|
|
|
|
// find a free beam
|
|
for (i=0, b=cl_beams; i < beams_running; i++, b++)
|
|
{
|
|
if (!b->info)
|
|
goto found;
|
|
}
|
|
|
|
|
|
// if (i >= cl_maxbeams.ival)
|
|
// return NULL;
|
|
if (i == cl_beams_max)
|
|
{
|
|
int nm = (i+1)*2;
|
|
CL_ClearTEntParticleState();
|
|
|
|
cl_beams = BZ_Realloc(cl_beams, nm*sizeof(*cl_beams));
|
|
memset(cl_beams + cl_beams_max, 0, sizeof(*cl_beams)*(nm-cl_beams_max));
|
|
cl_beams_max = nm;
|
|
}
|
|
|
|
beams_running++;
|
|
b = &cl_beams[i];
|
|
|
|
found:
|
|
b->info = btype;
|
|
b->bflags = 0;
|
|
VectorClear(b->offset);
|
|
return b;
|
|
}
|
|
#define STREAM_ATTACHTOPLAYER 1 //if owned by the viewentity then attach to camera (but don't for other entities).
|
|
#define STREAM_JITTER 2 //moves up to 30qu forward/back (40qu per sec)
|
|
#define STREAM_ATTACHED 16 //attach it to any entity's origin
|
|
#define STREAM_TRANSLUCENT 32
|
|
beam_t *CL_AddBeam (enum beamtype_e tent, int ent, vec3_t start, vec3_t end) //fixme: use TE_ numbers instead of 0 - 5
|
|
{
|
|
beam_t *b;
|
|
|
|
model_t *m;
|
|
int btype, etype;
|
|
int i;
|
|
vec3_t impact, normal;
|
|
vec3_t extra;
|
|
|
|
//zquake compat requires some parsing weirdness.
|
|
switch(tent)
|
|
{
|
|
case BT_Q1LIGHTNING1:
|
|
if (ent < 0 && ent >= -512) //a zquake concept. ent between -1 and -maxplayers is to be taken to be a railtrail from a particular player instead of a beam.
|
|
{
|
|
// TODO: add support for those finnicky colored railtrails...
|
|
if (P_ParticleTrail(start, end, rtqw_railtrail, 0.1, -ent, NULL, NULL))
|
|
P_ParticleTrailIndex(start, end, P_INVALID, 0.1, 208, 8, NULL);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case BT_Q1LIGHTNING2:
|
|
if (ent < 0 && ent >= -MAX_CLIENTS) //based on the railgun concept - this adds a rogue style TE_BEAM effect.
|
|
tent = BT_Q1BEAM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
btype = beamtypes[tent].ef_beam;
|
|
etype = beamtypes[tent].ef_impact;
|
|
|
|
/*don't bother loading the model if we have a particle effect for it instead*/
|
|
if (ruleset_allow_particle_lightning.ival && btype >= 0)
|
|
m = NULL;
|
|
else
|
|
{
|
|
m = beamtypes[tent].model;
|
|
if (!m)
|
|
m = beamtypes[tent].model = Mod_ForName(beamtypes[tent].modelname, MLV_WARN);
|
|
}
|
|
|
|
if (m && m->loadstate != MLS_LOADED)
|
|
CL_CheckOrEnqueDownloadFile(m->name, NULL, 0);
|
|
|
|
// save end position for truelightning
|
|
if (ent)
|
|
{
|
|
for (i = 0; i < cl.splitclients; i++)
|
|
{
|
|
playerview_t *pv = &cl.playerview[i];
|
|
if (ent == ((pv->cam_state == CAM_EYECAM)?(pv->cam_spec_track+1):(pv->playernum+1)))
|
|
{
|
|
VectorCopy(end, playerbeam_end[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (etype >= 0 && cls.state == ca_active && etype != P_INVALID)
|
|
{
|
|
if (cl_beam_trace.ival)
|
|
{
|
|
VectorSubtract(end, start, normal);
|
|
VectorNormalize(normal);
|
|
VectorMA(end, 4, normal, extra); //extend the end-point by four
|
|
if (CL_TraceLine(start, extra, impact, normal, NULL)>=1)
|
|
etype = -1;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(end, impact);
|
|
normal[0] = normal[1] = normal[2] = 0;
|
|
}
|
|
}
|
|
|
|
b = CL_NewBeam(ent, -1, &beamtypes[tent]);
|
|
if (!b)
|
|
{
|
|
Con_Printf ("beam list overflow!\n");
|
|
return NULL;
|
|
}
|
|
|
|
b->rflags = RF_NOSHADOW;
|
|
b->entity = ent;
|
|
b->info = &beamtypes[tent];
|
|
b->tag = -1;
|
|
b->bflags |= /*STREAM_ATTACHED|*/STREAM_ATTACHTOPLAYER;
|
|
b->endtime = cl.time + 0.2;
|
|
b->alpha = 1;
|
|
b->skin = 0;
|
|
VectorCopy (start, b->start);
|
|
VectorCopy (end, b->end);
|
|
|
|
if (etype >= 0)
|
|
{
|
|
P_RunParticleEffectState (impact, normal, 1, etype, &(b->emitstate));
|
|
if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, -10, 20);
|
|
}
|
|
return b;
|
|
}
|
|
void CL_ParseBeamOffset (enum beamtype_e tent)
|
|
{
|
|
int ent;
|
|
vec3_t start, end, offset;
|
|
beam_t *b;
|
|
|
|
ent = MSGCL_ReadEntity ();
|
|
|
|
start[0] = MSG_ReadCoord ();
|
|
start[1] = MSG_ReadCoord ();
|
|
start[2] = MSG_ReadCoord ();
|
|
|
|
end[0] = MSG_ReadCoord ();
|
|
end[1] = MSG_ReadCoord ();
|
|
end[2] = MSG_ReadCoord ();
|
|
|
|
MSG_ReadPos(offset);
|
|
|
|
b = CL_AddBeam(tent, ent, start, end);
|
|
if (b)
|
|
VectorCopy(offset, b->offset);
|
|
}
|
|
beam_t *CL_ParseBeam (enum beamtype_e tent)
|
|
{
|
|
int ent;
|
|
vec3_t start, end;
|
|
|
|
ent = MSGCL_ReadEntity ();
|
|
|
|
start[0] = MSG_ReadCoord ();
|
|
start[1] = MSG_ReadCoord ();
|
|
start[2] = MSG_ReadCoord ();
|
|
|
|
end[0] = MSG_ReadCoord ();
|
|
end[1] = MSG_ReadCoord ();
|
|
end[2] = MSG_ReadCoord ();
|
|
|
|
return CL_AddBeam(tent, ent, start, end);
|
|
}
|
|
|
|
//finds the latest non-lerped position
|
|
float *CL_FindLatestEntityOrigin(int entnum)
|
|
{
|
|
int i;
|
|
packet_entities_t *pe;
|
|
int framenum = cl.validsequence & UPDATE_MASK;
|
|
pe = &cl.inframes[framenum].packet_entities;
|
|
for (i = 0; i < pe->num_entities; i++)
|
|
{
|
|
if (pe->entities[i].number == entnum)
|
|
return pe->entities[i].origin;
|
|
}
|
|
if (entnum > 0 && entnum <= MAX_CLIENTS)
|
|
{
|
|
entnum--;
|
|
if (cl.inframes[framenum].playerstate[entnum].messagenum == cl.parsecount)
|
|
return cl.inframes[framenum].playerstate[entnum].origin;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void CL_ParseStream (int type)
|
|
{
|
|
int ent;
|
|
vec3_t start, end;
|
|
beam_t *b, *b2;
|
|
int flags;
|
|
int tag;
|
|
float duration;
|
|
int skin;
|
|
tentmodels_t *info;
|
|
|
|
ent = MSGCL_ReadEntity();
|
|
flags = MSG_ReadByte();
|
|
tag = flags&15;
|
|
flags-=tag;
|
|
duration = (float)MSG_ReadByte()*0.05;
|
|
skin = 0;
|
|
if(type == TEH2_STREAM_COLORBEAM)
|
|
{
|
|
skin = MSG_ReadByte();
|
|
}
|
|
start[0] = MSG_ReadCoord();
|
|
start[1] = MSG_ReadCoord();
|
|
start[2] = MSG_ReadCoord();
|
|
end[0] = MSG_ReadCoord();
|
|
end[1] = MSG_ReadCoord();
|
|
end[2] = MSG_ReadCoord();
|
|
|
|
switch(type)
|
|
{
|
|
case TEH2_STREAM_LIGHTNING_SMALL:
|
|
info = &beamtypes[BT_H2LIGHTNING_SMALL];
|
|
flags |= STREAM_JITTER;
|
|
break;
|
|
case TEH2_STREAM_LIGHTNING:
|
|
info = &beamtypes[BT_H2LIGHTNING];
|
|
flags |= STREAM_JITTER;
|
|
break;
|
|
case TEH2_STREAM_ICECHUNKS:
|
|
info = &beamtypes[BT_H2ICECHUNKS];
|
|
flags |= STREAM_JITTER;
|
|
if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, 0, 20);
|
|
break;
|
|
case TEH2_STREAM_SUNSTAFF1:
|
|
info = &beamtypes[BT_H2SUNSTAFF1];
|
|
break;
|
|
case TEH2_STREAM_SUNSTAFF2:
|
|
info = &beamtypes[BT_H2SUNSTAFF2];
|
|
if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, -10, 20);
|
|
break;
|
|
case TEH2_STREAM_COLORBEAM:
|
|
info = &beamtypes[BT_H2COLORBEAM];
|
|
break;
|
|
case TEH2_STREAM_GAZE:
|
|
info = &beamtypes[BT_H2GAZE];
|
|
break;
|
|
case TEH2_STREAM_FAMINE:
|
|
info = &beamtypes[BT_H2FAMINE];
|
|
break;
|
|
case TEH2_STREAM_CHAIN:
|
|
info = &beamtypes[BT_H2CHAIN];
|
|
break;
|
|
default:
|
|
Con_Printf("CL_ParseStream: type %i\n", type);
|
|
info = &beamtypes[BT_H2LIGHTNING];
|
|
break;
|
|
}
|
|
|
|
b = CL_NewBeam(ent, tag, info);
|
|
if (!b)
|
|
{
|
|
Con_Printf ("beam list overflow!\n");
|
|
return;
|
|
}
|
|
|
|
b->rflags = RF_NOSHADOW;
|
|
b->entity = ent;
|
|
b->tag = tag;
|
|
b->bflags = flags;
|
|
b->endtime = cl.time + duration;
|
|
b->alpha = 1;
|
|
b->skin = skin;
|
|
VectorCopy (start, b->start);
|
|
VectorCopy (end, b->end);
|
|
|
|
if (b->bflags & STREAM_ATTACHED)
|
|
{
|
|
float *entorg = CL_FindLatestEntityOrigin(ent);
|
|
if (!entorg)
|
|
b->bflags &= ~STREAM_ATTACHED; //not found, attached isn't valid.
|
|
else
|
|
{
|
|
VectorSubtract(b->start, entorg, b->offset);
|
|
}
|
|
}
|
|
|
|
//special handling...
|
|
if (info->partner && info->ef_beam == P_INVALID)
|
|
{
|
|
b2 = CL_NewBeam(ent, tag+128, info->partner);
|
|
if (b2)
|
|
{
|
|
P_DelinkTrailstate(&b2->trailstate);
|
|
P_DelinkTrailstate(&b2->emitstate);
|
|
memcpy(b2, b, sizeof(*b2));
|
|
b2->trailstate = b2->emitstate = 0;
|
|
b2->info = info->partner;
|
|
b2->tag = tag+128;
|
|
b2->trailstate = trailkey_null;
|
|
b2->emitstate = trailkey_null;
|
|
b2->alpha = 0.5;
|
|
b2->rflags = RF_TRANSLUCENT|RF_NOSHADOW;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ParseTEnt
|
|
=================
|
|
*/
|
|
|
|
#ifdef NQPROT
|
|
void CL_ParseTEnt (qboolean nqprot)
|
|
#else
|
|
void CL_ParseTEnt (void)
|
|
#endif
|
|
{
|
|
#ifndef NQPROT
|
|
#define nqprot false //it's easier
|
|
#endif
|
|
int type;
|
|
vec3_t pos, pos2;
|
|
dlight_t *dl;
|
|
int rnd;
|
|
// explosion_t *ex;
|
|
int cnt, colour;
|
|
|
|
#ifdef CSQC_DAT
|
|
//I know I'm going to regret this.
|
|
if (CSQC_ParseTempEntity())
|
|
return;
|
|
#endif
|
|
|
|
type = MSG_ReadByte ();
|
|
|
|
if (nqprot)
|
|
{
|
|
//easiest way to handle these
|
|
//should probably also do qwgunshot ones with nq protocols or something
|
|
switch(type)
|
|
{
|
|
case TENQ_EXPLOSION2:
|
|
type = TEQW_EXPLOSION2;
|
|
break;
|
|
case TENQ_BEAM:
|
|
type = TEQW_BEAM;
|
|
break;
|
|
case TENQ_QWEXPLOSION:
|
|
type = TEQW_QWEXPLOSION;
|
|
break;
|
|
case TENQ_NQEXPLOSION:
|
|
type = TEQW_NQEXPLOSION;
|
|
break;
|
|
case TENQ_NQGUNSHOT:
|
|
type = TEQW_NQGUNSHOT;
|
|
break;
|
|
case TENQ_QWGUNSHOT:
|
|
type = TEQW_QWGUNSHOT;
|
|
break;
|
|
case TENQ_RAILTRAIL:
|
|
type = TEQW_RAILTRAIL;
|
|
break;
|
|
case TENQ_NEHLIGHTNING4:
|
|
type = TEQW_NEHLIGHTNING4;
|
|
break;
|
|
// case TENQ_NEHSMOKE:
|
|
// type = TEQW_NEHSMOKE;
|
|
// break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
//else QW values
|
|
|
|
//right, nq vs qw doesn't matter now, supposedly.
|
|
|
|
if (cl_shownet.ival >= 2)
|
|
{
|
|
static char *te_names[] = {
|
|
/* 0*/"spike", "superspike", "qwgunshot", "qwexplosion",
|
|
/* 4*/"tarexplosion", "lightning1", "lightning2", "wizspike",
|
|
/* 8*/"knightspike", "lightning3", "lavasplash", "teleport",
|
|
/*12*/"blood", "lightningblood", "bullet", "superbullet", //bullets deprecated
|
|
/*16*/"neh_explosion3", "railtrail", "beam", "explosion2",
|
|
/*20*/"nqexplosion", "nqgunshot", NULL, NULL,
|
|
/*24*/"h2lightsml", "h2chain", "h2sunstf1", "h2sunstf2",
|
|
/*28*/"h2light", "h2cb", "h2ic", "h2gaze",
|
|
/*32*/"h2famine", "h2partexp",NULL,NULL,
|
|
/*36*/NULL,NULL,NULL,NULL,
|
|
/*40*/NULL,NULL,NULL,NULL,
|
|
/*44*/NULL,NULL,NULL,NULL,
|
|
/*48*/NULL,NULL,"dpblood","dpspark"
|
|
/*52*/"dpbloodshower","dpexplosionrgb","dpparticlecube","dpparticlerain",
|
|
/*56*/"dpparticlesnow","dpgunshotquad","dpspikequad","dpsuperspikequad",
|
|
/*60*/NULL,NULL,NULL,NULL,
|
|
/*64*/NULL,NULL,NULL,NULL,
|
|
/*68*/NULL,NULL,"dpexplosionquad",NULL,
|
|
/*72*/"dpsmallflash","dpcustomflash","dpflamejet","dpplasmaburn",
|
|
/*76*/"dpteig3","dpsmoke","dpteibigexplosion","dpteiplasmahit",
|
|
/*80*/
|
|
};
|
|
if (type < countof(te_names) && te_names[type])
|
|
Con_Printf(" te_%s\n", te_names[type]);
|
|
else
|
|
Con_Printf(" te_unknown_%i\n", type);
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case TE_WIZSPIKE: // spike hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, 0, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_wizspike))
|
|
P_RunParticleEffect (pos, vec3_origin, 20, 30);
|
|
|
|
S_StartSound (0, 0, cl_sfx_wizhit, pos, NULL, 1, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case TE_KNIGHTSPIKE: // spike hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_knightspike))
|
|
P_RunParticleEffect (pos, vec3_origin, 226, 20);
|
|
|
|
S_StartSound (0, 0, cl_sfx_knighthit, pos, NULL, 1, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case TEDP_SPIKEQUAD:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptdp_spikequad))
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
|
|
if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 10);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TE_SPIKE: // spike hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
|
|
if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 10);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TEDP_SUPERSPIKEQUAD: // super spike hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptdp_superspikequad))
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
|
|
if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
|
|
if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 20);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TE_SUPERSPIKE: // super spike hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
|
|
if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
|
|
if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 20);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
|
|
#ifdef PEXT_TE_BULLET
|
|
case TE_BULLET:
|
|
if (!(cls.fteprotocolextensions & PEXT_TE_BULLET))
|
|
Host_EndGame("Thought PEXT_TE_BULLET was disabled");
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptfte_bullet))
|
|
if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 10);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TEQW_SUPERBULLET:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptfte_superbullet))
|
|
if (P_RunParticleEffectType(pos, NULL, 2, ptfte_bullet))
|
|
if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 20);
|
|
|
|
if ( rand() % 5 )
|
|
S_StartSound (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
{
|
|
rnd = rand() & 3;
|
|
if (rnd == 1)
|
|
S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
|
|
else if (rnd == 2)
|
|
S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
|
|
else
|
|
S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case TEDP_EXPLOSIONQUAD: // rocket explosion
|
|
// particles
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptdp_explosionquad))
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
|
|
P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -1, -1, -1, 100);
|
|
|
|
// light
|
|
if (r_explosionlight.value)
|
|
{
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = 150 + r_explosionlight.value*200;
|
|
dl->die = cl.time + 1;
|
|
dl->decay = 300;
|
|
|
|
dl->color[0] = 4.0;
|
|
dl->color[1] = 2.0;
|
|
dl->color[2] = 0.5;
|
|
dl->channelfade[0] = 0.196;
|
|
dl->channelfade[1] = 0.23;
|
|
dl->channelfade[2] = 0.12;
|
|
}
|
|
|
|
|
|
// sound
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
|
|
// sprite
|
|
if (cl_expsprite.ival) // temp hopefully
|
|
{
|
|
explosion_t *ex = CL_AllocExplosion (pos);
|
|
ex->start = cl.time;
|
|
ex->model = Mod_ForName ("progs/s_explod.spr", MLV_WARN);
|
|
ex->endalpha = ex->startalpha; //don't fade out
|
|
}
|
|
break;
|
|
case TEQW_NQEXPLOSION: //nq-style, no sprite
|
|
case TEQW_QWEXPLOSION: //qw-style, with (optional) sprite
|
|
// particles
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
|
|
P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -1, -1, -1, 100);
|
|
|
|
// light
|
|
if (r_explosionlight.value)
|
|
{
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = 150 + r_explosionlight.value*200;
|
|
dl->die = cl.time + 0.75;
|
|
dl->decay = dl->radius*2;
|
|
|
|
VectorCopy(r_explosionlight_colour.vec4, dl->color);
|
|
VectorCopy(r_explosionlight_fade.vec4, dl->channelfade);
|
|
}
|
|
|
|
|
|
// sound
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
|
|
// sprite
|
|
if (type == TEQW_QWEXPLOSION && cl_expsprite.ival) // temp hopefully
|
|
{
|
|
explosion_t *ex = CL_AllocExplosion (pos);
|
|
ex->start = cl.time;
|
|
ex->model = Mod_ForName ("progs/s_explod.spr", MLV_WARN);
|
|
ex->endalpha = ex->startalpha; //don't fade out
|
|
}
|
|
break;
|
|
|
|
case TEQW_EXPLOSION2:
|
|
{
|
|
int colorStart;
|
|
int colorLength;
|
|
int ef;
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
colorStart = MSG_ReadByte ();
|
|
colorLength = MSG_ReadByte ();
|
|
|
|
ef = P_FindParticleType(va("TE_EXPLOSION2_%i_%i", colorStart, colorLength));
|
|
if (ef == P_INVALID)
|
|
ef = pt_explosion;
|
|
P_RunParticleEffectType(pos, NULL, 1, ef);
|
|
if (r_explosionlight.value)
|
|
{
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = 350;
|
|
dl->die = cl.time + 0.5;
|
|
dl->decay = 300;
|
|
}
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
}
|
|
break;
|
|
|
|
case TE_EXPLOSION3_NEH:
|
|
case TEDP_EXPLOSIONRGB:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
|
|
P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -1, -1, -1, 100);
|
|
|
|
if (type == TEDP_EXPLOSIONRGB)
|
|
{
|
|
pos2[0] = MSG_ReadByte()/255.0;
|
|
pos2[1] = MSG_ReadByte()/255.0;
|
|
pos2[2] = MSG_ReadByte()/255.0;
|
|
}
|
|
else
|
|
{ //TE_EXPLOSION3_NEH
|
|
pos2[0] = MSG_ReadCoord();
|
|
pos2[1] = MSG_ReadCoord();
|
|
pos2[2] = MSG_ReadCoord();
|
|
}
|
|
|
|
// light
|
|
if (r_explosionlight.value)
|
|
{
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = 150 + r_explosionlight.value*200;
|
|
dl->die = cl.time + 0.5;
|
|
dl->decay = 300;
|
|
|
|
dl->color[0] = 0.4f*pos2[0];
|
|
dl->color[1] = 0.4f*pos2[1];
|
|
dl->color[2] = 0.4f*pos2[2];
|
|
dl->channelfade[0] = 0;
|
|
dl->channelfade[1] = 0;
|
|
dl->channelfade[2] = 0;
|
|
}
|
|
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case TEDP_TEI_BIGEXPLOSION:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptdp_tei_bigexplosion))
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
|
|
P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -1, -1, -1, 100);
|
|
|
|
// light
|
|
if (r_explosionlight.value)
|
|
{
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
// no point in doing this the fuh/ez way
|
|
dl->radius = 500*r_explosionlight.value;
|
|
dl->die = cl.time + 1;
|
|
dl->decay = 500;
|
|
|
|
dl->color[0] = 2.0f;
|
|
dl->color[1] = 1.5f;
|
|
dl->color[2] = 0.75f;
|
|
dl->channelfade[0] = 0;
|
|
dl->channelfade[1] = 0;
|
|
dl->channelfade[2] = 0;
|
|
}
|
|
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case TE_TAREXPLOSION: // tarbaby explosion
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
P_RunParticleEffectType(pos, NULL, 1, pt_tarexplosion);
|
|
|
|
S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case TE_LIGHTNING1: // lightning bolts
|
|
CL_ParseBeam (BT_Q1LIGHTNING1);
|
|
break;
|
|
case TE_LIGHTNING2: // lightning bolts
|
|
CL_ParseBeam (BT_Q1LIGHTNING2);
|
|
break;
|
|
case TE_LIGHTNING3: // lightning bolts
|
|
CL_ParseBeam (BT_Q1LIGHTNING3);
|
|
break;
|
|
case TEQW_NEHLIGHTNING4:
|
|
Con_DPrintf("TEQW_NEHLIGHTNING4 not implemented\n");
|
|
MSG_ReadString();
|
|
CL_ParseBeam (BT_Q1LIGHTNING2);
|
|
break;
|
|
|
|
case TE_LAVASPLASH:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
P_RunParticleEffectType(pos, NULL, 1, pt_lavasplash);
|
|
break;
|
|
|
|
case TE_TELEPORT:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
P_RunParticleEffectType(pos, NULL, 1, pt_teleportsplash);
|
|
break;
|
|
|
|
case TEDP_GUNSHOTQUAD: // bullet hitting wall
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptdp_gunshotquad))
|
|
if (P_RunParticleEffectType(pos, NULL, 1, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 20);
|
|
|
|
break;
|
|
|
|
case TEQW_QWGUNSHOT: // bullet hitting wall
|
|
case TEQW_NQGUNSHOT:
|
|
if (type == TEQW_NQGUNSHOT)
|
|
cnt = 1;
|
|
else
|
|
cnt = MSG_ReadByte ();
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, cnt, pt_gunshot))
|
|
P_RunParticleEffect (pos, vec3_origin, 0, 20*cnt);
|
|
|
|
break;
|
|
|
|
case TEQW_QWBLOOD: // bullets hitting body
|
|
cnt = MSG_ReadByte ();
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, 0, -10, -10, 40);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, cnt, ptqw_blood))
|
|
if (P_RunParticleEffectType(pos, NULL, cnt, ptdp_blood))
|
|
P_RunParticleEffect (pos, vec3_origin, 73, 20*cnt);
|
|
|
|
break;
|
|
|
|
case TEQW_LIGHTNINGBLOOD: // lightning hitting body
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, 1, -10, -10, 20);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, ptqw_lightningblood))
|
|
P_RunParticleEffect (pos, vec3_origin, 225, 50);
|
|
|
|
break;
|
|
|
|
case TEQW_BEAM:
|
|
CL_ParseBeam (BT_Q1BEAM);
|
|
break;
|
|
|
|
case TEQW_RAILTRAIL:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
|
|
if (P_ParticleTrail(pos, pos2, rtqw_railtrail, 1, 0, NULL, NULL))
|
|
P_ParticleTrailIndex(pos, pos2, P_INVALID, 1, 208, 8, NULL);
|
|
break;
|
|
|
|
case TEH2_STREAM_LIGHTNING_SMALL:
|
|
case TEH2_STREAM_CHAIN:
|
|
case TEH2_STREAM_SUNSTAFF1:
|
|
case TEH2_STREAM_SUNSTAFF2:
|
|
case TEH2_STREAM_LIGHTNING:
|
|
case TEH2_STREAM_COLORBEAM:
|
|
case TEH2_STREAM_ICECHUNKS:
|
|
case TEH2_STREAM_GAZE:
|
|
case TEH2_STREAM_FAMINE:
|
|
CL_ParseStream (type);
|
|
break;
|
|
|
|
case TEDP_BLOOD:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
pos2[0] = MSG_ReadChar ();
|
|
pos2[1] = MSG_ReadChar ();
|
|
pos2[2] = MSG_ReadChar ();
|
|
|
|
cnt = MSG_ReadByte ();
|
|
|
|
P_RunParticleEffectType(pos, pos2, cnt, ptdp_blood);
|
|
break;
|
|
|
|
case TEDP_SPARK:
|
|
pos[0] = MSG_ReadCoord (); //org
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
pos2[0] = MSG_ReadChar (); //vel
|
|
pos2[1] = MSG_ReadChar ();
|
|
pos2[2] = MSG_ReadChar ();
|
|
|
|
cnt = MSG_ReadByte ();
|
|
{
|
|
P_RunParticleEffectType(pos, pos2, cnt, ptdp_spark);
|
|
}
|
|
break;
|
|
|
|
case TEDP_BLOODSHOWER:
|
|
{
|
|
vec3_t vel = {0,0,0};
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
|
|
vel[2] = -MSG_ReadCoord ();
|
|
|
|
cnt = MSG_ReadShort ();
|
|
|
|
P_RunParticleCube(P_FindParticleType("te_bloodshower"), pos, pos2, vel, vel, cnt, 0, false, 0);
|
|
}
|
|
break;
|
|
|
|
case TEDP_SMALLFLASH:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
// light
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = 200;
|
|
dl->decay = 1000;
|
|
dl->die = cl.time + 0.2;
|
|
dl->color[0] = 2.0;
|
|
dl->color[1] = 2.0;
|
|
dl->color[2] = 2.0;
|
|
break;
|
|
|
|
case TEDP_CUSTOMFLASH:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
// light
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (pos, dl->origin);
|
|
dl->radius = MSG_ReadByte()*8;
|
|
pos2[0] = (MSG_ReadByte() + 1) * (1.0 / 256.0);
|
|
dl->die = cl.time + pos2[0];
|
|
dl->decay = dl->radius / pos2[0];
|
|
|
|
dl->color[0] = MSG_ReadByte()*(1.0f/127.0f);
|
|
dl->color[1] = MSG_ReadByte()*(1.0f/127.5f);
|
|
dl->color[2] = MSG_ReadByte()*(1.0f/127.0f);
|
|
|
|
break;
|
|
|
|
case TEDP_FLAMEJET:
|
|
// origin
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
// velocity
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
|
|
// count
|
|
cnt = MSG_ReadByte ();
|
|
|
|
if (P_RunParticleEffectType(pos, pos2, cnt, ptdp_flamejet))
|
|
P_RunParticleEffect (pos, pos2, 232, cnt);
|
|
break;
|
|
|
|
case TEDP_PLASMABURN:
|
|
// origin
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
// stain (Hopefully this is close to how DP does it)
|
|
if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 30);
|
|
|
|
if (P_RunParticleEffectType(pos, NULL, 1, P_FindParticleType("te_plasmaburn")))
|
|
P_RunParticleEffect(pos, vec3_origin, 15, 50);
|
|
break;
|
|
|
|
case TEDP_TEI_G3: //nexuiz's nex beam
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
|
|
//sigh...
|
|
MSG_ReadCoord ();
|
|
MSG_ReadCoord ();
|
|
MSG_ReadCoord ();
|
|
|
|
if (P_ParticleTrail(pos, pos2, P_FindParticleType("te_nexbeam"), 1, 0, NULL, NULL))
|
|
P_ParticleTrailIndex(pos, pos2, P_INVALID, 1, 15, 0, NULL);
|
|
break;
|
|
|
|
case TEDP_SMOKE:
|
|
//org
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
//dir
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
|
|
//count
|
|
cnt = MSG_ReadByte ();
|
|
{
|
|
P_RunParticleEffectType(pos, pos2, cnt, ptdp_tei_smoke);
|
|
}
|
|
break;
|
|
|
|
case TEDP_TEI_PLASMAHIT:
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
|
|
//dir
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
cnt = MSG_ReadByte ();
|
|
|
|
{
|
|
P_RunParticleEffectType(pos, pos2, cnt, ptdp_tei_plasmahit);
|
|
}
|
|
break;
|
|
|
|
case TEDP_PARTICLECUBE:
|
|
{
|
|
vec3_t dir;
|
|
int jitter;
|
|
int gravity;
|
|
|
|
//min
|
|
pos[0] = MSG_ReadCoord();
|
|
pos[1] = MSG_ReadCoord();
|
|
pos[2] = MSG_ReadCoord();
|
|
|
|
//max
|
|
pos2[0] = MSG_ReadCoord();
|
|
pos2[1] = MSG_ReadCoord();
|
|
pos2[2] = MSG_ReadCoord();
|
|
|
|
//dir
|
|
dir[0] = MSG_ReadCoord();
|
|
dir[1] = MSG_ReadCoord();
|
|
dir[2] = MSG_ReadCoord();
|
|
|
|
cnt = MSG_ReadShort(); //count
|
|
colour = MSG_ReadByte (); //colour
|
|
gravity = MSG_ReadByte (); //gravity flag
|
|
jitter = MSG_ReadCoord(); //jitter
|
|
|
|
P_RunParticleCube(P_INVALID, pos, pos2, dir, dir, cnt, colour, gravity, jitter);
|
|
}
|
|
break;
|
|
case TEDP_PARTICLERAIN:
|
|
{
|
|
vec3_t dir;
|
|
|
|
//min
|
|
pos[0] = MSG_ReadCoord();
|
|
pos[1] = MSG_ReadCoord();
|
|
pos[2] = MSG_ReadCoord();
|
|
|
|
//max
|
|
pos2[0] = MSG_ReadCoord();
|
|
pos2[1] = MSG_ReadCoord();
|
|
pos2[2] = MSG_ReadCoord();
|
|
|
|
//dir
|
|
dir[0] = MSG_ReadCoord();
|
|
dir[1] = MSG_ReadCoord();
|
|
dir[2] = MSG_ReadCoord();
|
|
|
|
cnt = (unsigned short)MSG_ReadShort(); //count
|
|
colour = MSG_ReadByte (); //colour
|
|
|
|
P_RunParticleWeather(pos, pos2, dir, cnt, colour, "rain");
|
|
}
|
|
break;
|
|
case TEDP_PARTICLESNOW:
|
|
{
|
|
vec3_t dir;
|
|
|
|
//min
|
|
pos[0] = MSG_ReadCoord();
|
|
pos[1] = MSG_ReadCoord();
|
|
pos[2] = MSG_ReadCoord();
|
|
|
|
//max
|
|
pos2[0] = MSG_ReadCoord();
|
|
pos2[1] = MSG_ReadCoord();
|
|
pos2[2] = MSG_ReadCoord();
|
|
|
|
//dir
|
|
dir[0] = MSG_ReadCoord();
|
|
dir[1] = MSG_ReadCoord();
|
|
dir[2] = MSG_ReadCoord();
|
|
|
|
cnt = (unsigned short)MSG_ReadShort(); //count
|
|
colour = MSG_ReadByte (); //colour
|
|
|
|
P_RunParticleWeather(pos, pos2, dir, cnt, colour, "snow");
|
|
}
|
|
break;
|
|
|
|
// case TEQW_NEHRAILTRAIL:
|
|
// case TEQW_NEHEXPLOSION3:
|
|
// case TEQW_NEHLIGHTNING4:
|
|
// case TEQW_NEHSMOKE:
|
|
|
|
default:
|
|
Host_EndGame ("CL_ParseTEnt: bad type - %i", type);
|
|
}
|
|
}
|
|
|
|
void CL_ParseTEnt_Sized (void)
|
|
{
|
|
unsigned short sz = MSG_ReadShort();
|
|
int start = MSG_GetReadCount();
|
|
|
|
for(;;)
|
|
{
|
|
#ifdef NQPROT
|
|
if (sz&0x8000)
|
|
{
|
|
sz&=~0x8000;
|
|
CL_ParseTEnt(true);
|
|
}
|
|
else
|
|
CL_ParseTEnt(false);
|
|
#else
|
|
CL_ParseTEnt();
|
|
#endif
|
|
|
|
if (MSG_GetReadCount() < start + sz)
|
|
{ //try to be more compatible with xonotic.
|
|
int next = MSG_ReadByte();
|
|
if (next == svc_temp_entity)
|
|
continue;
|
|
|
|
Con_Printf("Sized temp_entity data too large (next byte %i, %i bytes unread)\n", next, (start+sz)-MSG_GetReadCount()-1);
|
|
MSG_ReadSkip(start+sz-MSG_GetReadCount());
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
if (MSG_GetReadCount() != start + sz)
|
|
{
|
|
Con_Printf("Tempentity size did not match parsed size misread a gamecode packet (%i bytes too much)\n", MSG_GetReadCount() - (start+sz));
|
|
MSG_ReadSkip(start+sz-MSG_GetReadCount());
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
char name[64];
|
|
int netstyle;
|
|
int particleeffecttype;
|
|
char stain[3];
|
|
qbyte radius;
|
|
vec3_t dlightrgb;
|
|
float dlightradius;
|
|
float dlighttime;
|
|
vec3_t dlightcfade;
|
|
} clcustomtents_t;
|
|
|
|
typedef struct custtentinst_s
|
|
{
|
|
struct custtentinst_s *next;
|
|
clcustomtents_t *type;
|
|
int id;
|
|
vec3_t pos;
|
|
vec3_t pos2;
|
|
vec3_t dir;
|
|
int count;
|
|
} custtentinst_t;
|
|
custtentinst_t *activepcusttents;
|
|
|
|
void CL_SpawnCustomTEnt(custtentinst_t *info)
|
|
{
|
|
clcustomtents_t *t = info->type;
|
|
qboolean failed;
|
|
if (t->netstyle & CTE_ISBEAM)
|
|
{
|
|
if (t->netstyle & (CTE_CUSTOMVELOCITY|CTE_CUSTOMDIRECTION))
|
|
{
|
|
vec3_t org;
|
|
int i, j;
|
|
//FIXME: pvs cull
|
|
if (t->particleeffecttype == -1)
|
|
failed = true;
|
|
else
|
|
{
|
|
failed = false;
|
|
for (i=0 ; i<info->count ; i++)
|
|
{
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
org[j] = info->pos[j] + (info->pos2[j] - info->pos[j])*frandom();
|
|
}
|
|
|
|
failed |= P_RunParticleEffectType(org, info->dir, 1, t->particleeffecttype);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
failed = P_ParticleTrail(info->pos, info->pos2, t->particleeffecttype, 1, 0, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (t->netstyle & (CTE_CUSTOMVELOCITY|CTE_CUSTOMDIRECTION))
|
|
failed = P_RunParticleEffectType(info->pos, info->dir, info->count, t->particleeffecttype);
|
|
else
|
|
failed = P_RunParticleEffectType(info->pos, NULL, info->count, t->particleeffecttype);
|
|
}
|
|
|
|
if (failed)
|
|
Con_DPrintf("Failed to create effect %s\n", t->name);
|
|
|
|
if (t->netstyle & CTE_STAINS)
|
|
{ //added at pos2 - end of trail
|
|
Surf_AddStain(info->pos2, t->stain[0], t->stain[1], t->stain[2], 40);
|
|
}
|
|
if (t->netstyle & CTE_GLOWS)
|
|
{ //added at pos1 firer's end.
|
|
dlight_t *dl;
|
|
dl = CL_AllocDlight (0);
|
|
VectorCopy (info->pos, dl->origin);
|
|
dl->radius = t->dlightradius*4;
|
|
dl->die = cl.time + t->dlighttime;
|
|
dl->decay = t->radius/t->dlighttime;
|
|
|
|
dl->color[0] = t->dlightrgb[0];
|
|
dl->color[1] = t->dlightrgb[1];
|
|
dl->color[2] = t->dlightrgb[2];
|
|
|
|
if (t->netstyle & CTE_CHANNELFADE)
|
|
{
|
|
dl->channelfade[0] = t->dlightcfade[0];
|
|
dl->channelfade[1] = t->dlightcfade[1];
|
|
dl->channelfade[2] = t->dlightcfade[2];
|
|
}
|
|
|
|
/*
|
|
if (dl->color[0] < 0)
|
|
dl->channelfade[0] = 0;
|
|
else
|
|
dl->channelfade[0] = dl->color[0]/t->dlighttime;
|
|
|
|
if (dl->color[1] < 0)
|
|
dl->channelfade[1] = 0;
|
|
else
|
|
dl->channelfade[1] = dl->color[0]/t->dlighttime;
|
|
|
|
if (dl->color[2] < 0)
|
|
dl->channelfade[2] = 0;
|
|
else
|
|
dl->channelfade[2] = dl->color[0]/t->dlighttime;
|
|
*/
|
|
}
|
|
}
|
|
|
|
void CL_RunPCustomTEnts(void)
|
|
{
|
|
custtentinst_t *ef;
|
|
static float lasttime;
|
|
float since = cl.time - lasttime;
|
|
if (since < 0)
|
|
lasttime = cl.time;
|
|
else if (since < 1/60.0)
|
|
return;
|
|
lasttime = cl.time;
|
|
for (ef = activepcusttents; ef; ef = ef->next)
|
|
{
|
|
CL_SpawnCustomTEnt(ef);
|
|
}
|
|
}
|
|
|
|
clcustomtents_t customtenttype[255]; //network based.
|
|
|
|
qboolean CL_WriteCustomTEnt(sizebuf_t *buf, int id)
|
|
{
|
|
clcustomtents_t *t;
|
|
if ((unsigned)id >= 255u)
|
|
return false;
|
|
t = &customtenttype[id];
|
|
if (*t->name)
|
|
{
|
|
MSG_WriteByte(buf, svcfte_customtempent);
|
|
MSG_WriteByte(buf, 255);
|
|
MSG_WriteByte(buf, id);
|
|
MSG_WriteByte(buf, t->netstyle);
|
|
MSG_WriteString(buf, t->name);
|
|
|
|
if (t->netstyle & CTE_STAINS)
|
|
{
|
|
MSG_WriteChar(buf, t->stain[0]);
|
|
MSG_WriteChar(buf, t->stain[1]);
|
|
MSG_WriteChar(buf, t->stain[2]);
|
|
MSG_WriteByte(buf, t->radius);
|
|
}
|
|
if (t->netstyle & CTE_GLOWS)
|
|
{
|
|
MSG_WriteByte(buf, t->dlightrgb[0]*255);
|
|
MSG_WriteByte(buf, t->dlightrgb[1]*255);
|
|
MSG_WriteByte(buf, t->dlightrgb[2]*255);
|
|
MSG_WriteByte(buf, t->dlightradius);
|
|
MSG_WriteByte(buf, t->dlighttime*16);
|
|
if (t->netstyle & CTE_CHANNELFADE)
|
|
{
|
|
MSG_WriteByte(buf, t->dlightcfade[0]*64);
|
|
MSG_WriteByte(buf, t->dlightcfade[1]*64);
|
|
MSG_WriteByte(buf, t->dlightcfade[2]*64);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CL_ParseCustomTEnt(void)
|
|
{
|
|
char *str;
|
|
clcustomtents_t *t;
|
|
int type = MSG_ReadByte();
|
|
custtentinst_t info;
|
|
|
|
if (type == 255) //255 is register
|
|
{
|
|
type = MSG_ReadByte();
|
|
if (type == 255)
|
|
Host_EndGame("Custom temp type 255 isn't valid\n");
|
|
t = &customtenttype[type];
|
|
|
|
t->netstyle = MSG_ReadByte();
|
|
str = MSG_ReadString();
|
|
Q_strncpyz(t->name, str, sizeof(t->name));
|
|
t->particleeffecttype = P_FindParticleType(str);
|
|
if (cl_shownet.ival >= 3)
|
|
Con_Printf("\tdefine \"%s\" (%x)\n", t->name, t->netstyle);
|
|
|
|
if (t->netstyle & CTE_STAINS)
|
|
{
|
|
t->stain[0] = MSG_ReadChar();
|
|
t->stain[1] = MSG_ReadChar();
|
|
t->stain[2] = MSG_ReadChar();
|
|
t->radius = MSG_ReadByte();
|
|
}
|
|
else
|
|
t->radius = 0;
|
|
if (t->netstyle & CTE_GLOWS)
|
|
{
|
|
t->dlightrgb[0] = MSG_ReadByte()/255.0f;
|
|
t->dlightrgb[1] = MSG_ReadByte()/255.0f;
|
|
t->dlightrgb[2] = MSG_ReadByte()/255.0f;
|
|
t->dlightradius = MSG_ReadByte();
|
|
t->dlighttime = MSG_ReadByte()/16.0f;
|
|
if (t->netstyle & CTE_CHANNELFADE)
|
|
{
|
|
t->dlightcfade[0] = MSG_ReadByte()/64.0f;
|
|
t->dlightcfade[1] = MSG_ReadByte()/64.0f;
|
|
t->dlightcfade[2] = MSG_ReadByte()/64.0f;
|
|
}
|
|
}
|
|
else
|
|
t->dlighttime = 0;
|
|
return;
|
|
}
|
|
|
|
t = &customtenttype[type];
|
|
|
|
if (cl_shownet.ival >= 3)
|
|
Con_Printf("\tspawn \"%s\" (%x)\n", t->name, t->netstyle);
|
|
|
|
info.type = t;
|
|
if (t->netstyle & CTE_PERSISTANT)
|
|
{
|
|
info.id = MSG_ReadShort();
|
|
}
|
|
else
|
|
info.id = 0;
|
|
|
|
if (info.id & 0x8000)
|
|
{
|
|
VectorClear(info.pos);
|
|
VectorClear(info.pos2);
|
|
info.count = 0;
|
|
VectorClear(info.dir);
|
|
}
|
|
else
|
|
{
|
|
MSG_ReadPos (info.pos);
|
|
|
|
if (t->netstyle & CTE_ISBEAM)
|
|
MSG_ReadPos (info.pos2);
|
|
else
|
|
VectorCopy(info.pos, info.pos2);
|
|
|
|
if (t->netstyle & CTE_CUSTOMCOUNT)
|
|
info.count = MSG_ReadByte();
|
|
else
|
|
info.count = 1;
|
|
|
|
if (t->netstyle & CTE_CUSTOMVELOCITY)
|
|
{
|
|
info.dir[0] = MSG_ReadCoord();
|
|
info.dir[1] = MSG_ReadCoord();
|
|
info.dir[2] = MSG_ReadCoord();
|
|
}
|
|
else if (t->netstyle & CTE_CUSTOMDIRECTION)
|
|
MSG_ReadDir (info.dir);
|
|
else
|
|
VectorClear(info.dir);
|
|
}
|
|
|
|
if (t->netstyle & CTE_PERSISTANT)
|
|
{
|
|
if (info.id & 0x8000)
|
|
{
|
|
custtentinst_t **link, *o;
|
|
for (link = &activepcusttents; *link; )
|
|
{
|
|
o = *link;
|
|
if (o->id == info.id)
|
|
{
|
|
*link = o->next;
|
|
Z_Free(o);
|
|
}
|
|
else
|
|
link = &(*link)->next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//heap fragmentation is going to suck here.
|
|
custtentinst_t *n = Z_Malloc(sizeof(*n));
|
|
info.next = activepcusttents;
|
|
*n = info;
|
|
activepcusttents = n;
|
|
}
|
|
}
|
|
else
|
|
CL_SpawnCustomTEnt(&info);
|
|
}
|
|
void CL_RefreshCustomTEnts(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(customtenttype)/sizeof(customtenttype[0]); i++)
|
|
{
|
|
customtenttype[i].particleeffecttype = (!*customtenttype[i].name)?-1:P_FindParticleType(customtenttype[i].name);
|
|
|
|
// if (customtenttype[i].particleeffecttype == P_INVALID && *customtenttype[i].name)
|
|
// Con_DPrintf("%s was not loaded\n", customtenttype[i].name);
|
|
}
|
|
|
|
if (cl.particle_ssprecaches)
|
|
{
|
|
for (i = 0; i < MAX_SSPARTICLESPRE; i++)
|
|
{
|
|
if (cl.particle_ssname[i])
|
|
cl.particle_ssprecache[i] = P_FindParticleType(cl.particle_ssname[i]);
|
|
else
|
|
cl.particle_ssprecache[i] = P_INVALID;
|
|
}
|
|
}
|
|
if (cl.particle_csprecaches)
|
|
{
|
|
for (i = 0; i < MAX_CSPARTICLESPRE; i++)
|
|
{
|
|
if (cl.particle_csname[i])
|
|
cl.particle_csprecache[i] = P_FindParticleType(cl.particle_csname[i]);
|
|
else
|
|
cl.particle_csprecache[i] = P_INVALID;
|
|
}
|
|
}
|
|
#ifdef CSQC_DAT
|
|
CSQC_ResetTrails();
|
|
#endif
|
|
}
|
|
void CL_ClearCustomTEnts(void)
|
|
{
|
|
int i;
|
|
custtentinst_t *p;
|
|
while(activepcusttents)
|
|
{
|
|
p = activepcusttents;
|
|
activepcusttents = p->next;
|
|
Z_Free(p);
|
|
}
|
|
for (i = 0; i < sizeof(customtenttype)/sizeof(customtenttype[0]); i++)
|
|
{
|
|
*customtenttype[i].name = 0;
|
|
customtenttype[i].particleeffecttype = -1;
|
|
}
|
|
}
|
|
|
|
int CL_TranslateParticleFromServer(int qceffect)
|
|
{
|
|
if (cl.particle_ssprecaches && qceffect >= 0 && qceffect < MAX_SSPARTICLESPRE)
|
|
{
|
|
/*proper precaches*/
|
|
return cl.particle_ssprecache[qceffect];
|
|
}
|
|
else if (-qceffect >= 0 && -qceffect < MAX_CSPARTICLESPRE)
|
|
{
|
|
qceffect = -qceffect;
|
|
return cl.particle_csprecache[qceffect];
|
|
}
|
|
else
|
|
return P_INVALID;
|
|
}
|
|
|
|
void CL_ParseTrailParticles(void)
|
|
{
|
|
int entityindex;
|
|
int effectindex;
|
|
vec3_t start, end;
|
|
trailkey_t *tk;
|
|
|
|
entityindex = MSGCL_ReadEntity();
|
|
effectindex = (unsigned short)MSG_ReadShort();
|
|
|
|
start[0] = MSG_ReadCoord();
|
|
start[1] = MSG_ReadCoord();
|
|
start[2] = MSG_ReadCoord();
|
|
end[0] = MSG_ReadCoord();
|
|
end[1] = MSG_ReadCoord();
|
|
end[2] = MSG_ReadCoord();
|
|
|
|
effectindex = CL_TranslateParticleFromServer(effectindex);
|
|
|
|
if (entityindex>0 && (unsigned int)entityindex < cl.maxlerpents)
|
|
tk = &cl.lerpents[entityindex].trailstate;
|
|
else
|
|
tk = NULL;
|
|
|
|
if (P_ParticleTrail(start, end, effectindex, 1, entityindex, NULL, tk))
|
|
P_ParticleTrail(start, end, rt_blood, 1, entityindex, NULL, tk);
|
|
}
|
|
|
|
void CL_ParsePointParticles(qboolean compact)
|
|
{
|
|
vec3_t org, dir;
|
|
unsigned int effectindex;
|
|
float count;
|
|
|
|
effectindex = (unsigned short)MSG_ReadShort();
|
|
org[0] = MSG_ReadCoord();
|
|
org[1] = MSG_ReadCoord();
|
|
org[2] = MSG_ReadCoord();
|
|
if (compact)
|
|
{
|
|
dir[0] = dir[1] = dir[2] = 0;
|
|
count = 1;
|
|
}
|
|
else
|
|
{
|
|
dir[0] = MSG_ReadCoord();
|
|
dir[1] = MSG_ReadCoord();
|
|
dir[2] = MSG_ReadCoord();
|
|
count = (unsigned short)MSG_ReadShort();
|
|
}
|
|
|
|
effectindex = CL_TranslateParticleFromServer(effectindex);
|
|
|
|
if (cl.splitclients <= 1 && cl_part_density_fade.value > 0)
|
|
{
|
|
vec3_t move;
|
|
float dist;
|
|
VectorSubtract(org, cl.playerview[0].audio.origin, move);
|
|
dist = VectorLength(move);
|
|
if (dist > cl_part_density_fade_start.value)
|
|
{
|
|
dist -= cl_part_density_fade_start.value;
|
|
count = count - dist/cl_part_density_fade.value;
|
|
if (count < 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (P_RunParticleEffectType(org, dir, count, effectindex))
|
|
P_RunParticleEffect (org, dir, 15, 15);
|
|
}
|
|
|
|
void CLNQ_ParseParticleEffect (void)
|
|
{
|
|
vec3_t org, dir;
|
|
int i, msgcount, color;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
org[i] = MSG_ReadCoord ();
|
|
for (i=0 ; i<3 ; i++)
|
|
dir[i] = MSG_ReadChar () * (1.0/16);
|
|
msgcount = MSG_ReadByte ();
|
|
color = MSG_ReadByte ();
|
|
|
|
if (msgcount == 255)
|
|
{
|
|
// treat as spriteless explosion (qtest/some mods require this)
|
|
if (P_RunParticleEffectType(org, NULL, 1, pt_explosion))
|
|
P_RunParticleEffect(org, NULL, 107, 1024); // should be 97-111
|
|
}
|
|
else
|
|
P_RunParticleEffect (org, dir, color, msgcount);
|
|
}
|
|
void CL_ParseParticleEffect2 (void)
|
|
{
|
|
vec3_t org, dmin, dmax;
|
|
int i, msgcount, color, effect;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
org[i] = MSG_ReadCoord ();
|
|
for (i=0 ; i<3 ; i++)
|
|
dmin[i] = MSG_ReadFloat ();
|
|
for (i=0 ; i<3 ; i++)
|
|
dmax[i] = MSG_ReadFloat ();
|
|
color = MSG_ReadShort ();
|
|
msgcount = MSG_ReadByte ();
|
|
effect = MSG_ReadByte ();
|
|
|
|
P_RunParticleEffect2 (org, dmin, dmax, color, effect, msgcount);
|
|
}
|
|
void CL_ParseParticleEffect3 (void)
|
|
{
|
|
vec3_t org, box;
|
|
int i, msgcount, color, effect;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
org[i] = MSG_ReadCoord ();
|
|
for (i=0 ; i<3 ; i++)
|
|
box[i] = MSG_ReadByte ();
|
|
color = MSG_ReadShort ();
|
|
msgcount = MSG_ReadByte ();
|
|
effect = MSG_ReadByte ();
|
|
|
|
P_RunParticleEffect3 (org, box, color, effect, msgcount);
|
|
}
|
|
void CL_ParseParticleEffect4 (void)
|
|
{
|
|
vec3_t org;
|
|
int i, msgcount, color, effect;
|
|
float radius;
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
org[i] = MSG_ReadCoord ();
|
|
radius = MSG_ReadByte();
|
|
color = MSG_ReadShort ();
|
|
msgcount = MSG_ReadByte ();
|
|
effect = MSG_ReadByte ();
|
|
|
|
P_RunParticleEffect4 (org, radius, color, effect, msgcount);
|
|
}
|
|
|
|
void CL_SpawnSpriteEffect(vec3_t org, vec3_t dir, vec3_t orientationup, model_t *model, int startframe, int framecount, float framerate, float alpha, float scale, float randspin, float gravity, int traileffect, unsigned int renderflags, int skinnum)
|
|
{
|
|
explosion_t *ex;
|
|
|
|
ex = CL_AllocExplosion (org);
|
|
ex->start = cl.time;
|
|
ex->model = model;
|
|
ex->firstframe = startframe;
|
|
ex->numframes = framecount;
|
|
ex->framerate = framerate;
|
|
ex->skinnum = skinnum;
|
|
ex->traileffect = traileffect;
|
|
ex->scale = scale;
|
|
|
|
ex->flags |= renderflags;
|
|
|
|
//sprites always use a fixed alpha. models can too if the alpha is < 0
|
|
if (model->type == mod_sprite || alpha < 0)
|
|
ex->endalpha = fabs(alpha);
|
|
ex->startalpha = fabs(alpha);
|
|
|
|
if (ex->endalpha < 1 || ex->startalpha < 1)
|
|
ex->flags |= RF_TRANSLUCENT;
|
|
|
|
if (randspin)
|
|
{
|
|
ex->angles[0] = frandom()*360;
|
|
ex->angles[1] = frandom()*360;
|
|
ex->angles[2] = frandom()*360;
|
|
|
|
ex->avel[0] = crandom()*randspin;
|
|
ex->avel[1] = crandom()*randspin;
|
|
ex->avel[2] = crandom()*randspin;
|
|
}
|
|
ex->gravity = gravity;
|
|
|
|
if (orientationup)
|
|
{
|
|
ex->angles[0] = acos(orientationup[2])/M_PI*180;
|
|
if (orientationup[0])
|
|
ex->angles[1] = atan2(orientationup[1], orientationup[0])/M_PI*180;
|
|
else if (orientationup[1] > 0)
|
|
ex->angles[1] = 90;
|
|
else if (orientationup[1] < 0)
|
|
ex->angles[1] = 270;
|
|
else
|
|
ex->angles[1] = 0;
|
|
ex->angles[0]*=r_meshpitch.value;
|
|
ex->angles[2]*=r_meshroll.value;
|
|
}
|
|
|
|
|
|
if (dir)
|
|
{
|
|
// vec3_t spos;
|
|
// float dlen;
|
|
// dlen = -10/VectorLength(dir);
|
|
// VectorMA(ex->origin, dlen, dir, spos);
|
|
// TraceLineN(spos, org, ex->origin, NULL);
|
|
|
|
VectorCopy(dir, ex->velocity);
|
|
}
|
|
else
|
|
VectorClear(ex->velocity);
|
|
}
|
|
|
|
// [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate
|
|
// [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate
|
|
void CL_ParseEffect (qboolean effect2)
|
|
{
|
|
vec3_t org;
|
|
int modelindex;
|
|
int startframe;
|
|
int framecount;
|
|
int framerate;
|
|
model_t *mod;
|
|
|
|
org[0] = MSG_ReadCoord();
|
|
org[1] = MSG_ReadCoord();
|
|
org[2] = MSG_ReadCoord();
|
|
|
|
if (effect2)
|
|
modelindex = MSG_ReadShort();
|
|
else
|
|
modelindex = MSG_ReadByte();
|
|
|
|
if (effect2)
|
|
startframe = MSG_ReadShort();
|
|
else
|
|
startframe = MSG_ReadByte();
|
|
|
|
framecount = MSG_ReadByte();
|
|
framerate = MSG_ReadByte();
|
|
|
|
mod = cl.model_precache[modelindex];
|
|
CL_SpawnSpriteEffect(org, NULL, NULL, mod, startframe, framecount, framerate, mod->type==mod_sprite?-1:1, 1, 0, 0, P_INVALID, 0, 0);
|
|
}
|
|
|
|
#ifdef Q2CLIENT
|
|
void CL_SmokeAndFlash(vec3_t origin)
|
|
{
|
|
explosion_t *ex;
|
|
|
|
ex = CL_AllocExplosion (origin);
|
|
VectorClear(ex->angles);
|
|
// ex->type = ex_misc;
|
|
ex->numframes = 4;
|
|
ex->flags = RF_TRANSLUCENT;
|
|
ex->start = cl.time;
|
|
ex->model = Mod_ForName (q2tentmodels[q2cl_mod_smoke].modelname, MLV_WARN);
|
|
|
|
ex = CL_AllocExplosion (origin);
|
|
VectorClear(ex->angles);
|
|
// ex->type = ex_flash;
|
|
ex->flags = RF_FULLBRIGHT;
|
|
ex->numframes = 2;
|
|
ex->start = cl.time;
|
|
ex->model = Mod_ForName (q2tentmodels[q2cl_mod_flash].modelname, MLV_WARN);
|
|
}
|
|
|
|
void CL_Laser (vec3_t start, vec3_t end, int colors)
|
|
{
|
|
explosion_t *ex = CL_AllocExplosion(start);
|
|
ex->firstframe = 0;
|
|
ex->numframes = 10;
|
|
ex->startalpha = 0.33f;
|
|
ex->endalpha = 0;
|
|
ex->model = NULL;
|
|
ex->skinnum = (colors >> ((rand() % 4)*8)) & 0xff;
|
|
VectorCopy (start, ex->origin);
|
|
VectorCopy (end, ex->oldorigin);
|
|
ex->flags = RF_TRANSLUCENT | Q2RF_BEAM;
|
|
ex->start = cl.time;
|
|
ex->framerate = 100; // smoother fading
|
|
}
|
|
|
|
void CLQ2_ParseSteam(void)
|
|
{
|
|
vec3_t pos, dir;
|
|
/*qbyte colour;
|
|
short magnitude;
|
|
unsigned int duration;*/
|
|
signed int id = MSG_ReadShort();
|
|
/*qbyte count =*/ MSG_ReadByte();
|
|
MSG_ReadPos(pos);
|
|
MSG_ReadDir(dir);
|
|
/*colour =*/ MSG_ReadByte();
|
|
/*magnitude =*/ MSG_ReadShort();
|
|
|
|
if (id != -1)
|
|
/*duration =*/ MSG_ReadLong();
|
|
else
|
|
/*duration = 0;*/
|
|
|
|
Con_Printf("FIXME: CLQ2_ParseSteam: stub\n");
|
|
}
|
|
|
|
void CLQ2_ParseTEnt (void)
|
|
{
|
|
beam_t *b;
|
|
q2particleeffects_t type;
|
|
int pt;
|
|
vec3_t pos, pos2, dir;
|
|
int cnt;
|
|
int color;
|
|
int r;
|
|
int ent;
|
|
// int magnitude;
|
|
// explosion_t *ex;
|
|
|
|
type = MSG_ReadByte ();
|
|
|
|
if (type <= Q2TE_MAX)
|
|
{
|
|
if (cl_shownet.ival > 1)
|
|
Con_Printf("\t%s\n", q2efnames[type]);
|
|
pt = pt_q2[type];
|
|
}
|
|
else
|
|
{
|
|
if (cl_shownet.ival > 1)
|
|
Con_Printf("\tTE%u\n", type);
|
|
pt = P_INVALID;
|
|
}
|
|
switch (type)
|
|
{
|
|
case Q2TE_GUNSHOT: //grey tall thing with smoke+sparks
|
|
case Q2TE_BLOOD: //red tall thing
|
|
case Q2TE_SPARKS: //orange tall thing (with not many particles)
|
|
case Q2TE_BLASTER: //regular blaster
|
|
case Q2TE_SHOTGUN: //gunshot with less particles
|
|
case Q2TE_SCREEN_SPARKS://green+grey tall
|
|
case Q2TE_SHIELD_SPARKS://blue+grey tall
|
|
case Q2TE_BULLET_SPARKS://orange+grey tall+smoke
|
|
case Q2TE_GREENBLOOD: //yellow...
|
|
case Q2TE_BLASTER2: //green version of te_blaster
|
|
case Q2TE_MOREBLOOD: //te_blood*2
|
|
case Q2TE_HEATBEAM_SPARKS://white outwards puffs
|
|
case Q2TE_HEATBEAM_STEAM://orange outwards puffs
|
|
case Q2TE_ELECTRIC_SPARKS://blue tall
|
|
case Q2TE_FLECHETTE: //grey version of te_blaster
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadDir (dir);
|
|
P_RunParticleEffectType(pos, dir, 1, pt);
|
|
break;
|
|
case Q2TE_BFG_LASER:
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadPos (pos2);
|
|
CL_Laser(pos, pos2, 0xd0d1d2d3);
|
|
break;
|
|
case Q2TE_RAILTRAIL: //blue spiral, grey particles
|
|
case Q2TE_BUBBLETRAIL: //grey sparse trail, slow riser
|
|
// case Q2TE_BFG_LASER: //green laser
|
|
case Q2TE_DEBUGTRAIL: //long lived blue trail
|
|
case Q2TE_BUBBLETRAIL2: //grey rising trail
|
|
case Q2TE_BLUEHYPERBLASTER: //TE_BLASTER without model+light
|
|
case Q2TEEX_RAILTRAIL2:
|
|
case Q2TEEX_BFGZAP:
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadPos (pos2);
|
|
P_ParticleTrail(pos, pos2, pt, 1, 0, NULL, NULL);
|
|
P_RunParticleEffectTypeString(pos2, NULL, 1, va("%s_end", q2efnames[type]));
|
|
break;
|
|
case Q2TE_EXPLOSION1: //column
|
|
case Q2TE_EXPLOSION2: //splits
|
|
case Q2TE_ROCKET_EXPLOSION://top blob/column
|
|
case Q2TE_GRENADE_EXPLOSION://indistinguishable from TE_EXPLOSION2
|
|
case Q2TE_ROCKET_EXPLOSION_WATER://rocket but with different sound
|
|
case Q2TE_GRENADE_EXPLOSION_WATER://different sound
|
|
case Q2TE_BFG_EXPLOSION://green light+sprite
|
|
case Q2TE_BFG_BIGEXPLOSION://green+white fast particles
|
|
case Q2TE_BOSSTPORT://splitting+merging+upwards particles.
|
|
case Q2TE_PLASMA_EXPLOSION://looks like rocket explosion to me
|
|
case Q2TE_PLAIN_EXPLOSION://looks like rocket explosion to me
|
|
case Q2TE_CHAINFIST_SMOKE://small smoke
|
|
case Q2TE_TRACKER_EXPLOSION://black light, slow particles
|
|
case Q2TE_TELEPORT_EFFECT://q1-style teleport
|
|
case Q2TE_DBALL_GOAL://q1-style teleport
|
|
case Q2TE_NUKEBLAST://dome expansion (blue/white particles)
|
|
case Q2TE_WIDOWSPLASH://dome (orange+gravity)
|
|
case Q2TE_EXPLOSION1_BIG://buggy model
|
|
case Q2TE_EXPLOSION1_NP://looks like a rocket explosion to me
|
|
MSG_ReadPos (pos);
|
|
P_RunParticleEffectType(pos, NULL, 1, pt);
|
|
break;
|
|
case Q2TE_SPLASH:
|
|
cnt = MSG_ReadByte ();
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadDir (dir);
|
|
r = MSG_ReadByte () + Q2SPLASH_UNKNOWN;
|
|
if (r > Q2SPLASH_MAX)
|
|
r = Q2SPLASH_UNKNOWN;
|
|
pt = pt_q2[r];
|
|
P_RunParticleEffectType(pos, NULL, 1, pt);
|
|
break;
|
|
|
|
case Q2TE_PARASITE_ATTACK:
|
|
case Q2TE_MEDIC_CABLE_ATTACK:
|
|
CL_ParseBeam (BT_Q2PARASITE);
|
|
break;
|
|
case Q2TE_HEATBEAM:
|
|
b = CL_ParseBeam (BT_Q2HEATBEAM); //2, 7, -3
|
|
if (b)
|
|
{
|
|
b->bflags |= STREAM_ATTACHED;
|
|
VectorSet(b->offset, 2, 7, -3);
|
|
}
|
|
break;
|
|
case Q2TE_MONSTER_HEATBEAM:
|
|
b = CL_ParseBeam (BT_Q2HEATBEAM);
|
|
if (b)
|
|
b->bflags |= STREAM_ATTACHED;
|
|
break;
|
|
case Q2TE_GRAPPLE_CABLE:
|
|
CL_ParseBeamOffset (BT_Q2GRAPPLE);
|
|
break;
|
|
|
|
case Q2TE_LIGHTNING:
|
|
ent = MSGCL_ReadEntity ();
|
|
/*toent =*/ MSGCL_ReadEntity (); //ident only.
|
|
pos[0] = MSG_ReadCoord ();
|
|
pos[1] = MSG_ReadCoord ();
|
|
pos[2] = MSG_ReadCoord ();
|
|
pos2[0] = MSG_ReadCoord ();
|
|
pos2[1] = MSG_ReadCoord ();
|
|
pos2[2] = MSG_ReadCoord ();
|
|
CL_AddBeam(BT_Q2LIGHTNING, ent, pos, pos2);
|
|
break;
|
|
|
|
case Q2TE_LASER_SPARKS:
|
|
case Q2TE_WELDING_SPARKS:
|
|
case Q2TE_TUNNEL_SPARKS:
|
|
cnt = MSG_ReadByte ();
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadDir (dir);
|
|
color = MSG_ReadByte ();
|
|
P_RunParticleEffectPalette(va("q2part.%s", q2efnames[type]), pos, dir, color, cnt);
|
|
break;
|
|
|
|
case Q2TE_STEAM:
|
|
CLQ2_ParseSteam();
|
|
break;
|
|
|
|
case Q2TE_FORCEWALL:
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadPos (pos2);
|
|
color = MSG_ReadByte ();
|
|
P_ParticleTrailIndex(pos, pos2, pt, 1, color, 0, NULL);
|
|
break;
|
|
|
|
case Q2TE_FLASHLIGHT: //white 400-radius dlight
|
|
MSG_ReadPos(pos);
|
|
ent = MSG_ReadShort();
|
|
P_ParticleTrail(pos, pos, pt, 1, ent, NULL, NULL);
|
|
break;
|
|
case Q2TE_WIDOWBEAMOUT: /*requires state tracking to keep it splurting constantly for 2.1 secs*/
|
|
ent = MSG_ReadShort();
|
|
MSG_ReadPos(pos);
|
|
Con_Printf("FIXME: Q2TE_WIDOWBEAMOUT not implemented\n");
|
|
break;
|
|
|
|
//case Q2TE_RAILTRAIL2: /*not implemented in vanilla*/
|
|
case Q2TE_FLAME: /*not implemented in vanilla*/
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
break;
|
|
|
|
case Q2TEEX_BLUEHYPERBLASTER:
|
|
case Q2TEEX_BERSERK_SLAM:
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadDir (pos2);
|
|
P_RunParticleEffectType(pos, dir, 1, pt);
|
|
break;
|
|
|
|
case Q2TEEX_GRAPPLE_CABLE_2:
|
|
CL_ParseBeam (BT_Q2GRAPPLE);
|
|
break;
|
|
|
|
case Q2TEEX_POWER_SPLASH:
|
|
MSGCL_ReadEntity();
|
|
MSG_ReadByte();
|
|
break;
|
|
case Q2TEEX_LIGHTNING_BEAM:
|
|
CL_ParseBeam (BT_Q2LIGHTNING);
|
|
break;
|
|
case Q2TEEX_EXPLOSION1_NL:
|
|
case Q2TEEX_EXPLOSION2_NL:
|
|
MSG_ReadPos (pos);
|
|
P_RunParticleEffectType(pos, NULL, 1, pt);
|
|
break;
|
|
|
|
|
|
//My old attempt at running AlienArena years ago. probably not enough now. Other engines will have other effects.
|
|
/* case CRTE_LEADERBLASTER:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
case CRTE_BLASTER_MUZZLEFLASH:
|
|
MSG_ReadPos (pos);
|
|
ex = CL_AllocExplosion (pos);
|
|
ex->flags = RF_FULLBRIGHT|RF_NOSHADOW;
|
|
ex->start = cl.q2frame.servertime - 100;
|
|
CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0.1*5, 0*5);
|
|
P_RunParticleEffectTypeString(pos, NULL, 1, "te_muzzleflash");
|
|
break;
|
|
case CRTE_BLUE_MUZZLEFLASH:
|
|
MSG_ReadPos (pos);
|
|
ex = CL_AllocExplosion (pos);
|
|
ex->flags = RF_FULLBRIGHT|RF_NOSHADOW;
|
|
ex->start = cl.q2frame.servertime - 100;
|
|
CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0.1*5, 0*5);
|
|
P_RunParticleEffectTypeString(pos, NULL, 1, "te_blue_muzzleflash");
|
|
break;
|
|
case CRTE_SMART_MUZZLEFLASH:
|
|
MSG_ReadPos (pos);
|
|
ex = CL_AllocExplosion (pos);
|
|
ex->flags = RF_FULLBRIGHT|RF_NOSHADOW;
|
|
ex->start = cl.q2frame.servertime - 100;
|
|
CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0*5, 0.2*5);
|
|
P_RunParticleEffectTypeString(pos, NULL, 1, "te_smart_muzzleflash");
|
|
break;
|
|
case CRTE_LEADERFIELD:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
case CRTE_DEATHFIELD:
|
|
MSG_ReadPos (pos);
|
|
ex = CL_AllocExplosion (pos);
|
|
VectorCopy (pos, ex->origin);
|
|
ex->flags = RF_FULLBRIGHT|RF_NOSHADOW;
|
|
ex->start = cl.q2frame.servertime - 100;
|
|
CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0*5, 0.2*5);
|
|
P_RunParticleEffectTypeString(pos, NULL, 1, "te_deathfield");
|
|
break;
|
|
case CRTE_BLASTERBEAM:
|
|
MSG_ReadPos (pos);
|
|
MSG_ReadPos (pos2);
|
|
P_ParticleTrail(pos, pos2, P_FindParticleType("q2part.TR_BLASTERTRAIL2"), 1, 0, NULL, NULL);
|
|
break;
|
|
case CRTE_STAIN:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
case CRTE_FIRE:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
case CRTE_CABLEGUT:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
case CRTE_SMOKE:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
*/
|
|
|
|
default:
|
|
Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
=================
|
|
CL_NewTempEntity
|
|
=================
|
|
*/
|
|
entity_t *CL_NewTempEntity (void)
|
|
{
|
|
entity_t *ent;
|
|
|
|
if (cl_numvisedicts == cl_maxvisedicts)
|
|
return NULL;
|
|
ent = &cl_visedicts[cl_numvisedicts];
|
|
cl_numvisedicts++;
|
|
ent->keynum = 0;
|
|
|
|
memset (ent, 0, sizeof(*ent));
|
|
ent->playerindex = -1;
|
|
ent->topcolour = TOP_DEFAULT;
|
|
ent->bottomcolour = BOTTOM_DEFAULT;
|
|
|
|
#ifdef PEXT_SCALE
|
|
ent->scale = 1;
|
|
#endif
|
|
ent->shaderRGBAf[0] = 1;
|
|
ent->shaderRGBAf[1] = 1;
|
|
ent->shaderRGBAf[2] = 1;
|
|
ent->shaderRGBAf[3] = 1;
|
|
return ent;
|
|
}
|
|
|
|
qboolean CSQC_GetEntityOrigin(unsigned int csqcent, float *out);
|
|
|
|
/*
|
|
=================
|
|
CL_UpdateBeams
|
|
=================
|
|
*/
|
|
void CL_UpdateBeams (float frametime)
|
|
{
|
|
int bnum;
|
|
int i, j;
|
|
beam_t *b;
|
|
vec3_t dist, org;
|
|
float *vieworg;
|
|
float d;
|
|
entity_t *ent;
|
|
entity_state_t *st;
|
|
float yaw, pitch;
|
|
float forward, offset;
|
|
int lastrunningbeam = -1;
|
|
tentmodels_t *type;
|
|
|
|
extern cvar_t cl_truelightning, v_viewheight;
|
|
|
|
// update lightning
|
|
for (bnum=0, b=cl_beams; bnum < beams_running; bnum++, b++)
|
|
{
|
|
type = b->info;
|
|
if (!type)
|
|
continue;
|
|
|
|
if (b->endtime < cl.time)
|
|
{
|
|
if (!cl.paused)
|
|
{ /*don't let lightning decay while paused*/
|
|
P_DelinkTrailstate(&b->trailstate);
|
|
P_DelinkTrailstate(&b->emitstate);
|
|
b->info = NULL;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
lastrunningbeam = bnum;
|
|
|
|
// if coming from the player, update the start position
|
|
if ((b->bflags & STREAM_ATTACHTOPLAYER) && b->entity > 0 && b->entity <= cl.allocated_client_slots)
|
|
{
|
|
for (j = 0; j < cl.splitclients; j++)
|
|
{
|
|
playerview_t *pv = &cl.playerview[j];
|
|
if (b->entity == pv->viewentity)
|
|
{
|
|
// player_state_t *pl;
|
|
// VectorSubtract(cl.simorg, b->start, org);
|
|
// VectorAdd(b->end, org, b->end); //move the end point by simorg-start
|
|
|
|
// pl = &cl.inframes[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
|
|
// if (pl->messagenum == cl.parsecount || cls.protocol == CP_NETQUAKE)
|
|
{
|
|
vec3_t fwd, org, ang, viewang;
|
|
float delta, f, len;
|
|
|
|
// if (cl.spectator && pv->cam_auto)
|
|
// { //if we're tracking someone, use their origin explicitly.
|
|
// vieworg = pl->origin;
|
|
// }
|
|
// else
|
|
#ifdef Q2CLIENT
|
|
if (cls.protocol == CP_QUAKE2)
|
|
vieworg = pv->predicted_origin;
|
|
else
|
|
#endif
|
|
vieworg = pv->simorg;
|
|
|
|
if (cl_truelightning.ival > 1 && cl.movesequence > cl_truelightning.ival)
|
|
{
|
|
outframe_t *frame = &cl.outframes[(cl.movesequence-cl_truelightning.ival)&UPDATE_MASK];
|
|
viewang[0] = SHORT2ANGLE(frame->cmd[j].angles[0]);
|
|
viewang[1] = SHORT2ANGLE(frame->cmd[j].angles[1]);
|
|
viewang[2] = SHORT2ANGLE(frame->cmd[j].angles[2]);
|
|
}
|
|
else
|
|
VectorCopy(pv->simangles, viewang);
|
|
|
|
VectorCopy (vieworg, b->start);
|
|
b->start[2] += pv->crouch + bound(-7, v_viewheight.value, 4);
|
|
|
|
f = bound(0, cl_truelightning.value, 1);
|
|
|
|
if (!f)
|
|
break;
|
|
|
|
VectorSubtract (playerbeam_end[j], vieworg, org);
|
|
len = VectorLength(org);
|
|
org[2] -= 22; // adjust for view height
|
|
VectorAngles (org, NULL, ang, false);
|
|
|
|
// lerp pitch
|
|
delta = anglemod(viewang[0] - ang[0]);
|
|
if (delta > 180)
|
|
delta -= 360;
|
|
if (ang[0] < -180)
|
|
ang[0] += 360;
|
|
ang[0] += delta * f;
|
|
|
|
// lerp yaw
|
|
delta = anglemod(viewang[1] - ang[1]);
|
|
if (delta > 180)
|
|
delta -= 360;
|
|
if (delta < -180)
|
|
delta += 360;
|
|
ang[1] += delta * f;
|
|
ang[2] = 0;
|
|
|
|
AngleVectors (ang, fwd, ang, ang);
|
|
VectorCopy(fwd, ang);
|
|
VectorScale (fwd, len, fwd);
|
|
VectorCopy (vieworg, org);
|
|
org[2] += 16;
|
|
VectorAdd (org, fwd, b->end);
|
|
|
|
if (cl_beam_trace.ival)
|
|
{
|
|
vec3_t normal;
|
|
VectorMA(org, len+4, ang, fwd);
|
|
if (CL_TraceLine(org, fwd, ang, normal, NULL) < 1)
|
|
VectorCopy (ang, b->end);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
VectorCopy (b->start, org);
|
|
}
|
|
#ifdef CSQC_DAT
|
|
else if ((b->bflags & 1) && b->entity > MAX_EDICTS)
|
|
{
|
|
CSQC_GetEntityOrigin(b->entity-MAX_EDICTS, org);
|
|
VectorCopy (b->start, org);
|
|
}
|
|
#endif
|
|
else if (b->bflags & STREAM_ATTACHED)
|
|
{
|
|
player_state_t *pl;
|
|
st = CL_FindPacketEntity(b->entity);
|
|
if (st)
|
|
{
|
|
VectorCopy(st->origin, b->start);
|
|
}
|
|
else if (b->entity <= cl.allocated_client_slots && b->entity > 0)
|
|
{
|
|
pl = &cl.inframes[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
|
|
VectorCopy(pl->origin, b->start);
|
|
}
|
|
|
|
VectorCopy (b->start, org);
|
|
}
|
|
else
|
|
VectorCopy (b->start, org);
|
|
VectorAdd(org, 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
|
|
{
|
|
yaw = (atan2(dist[1], dist[0]) * 180 / M_PI);
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
|
|
forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
|
|
pitch = (atan2(dist[2], forward) * 180 / M_PI);
|
|
if (pitch < 0)
|
|
pitch += 360;
|
|
}
|
|
|
|
if (ruleset_allow_particle_lightning.ival || !type->modelname)
|
|
if (type->ef_beam >= 0 && !P_ParticleTrail(org, b->end, type->ef_beam, frametime, b->entity, NULL, &b->trailstate))
|
|
continue;
|
|
if (!type->model)
|
|
{
|
|
type->model = type->modelname?Mod_ForName(type->modelname, MLV_WARN):NULL;
|
|
if (!type->model)
|
|
continue;
|
|
}
|
|
|
|
// add new entities for the lightning
|
|
d = VectorNormalize(dist);
|
|
|
|
if(b->bflags & STREAM_JITTER)
|
|
{
|
|
offset = (int)(cl.time*40)%30;
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
org[i] += dist[i]*offset;
|
|
}
|
|
}
|
|
|
|
while (d > 0)
|
|
{
|
|
ent = CL_NewTempEntity ();
|
|
if (!ent)
|
|
return;
|
|
VectorCopy (org, ent->origin);
|
|
ent->model = type->model;
|
|
ent->skinnum = b->skin;
|
|
#ifdef HEXEN2
|
|
ent->drawflags |= MLS_ABSLIGHT;
|
|
ent->abslight = 64 + 128 * bound(0, cl_shaftlight.value, 1);
|
|
#endif
|
|
ent->shaderRGBAf[3] = b->alpha;
|
|
ent->flags = b->rflags;
|
|
|
|
ent->angles[0] = -pitch;
|
|
ent->angles[1] = yaw;
|
|
ent->angles[2] = rand()%360;
|
|
AngleVectorsFLU(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
|
|
ent->angles[0] = pitch;
|
|
ent->framestate.g[FS_REG].lerpweight[0] = 1;
|
|
ent->framestate.g[FS_REG].frame[0] = 0;
|
|
ent->framestate.g[FS_REG].frametime[0] = cl.time - (b->endtime - 0.2);
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
org[i] += dist[i]*30;
|
|
d -= 30;
|
|
}
|
|
|
|
if (type->beamimpactmodel)
|
|
{
|
|
if (!type->impactmodel)
|
|
{
|
|
type->impactmodel = Mod_ForName(type->beamimpactmodel, MLV_WARN);
|
|
if (!type->impactmodel)
|
|
continue;
|
|
}
|
|
ent = CL_NewTempEntity ();
|
|
if (!ent)
|
|
return;
|
|
VectorCopy (b->end, ent->origin);
|
|
ent->model = type->impactmodel;
|
|
#ifdef HEXEN2
|
|
ent->drawflags |= MLS_ABSLIGHT;
|
|
ent->abslight = 128;
|
|
#endif
|
|
ent->shaderRGBAf[3] = b->alpha;
|
|
ent->flags = b->rflags;
|
|
ent->scale = type->impactscale;
|
|
|
|
ent->angles[0] = -pitch;
|
|
ent->angles[1] = yaw;
|
|
ent->angles[2] = rand()%360;
|
|
AngleVectorsFLU(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
|
|
ent->angles[0] = pitch;
|
|
ent->framestate.g[FS_REG].lerpweight[0] = 1;
|
|
ent->framestate.g[FS_REG].frame[0] = 0;
|
|
ent->framestate.g[FS_REG].frametime[0] = cl.time - (b->endtime - 0.2);
|
|
}
|
|
}
|
|
|
|
beams_running = lastrunningbeam+1;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_UpdateExplosions
|
|
=================
|
|
*/
|
|
void CL_UpdateExplosions (float frametime)
|
|
{
|
|
int i;
|
|
float f;
|
|
int of;
|
|
int numframes;
|
|
int firstframe;
|
|
explosion_t *ex;
|
|
entity_t *ent;
|
|
int lastrunningexplosion = -1;
|
|
vec3_t pos, norm;
|
|
float scale;
|
|
|
|
for (i=0, ex=cl_explosions; i < explosions_running; i++, ex++)
|
|
{
|
|
if (!ex->model && !(ex->flags&Q2RF_BEAM))
|
|
continue;
|
|
|
|
lastrunningexplosion = i;
|
|
if (ex->model)
|
|
{
|
|
if (ex->model->loadstate == MLS_LOADING)
|
|
continue;
|
|
if (ex->model->loadstate != MLS_LOADED)
|
|
{
|
|
ex->model = NULL;
|
|
ex->flags = 0;
|
|
P_DelinkTrailstate(&(ex->trailstate));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
f = ex->framerate*(cl.time - ex->start);
|
|
|
|
if (ex->firstframe >= 0)
|
|
{
|
|
firstframe = ex->firstframe;
|
|
numframes = ex->numframes;
|
|
|
|
if (!numframes)
|
|
numframes = ex->model->numframes - firstframe;
|
|
}
|
|
else
|
|
{
|
|
firstframe = 0;
|
|
numframes = ex->model->numframes;
|
|
}
|
|
|
|
of = (int)f-1;
|
|
if (ex->endalpha && (int)f == numframes)
|
|
{
|
|
scale = 1-(f-(int)f); //if we have endalpha not 0, then there is a final 'bonus' frame where the model scales down to 0. use numframes+framerate to control how fast it fades.
|
|
f = of; //clamp it to the old frame
|
|
}
|
|
else if ((int)f >= numframes || (int)f < 0)
|
|
{
|
|
ex->model = NULL;
|
|
ex->flags = 0;
|
|
P_DelinkTrailstate(&(ex->trailstate));
|
|
continue;
|
|
}
|
|
else
|
|
scale = 1;
|
|
if (of < 0)
|
|
of = 0;
|
|
|
|
ent = CL_NewTempEntity ();
|
|
if (!ent)
|
|
return;
|
|
|
|
if (ex->gravity)
|
|
{
|
|
VectorMA(ex->origin, frametime, ex->velocity, pos);
|
|
if (ex->velocity[0] || ex->velocity[1] || ex->velocity[2])
|
|
{
|
|
VectorClear(norm);
|
|
if (CL_TraceLine(ex->origin, pos, ent->origin, norm, NULL) < 1)
|
|
{
|
|
float sc = DotProduct(ex->velocity, norm) * -1.5;
|
|
VectorMA(ex->velocity, sc, norm, ex->velocity);
|
|
VectorScale(ex->velocity, 0.9, ex->velocity);
|
|
if (norm[2] > 0.7 && DotProduct(ex->velocity, ex->velocity) < 100)
|
|
{
|
|
VectorClear(ex->velocity);
|
|
VectorClear(ex->avel);
|
|
}
|
|
}
|
|
else
|
|
ex->velocity[2] -= ex->gravity * frametime;
|
|
VectorCopy(ent->origin, ex->origin);
|
|
}
|
|
else
|
|
VectorCopy(ex->origin, ent->origin);
|
|
}
|
|
else
|
|
{
|
|
VectorMA (ex->origin, f, ex->velocity, ent->origin);
|
|
}
|
|
VectorMA(ex->angles, frametime, ex->avel, ex->angles);
|
|
|
|
VectorCopy (ex->oldorigin, ent->oldorigin);
|
|
VectorCopy (ex->angles, ent->angles);
|
|
ent->skinnum = ex->skinnum;
|
|
ent->angles[0]*=r_meshpitch.value;
|
|
ent->angles[2]*=r_meshroll.value;
|
|
AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
|
|
VectorInverse(ent->axis[1]);
|
|
ent->model = ex->model;
|
|
ent->framestate.g[FS_REG].frame[1] = (int)f+firstframe;
|
|
ent->framestate.g[FS_REG].frame[0] = of+firstframe;
|
|
ent->framestate.g[FS_REG].lerpweight[1] = (f - (int)f);
|
|
ent->framestate.g[FS_REG].lerpweight[0] = 1-ent->framestate.g[FS_REG].lerpweight[1];
|
|
ent->shaderRGBAf[3] = (1.0 - f/(numframes))*(ex->startalpha-ex->endalpha) + ex->endalpha;
|
|
ent->flags = ex->flags;
|
|
ent->scale = scale*ex->scale;
|
|
#ifdef HEXEN2
|
|
ent->drawflags = SCALE_ORIGIN_ORIGIN;
|
|
#endif
|
|
cl.worldmodel->funcs.FindTouchedLeafs(cl.worldmodel, &ent->pvscache, ent->origin, ent->origin);
|
|
|
|
if (ex->traileffect != P_INVALID)
|
|
pe->ParticleTrail(ent->oldorigin, ent->origin, ex->traileffect, frametime, 0, ent->axis, &(ex->trailstate));
|
|
if (!(ex->flags & Q2RF_BEAM))
|
|
VectorCopy(ent->origin, ex->oldorigin); //don't corrupt q2 beams
|
|
if (ex->flags & Q2RF_BEAM)
|
|
{
|
|
ent->rtype = RT_BEAM;
|
|
ent->shaderRGBAf[0] = ((d_8to24srgbtable[ex->skinnum & 0xFF] >> 0) & 0xFF)/255.0;
|
|
ent->shaderRGBAf[1] = ((d_8to24srgbtable[ex->skinnum & 0xFF] >> 8) & 0xFF)/255.0;
|
|
ent->shaderRGBAf[2] = ((d_8to24srgbtable[ex->skinnum & 0xFF] >> 16) & 0xFF)/255.0;
|
|
}
|
|
else if (ex->skinnum < 0)
|
|
{
|
|
ent->skinnum = 7*f/(numframes);
|
|
}
|
|
}
|
|
|
|
explosions_running = lastrunningexplosion + 1;
|
|
}
|
|
|
|
entity_state_t *CL_FindPacketEntity(int num);
|
|
|
|
/*
|
|
=================
|
|
CL_UpdateTEnts
|
|
=================
|
|
*/
|
|
void CL_UpdateTEnts (void)
|
|
{
|
|
static float oldtime;
|
|
float frametime = cl.time - oldtime;
|
|
if (frametime < 0 || frametime > 100)
|
|
frametime = 0;
|
|
oldtime = cl.time;
|
|
|
|
CL_UpdateBeams (frametime);
|
|
CL_UpdateExplosions (frametime);
|
|
CL_RunPCustomTEnts ();
|
|
}
|