quakec/source/client/particles.qc
2024-01-13 21:47:02 -05:00

159 lines
No EOL
5.9 KiB
C++

/*
client/particles.qc
CSQC-Driven Particle Runner.
Copyright (C) 2021-2024 NZ:P Team
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
// Stored outside of struct for stack efficency.
float particle_optional_field;
float particle_optional_entity;
vector particle_position;
//
// Particles_MuzzleflashCallback()
// Executes Muzzleflash Particle effect.
// Optional: Which weapon was fired (left/right, for akimbo).
//
void() Particles_MuzzleflashCallback =
{
// Do not display Muzzleflashes if we're using a Sniper Scope or not drawing the viewmodel.
if (getstatf(STAT_WEAPONZOOM) == 2 || !cvar("r_drawviewmodel"))
return;
// Organized storage of optional_field and optional_entity.
float weapon_side = particle_optional_field;
float hit_entity = particle_optional_entity;
// Obtain our input view angles.
getinputstate(servercommandframe);
makevectors(input_angles);
vector muzzleflash_offset;
vector muzzleflash_position = getviewprop(VF_ORIGIN);
// If firing the left side of a dual-wield weapon, use the left side muzzleflash offset.
if (weapon_side == 0 && IsDualWeapon(getstatf(STAT_ACTIVEWEAPON)))
muzzleflash_offset = WepDef_GetLeftFlashOffset(getstatf(STAT_ACTIVEWEAPON))/1000;
// Otherwise, use the standard offset.
else
muzzleflash_offset = GetWeaponFlash_Offset(getstatf(STAT_ACTIVEWEAPON))/1000;
// Move to match ADS position if Zoomed in.
if(getstatf(STAT_WEAPONZOOM) == 1) {
muzzleflash_offset += GetWeaponADSOfs(getstatf(STAT_ACTIVEWEAPON))/1000;
}
muzzleflash_position += v_forward * muzzleflash_offset_z;
muzzleflash_position += v_right * muzzleflash_offset_x;
muzzleflash_position += v_up * muzzleflash_offset_y;
float muzzleflash_type = rint(random() * 2); // Choose one of three Muzzleflash variances.
// Display Muzzleflash Particle and Dynamic Light
if (IsPapWeapon(getstatf(STAT_ACTIVEWEAPON))) {
pointparticles(particleeffectnum(strcat("muzzle.muzzle_pap_part", ftos(muzzleflash_type))), muzzleflash_position, '0 0 0', 1);
// Pack-A-Punched Weapons can display either a Blue or Red light
if (random() > 0.5)
dynamiclight_add(muzzleflash_position, 256, '0.7 0 0');
else
dynamiclight_add(muzzleflash_position, 256, '0 0 0.7');
} else {
pointparticles(particleeffectnum(strcat("muzzle.muzzle_part", ftos(muzzleflash_type))), muzzleflash_position, '0 0 0', 1);
dynamiclight_add(muzzleflash_position, 256, '1.2 0.7 0.2');
}
// Grenade-Lunching weapons should not show decals.
if (GetFiretype(getstatf(STAT_ACTIVEWEAPON)) == FIRETYPE_GRENADE)
return;
if(hit_entity == 0) {
pointparticles(particleeffectnum("weapons.impact"), particle_position, '0 0 0', 1);
pointparticles(particleeffectnum("weapons.impact_decal"), particle_position, '0 0 0', 1);
}
};
// This struct must be ordered linearly for fast array lookups. Do NOT skip indexes.
var struct
{
float particle_type; // ID of Particle.
string particle_string; // String that refers to the Particle name/gamedir path. May be blank if callbacks utilized.
void() particle_func; // Callback that also gets executed on Particle_RunParticle, typically __NULL__. optional_field used here.
} csqc_particles[] =
{
{CSQC_PART_MUZZLEFLASH, "", Particles_MuzzleflashCallback}, // View Entity Muzzleflashes.
{CSQC_PART_EXPLOSION, "weapons.explode", __NULL__}, // Explosion effect.
{CSQC_PART_BLOODIMPACT, "blood.blood_particle", __NULL__}, // Blood Impact effect.
{CSQC_PART_ZOMBIEGIB, "blood.gibs", __NULL__}, // Zombie Gib effect.
{CSQC_PART_FIRE, "flames.flame_particle", __NULL__} // Fire/flame effect.
};
//
// Particles_RunParticle(particle_type, position, optional_field, optional_entity)
// Runs specific particle given it's type, location,
// and optional fields to pass to an optional callback.
//
void(float particle_type, vector position, float optional_field, float optional_entity) Particles_RunParticle =
{
// If the requested particle index does not have a callback function,
// run it outright.
if (csqc_particles[particle_type].particle_func == __NULL__)
pointparticles(particleeffectnum(csqc_particles[particle_type].particle_string), position, '0 0 0', 1);
// It has a callback -- so execute it instead.
else {
particle_optional_field = optional_field;
particle_optional_entity = optional_entity;
particle_position = position;
csqc_particles[particle_type].particle_func();
particle_optional_field = particle_optional_entity = 0;
particle_position = '0 0 0';
}
};
//
// Particles_Init()
// Particles never draw on their first run, so "allocate"
// them here. Particles only called via callbacks need
// invidiually referenced.
//
void() Particles_Init =
{
int i;
//
// Callback Particles
//
// Muzzleflashes.
for(i = 0; i < 3; i++) {
pointparticles(particleeffectnum(strcat("muzzle.muzzle_pap_part", itos(i))), '0 0 0', '0 0 0', 0);
pointparticles(particleeffectnum(strcat("muzzle.muzzle_part", itos(i))), '0 0 0', '0 0 0', 0);
}
//
// Normal References
//
for(i = 0; i < csqc_particles.length; i++) {
pointparticles(particleeffectnum(csqc_particles[i].particle_string), '0 0 0', '0 0 0', 0);
}
};