/* 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", 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", "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", }; 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 { /*static stuff*/ char *modelname; char *beamparticles; char *beamimpactparticle; int bflags; /*cached stuff*/ model_t *model; 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. trailstate_t *trailstate; trailstate_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; trailstate_t *trailstate; } explosion_t; explosion_t *cl_explosions; int cl_explosions_max; static int explosions_running; static int beams_running; static tentmodels_t beamtypes[] = { {"progs/bolt.mdl", "TE_LIGHTNING1", "TE_LIGHTNING1_END"}, {"progs/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/stsunsf2.mdl", NULL, NULL}, {"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}, }; sfx_t *cl_sfx_wizhit; sfx_t *cl_sfx_knighthit; sfx_t *cl_sfx_tink1; sfx_t *cl_sfx_ric1; sfx_t *cl_sfx_ric2; 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); cvar_t cl_truelightning = CVARF("cl_truelightning", "0", CVAR_SEMICHEAT); cvar_t cl_beam_trace = CVAR("cl_beam_trace", "0"); 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! cvar_t cl_shaftlight = {"gl_shaftlight", "0.8"}; 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; 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 (!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 (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 (&cl_legacystains, "Temporary entity control"); Cvar_Register (&cl_shaftlight, "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 ; iloadstate == 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].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 ; ientity == entity && b->tag == tag) { b->info = btype; return b; } } // find a free beam for (i=0, b=cl_beams; i < beams_running; i++, b++) { if (!b->info) { b->info = btype; return b; } } // if (i == beams_running && i < cl_maxbeams.ival) { 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++; cl_beams[i].info = btype; return &cl_beams[i]; } return NULL; } #define STREAM_ATTACHED 16 #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, -ent, NULL, NULL)) P_ParticleTrailIndex(start, end, P_INVALID, 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|*/1; b->endtime = cl.time + 0.2; b->alpha = 1; 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 |= 2; break; case TEH2_STREAM_LIGHTNING: info = &beamtypes[BT_H2LIGHTNING]; flags |= 2; break; case TEH2_STREAM_ICECHUNKS: info = &beamtypes[BT_H2ICECHUNKS]; flags |= 2; 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... switch(type) { case TEH2_STREAM_SUNSTAFF1: if (info->ef_beam == P_INVALID) { b2 = CL_NewBeam(ent, tag+128, &beamtypes[BT_H2SUNSTAFF1_SUB]); if (b2) { P_DelinkTrailstate(&b2->trailstate); P_DelinkTrailstate(&b2->emitstate); memcpy(b2, b, sizeof(*b2)); b2->trailstate = NULL; b2->emitstate = NULL; b2->alpha = 0.5; b2->rflags = RF_TRANSLUCENT|RF_NOSHADOW; } } //FIXME: we don't add the blob corners+smoke break; } } /* ================= 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_EXPLOSION_SPRITE: type = TE_EXPLOSION; break; case TE_EXPLOSION: type = TEQW_EXPLOSION_NOSPRITE; break; case TE_GUNSHOT: type = TE_GUNSHOT_NQCOMPAT; break; case TE_GUNSHOT_NQCOMPAT: type = TE_GUNSHOT; break; default: break; } } //right, nq vs qw doesn't matter now, supposedly. if (cl_shownet.ival >= 2) { static char *te_names[] = { "spike", "superspike", "qwgunshot", "qwexplosion", "tarexplosion", "lightning1", "lightning2", "wizspike", "knightspike", "lightning3", "lavasplash", "teleport", "blood", "lightningblood", "bullet", "superbullet", //bullets deprecated "railtrail", "beam", "explosion2", "nqexplosion", "nqgunshot", "?", "?", "?", #ifdef HEXEN2 "h2lightsml", "h2chain", "h2sunstf1", "h2sunstf2", "h2light", "h2cb", "h2ic", "h2gaze", "h2famine", "h2partexp" #endif }; if (type < countof(te_names)) Con_Printf(" te_%s\n", te_names[type]); else Con_Printf(" te_%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 TE_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_EXPLOSION_NOSPRITE: //nq-style, no sprite case TE_EXPLOSION: //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; dl->color[0] = 4.0; dl->color[1] = 2.0; dl->color[2] = 0.5; dl->channelfade[0] = 0.784; dl->channelfade[1] = 0.92; dl->channelfade[2] = 0.48; } // sound S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0); // sprite if (type == TE_EXPLOSION && 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 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); // 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*MSG_ReadByte()/255.0f; dl->color[1] = 0.4f*MSG_ReadByte()/255.0f; dl->color[2] = 0.4f*MSG_ReadByte()/255.0f; 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 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 TE_GUNSHOT: // bullet hitting wall case TE_GUNSHOT_NQCOMPAT: if (type == TE_GUNSHOT_NQCOMPAT) 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_BLOOD: // 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 TE_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, 0, NULL, NULL)) P_ParticleTrailIndex(pos, pos2, P_INVALID, 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"), 0, NULL, NULL)) P_ParticleTrailIndex(pos, pos2, P_INVALID, 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; default: Host_EndGame ("CL_ParseTEnt: bad type - %i", type); } } void MSG_ReadPos (vec3_t pos); void MSG_ReadDir (vec3_t dir); 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 ; icount ; 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, 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; trailstate_t **ts; 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 && (unsigned int)entityindex < MAX_EDICTS) ts = &cl.lerpents[entityindex].trailstate; else ts = NULL; if (P_ParticleTrail(start, end, effectindex, entityindex, NULL, ts)) P_ParticleTrail(start, end, rt_blood, entityindex, NULL, ts); } void CL_ParsePointParticles(qboolean compact) { vec3_t org, dir; unsigned int count, effectindex; 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 (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; } 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 = Q2RF_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) pt = pt_q2[type]; else 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 MSG_ReadPos (pos); MSG_ReadPos (pos2); P_ParticleTrail(pos, pos2, pt, 0, NULL, NULL); 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, color, 0, NULL); break; case Q2TE_FLASHLIGHT: //white 400-radius dlight MSG_ReadPos(pos); ent = MSG_ReadShort(); P_ParticleTrail(pos, pos, pt, 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; //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 = Q2RF_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 = Q2RF_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 = Q2RF_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 = Q2RF_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"), 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; } void CSQC_GetEntityOrigin(unsigned int csqcent, float *out); /* ================= CL_UpdateBeams ================= */ void CL_UpdateBeams (void) { 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 & 1) && 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 if (ang[0] < -180) ang[0] += 360; ang[0] += (viewang[0] - ang[0]) * 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 = (int) (atan2(dist[1], dist[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); pitch = (int) (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, 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 & 2) { 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; #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; AngleVectors(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; } } beams_running = lastrunningbeam+1; } /* ================= CL_UpdateExplosions ================= */ void CL_UpdateExplosions (void) { int i; float f; int of; int numframes; int firstframe; explosion_t *ex; entity_t *ent; int lastrunningexplosion = -1; vec3_t pos, norm; static float oldtime; float scale; float frametime = cl.time - oldtime; if (frametime < 0 || frametime > 100) frametime = 0; oldtime = cl.time; 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; 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 if (ex->traileffect != P_INVALID) pe->ParticleTrail(ent->oldorigin, ent->origin, ex->traileffect, 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) { CL_UpdateBeams (); CL_UpdateExplosions (); CL_RunPCustomTEnts (); }