FTE/CLIENT: Streamline CSQC Particle System

This commit is contained in:
MotoLegacy 2024-01-13 12:53:48 -05:00
parent 2fc682810d
commit a8f1730d58
7 changed files with 248 additions and 213 deletions

View file

@ -11,4 +11,5 @@
../source/client/chat.qc ../source/client/chat.qc
../source/client/user_input.qc ../source/client/user_input.qc
../source/client/view_model.qc ../source/client/view_model.qc
../source/client/particles.qc
../source/client/main.qc ../source/client/main.qc

View file

@ -153,28 +153,10 @@ noref void(float apiver, string enginename, float enginever) CSQC_Init =
} }
}; };
//
// Init_Particles()
// Spawns a dummy particle for each effect
// so it loads. (FIXME - is there a better
// way?)
//
void() Init_Particles =
{
for(int 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);
}
pointparticles(particleeffectnum("weapons.impact"), '0 0 0', '0 0 0', 0);
pointparticles(particleeffectnum("weapons.impact_decal"), '0 0 0', '0 0 0', 0);
pointparticles(particleeffectnum("weapons.explode"), '0 0 0', '0 0 0', 0);
pointparticles(particleeffectnum("blood.blood_particle"), '0 0 0', '0 0 0', 0);
}
noref void() CSQC_WorldLoaded = noref void() CSQC_WorldLoaded =
{ {
Achievement_Init(); Achievement_Init();
Init_Particles(); Particles_Init();
nameprint_time = time + 12; nameprint_time = time + 12;
huddir = "gfx/hud/"; huddir = "gfx/hud/";
@ -877,80 +859,18 @@ noref void() CSQC_Input_Frame =
noref void() CSQC_Parse_Event = noref void() CSQC_Parse_Event =
{ {
local float first = readbyte(); float event_type = readbyte();
switch (first) { switch (event_type) {
case EVENT_PISTOLFIRE: case CSQC_EVENT_PARTICLE:
local float entnum, traceent, side; float particle_type = readbyte();
local vector pos, norm; float part_pos_x = readcoord();
entnum = readentitynum(); float part_pos_y = readcoord();
side = readfloat(); float part_pos_z = readcoord();
pos_x = readcoord(); float part_optional = readbyte();
pos_y = readcoord(); float part_entity = readentitynum();
pos_z = readcoord(); vector particle_pos = [part_pos_x, part_pos_y, part_pos_z];
norm_x = readcoord(); Particles_RunParticle(particle_type, particle_pos, part_optional, part_entity);
norm_y = readcoord();
norm_z = readcoord();
traceent = readentitynum();
// Muzzleflash Logic
if (entnum == player_localentnum && getstatf(STAT_WEAPONZOOM) != 2)
{
// originally was view_angles, but thats only what is reported
// to the server, weapon kick is NOT reported, so offset continued
// to grow.
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 (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_PSP(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;
if (cvar("nzp_particles") && cvar("r_drawviewmodel")) {
float index = rint(random() * 2);
// Display Muzzleflash Particle and Dynamic Light
if (IsPapWeapon(getstatf(STAT_ACTIVEWEAPON))) {
pointparticles(particleeffectnum(strcat("muzzle.muzzle_pap_part", ftos(index))), muzzleflash_position, norm*24, 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(index))), muzzleflash_position, norm*24, 1);
dynamiclight_add(muzzleflash_position, 256, '1.2 0.7 0.2');
}
}
}
if (GetFiretype(getstatf(STAT_ACTIVEWEAPON)) == FIRETYPE_GRENADE)
return;
if(traceent == 0)
{
if (cvar("nzp_particles"))
pointparticles(particleeffectnum("weapons.impact"), pos, norm*24, 1);
if (cvar("nzp_decals"))
pointparticles(particleeffectnum("weapons.impact_decal"), pos, '0 0 0', 1);
}
break; break;
case EVENT_WEAPONRECOIL: case EVENT_WEAPONRECOIL:
local vector rec; local vector rec;
@ -960,48 +880,6 @@ noref void() CSQC_Parse_Event =
gun_kick += rec; gun_kick += rec;
break; break;
case EVENT_EXPLOSION:
local vector org;
org_x = readcoord();
org_y = readcoord();
org_z = readcoord();
if (cvar("nzp_decals"))
pointparticles(particleeffectnum("weapons.explode"), org, '0 0 0', 1);
break;
case EVENT_BLOOD:
vector loc;
loc_x = readcoord();
loc_y = readcoord();
loc_z = readcoord();
if (cvar("nzp_particles"))
pointparticles(particleeffectnum("blood.blood_particle"), loc, '0 0 0', 1);
break;
case EVENT_FLAME:
vector floc;
floc_x = readcoord();
floc_y = readcoord();
floc_z = readcoord();
if (cvar("nzp_particles")) {
pointparticles(particleeffectnum("flames.flame_particle"), floc, '0 0 0', 1);
}
break;
case EVENT_LIMBGIB:
vector gloc;
gloc_x = readcoord();
gloc_y = readcoord();
gloc_z = readcoord();
if (cvar("nzp_particles")) {
pointparticles(particleeffectnum("blood.blood_particle"), gloc, '0 0 0', 1);
pointparticles(particleeffectnum("blood.gibs"), gloc, '0 0 0', 1);
}
break;
case EVENT_CHATMESSAGE: case EVENT_CHATMESSAGE:
int sender = readbyte(); int sender = readbyte();
int player_id = readbyte(); int player_id = readbyte();

159
source/client/particles.qc Normal file
View file

@ -0,0 +1,159 @@
/*
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_PSP(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);
}
};

View file

@ -25,6 +25,34 @@
*/ */
#ifdef FTE
//
// FTE_RunParticleEffect(target, particle_type, position, optional_field, optional_entity)
// Fires a CSQC_EVENT_PARTICLE to clients specified (or
// world for global).
//
void(entity target, float particle_type, vector position, float optional_field, entity optional_entity) FTE_RunParticleEffect =
{
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, CSQC_EVENT_PARTICLE);
WriteByte(MSG_MULTICAST, particle_type);
WriteCoord(MSG_MULTICAST, position_x);
WriteCoord(MSG_MULTICAST, position_y);
WriteCoord(MSG_MULTICAST, position_z);
WriteByte(MSG_MULTICAST, optional_field);
WriteEntity(MSG_MULTICAST, optional_entity);
if (target != world) {
msg_entity = target;
multicast(position, MULTICAST_ONE);
} else {
multicast(position, MULTICAST_ALL);
}
};
#endif // FTE
void() NotifyGameEnd = void() NotifyGameEnd =
{ {
#ifdef FTE #ifdef FTE
@ -104,12 +132,7 @@ void(vector org) CallExplosion = {
#else #else
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(world, CSQC_PART_EXPLOSION, org, 0, world);
WriteByte(MSG_MULTICAST, EVENT_EXPLOSION);
WriteCoord(MSG_MULTICAST, org_x);
WriteCoord(MSG_MULTICAST, org_y);
WriteCoord(MSG_MULTICAST, org_z);
multicast('0 0 0', MULTICAST_ALL);
#endif // FTE #endif // FTE
} }
@ -652,14 +675,7 @@ void(vector org) Effect_Fire =
#else #else
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(world, CSQC_PART_FIRE, org, 0, world);
WriteByte(MSG_MULTICAST, EVENT_FLAME);
WriteCoord(MSG_MULTICAST, org_x);
WriteCoord(MSG_MULTICAST, org_y);
WriteCoord(MSG_MULTICAST, org_z);
multicast('0 0 0', MULTICAST_ALL);
//te_flamejet(self.origin, v_up*8, 10);
#endif // FTE #endif // FTE

View file

@ -54,18 +54,7 @@ void(float side) W_FireGrenade =
#ifdef FTE #ifdef FTE
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(self, CSQC_PART_MUZZLEFLASH, '0 0 0', side, world);
WriteByte(MSG_MULTICAST, EVENT_PISTOLFIRE);
WriteEntity(MSG_MULTICAST, self);
WriteFloat(MSG_MULTICAST, side);
WriteCoord(MSG_MULTICAST, 0);
WriteCoord(MSG_MULTICAST, 0);
WriteCoord(MSG_MULTICAST, 0);
WriteCoord(MSG_MULTICAST, 0);
WriteCoord(MSG_MULTICAST, 0);
WriteCoord(MSG_MULTICAST, 0);
WriteEntity(MSG_MULTICAST, world);
multicast(self.origin, MULTICAST_PHS);
#endif // FTE #endif // FTE
} }

View file

@ -560,13 +560,7 @@ void(vector org, vector vel, float damage) SpawnBlood =
#ifdef FTE #ifdef FTE
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(world, CSQC_PART_BLOODIMPACT, org, 0, world);
WriteByte(MSG_MULTICAST, EVENT_BLOOD);
WriteCoord(MSG_MULTICAST, org_x);
WriteCoord(MSG_MULTICAST, org_y);
WriteCoord(MSG_MULTICAST, org_z);
msg_entity = self;
multicast('0 0 0', MULTICAST_ONE);
#else #else
@ -586,12 +580,7 @@ void(vector where) spawn_gibs = {
#else #else
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(world, CSQC_PART_ZOMBIEGIB, where, 0, world);
WriteByte(MSG_MULTICAST, EVENT_LIMBGIB);
WriteCoord(MSG_MULTICAST, where_x);
WriteCoord(MSG_MULTICAST, where_y);
WriteCoord(MSG_MULTICAST, where_z);
multicast('0 0 0', MULTICAST_ALL);
#endif // FTE #endif // FTE
@ -824,18 +813,7 @@ void(float damage, vector dir, vector org, vector plane, entity hit_ent, float s
#ifdef FTE #ifdef FTE
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); FTE_RunParticleEffect(self, CSQC_PART_MUZZLEFLASH, org, side, hit_ent);
WriteByte(MSG_MULTICAST, EVENT_PISTOLFIRE);
WriteEntity(MSG_MULTICAST, self);
WriteFloat(MSG_MULTICAST, side);
WriteCoord(MSG_MULTICAST, org_x);
WriteCoord(MSG_MULTICAST, org_y);
WriteCoord(MSG_MULTICAST, org_z);
WriteCoord(MSG_MULTICAST, plane_x);
WriteCoord(MSG_MULTICAST, plane_y);
WriteCoord(MSG_MULTICAST, plane_z);
WriteEntity(MSG_MULTICAST, hit_ent);
multicast(trace_endpos, MULTICAST_PHS);
#endif // FTE #endif // FTE

View file

@ -28,37 +28,51 @@
#define true 1 #define true 1
#define false 0 #define false 0
#define EVENT_PISTOLFIRE 10 //
// CSQC Particle Types
//
#define CSQC_PART_MUZZLEFLASH 0 // View Entity Muzzleflashes.
#define CSQC_PART_EXPLOSION 1 // Explosion effect.
#define CSQC_PART_BLOODIMPACT 2 // Blood Impact effect.
#define CSQC_PART_ZOMBIEGIB 3 // Zombie Gib effect.
#define CSQC_PART_FIRE 4 // Fire/flame effect.
//
// CSQC Event Types
//
#define CSQC_EVENT_PARTICLE 10 // <byte> particle_type
// <coord> pos_x
// <coord> pos_y
// <coord> pos_z
// <byte> optional_field
// <entity> optional_entity
#define EVENT_USEPRINT 11 #define EVENT_USEPRINT 11
#define EVENT_NEWROUND 12 #define EVENT_NEWROUND 12
#define EVENT_SETROUND 13 #define EVENT_SETROUND 13
#define EVENT_PERK 22 #define EVENT_PERK 14
#define EVENT_UPDATE 23 #define EVENT_UPDATE 15
#define EVENT_BROADCAST 24 #define EVENT_BROADCAST 16
#define EVENT_POINTUPDATE 25 #define EVENT_POINTUPDATE 17
#define EVENT_BLACKOUT 26 #define EVENT_BLACKOUT 18
#define EVENT_SCROLLTEXT 28 #define EVENT_SCROLLTEXT 19
#define EVENT_WORLDDATA 29 #define EVENT_WORLDDATA 20
#define EVENT_ACHIEVEMENT 30 #define EVENT_ACHIEVEMENT 21
#define EVENT_PLAYERUPDATE 31 #define EVENT_PLAYERUPDATE 22
#define EVENT_WEAPONUPDATE 32 #define EVENT_WEAPONUPDATE 23
#define EVENT_HUDUPDATE 33 #define EVENT_HUDUPDATE 24
#define EVENT_EXPLOSION 34 #define EVENT_ACHIEVEMENTPROGRESS 25
#define EVENT_BLOOD 35 #define EVENT_REVIVEON 26
#define EVENT_ACHIEVEMENTPROGRESS 36 #define EVENT_REVIVEOFF 27
#define EVENT_REVIVEON 37 #define EVENT_REVIVECHANGE 28
#define EVENT_REVIVEOFF 38 #define EVENT_WEAPONRECOIL 29
#define EVENT_REVIVECHANGE 39 #define EVENT_SONGPLAY 30
#define EVENT_WEAPONRECOIL 40 #define EVENT_GRENADEPULSE 31
#define EVENT_SONGPLAY 41 #define EVENT_BETTYPROMPT 32
#define EVENT_GRENADEPULSE 42 #define EVENT_CHATMESSAGE 33
#define EVENT_BETTYPROMPT 43 #define EVENT_DOUBLETAPUPDATE 34
#define EVENT_LIMBGIB 44 #define EVENT_ENDGAME 35
#define EVENT_CHATMESSAGE 45 #define EVENT_MAPTYPE 36
#define EVENT_DOUBLETAPUPDATE 46
#define EVENT_FLAME 47
#define EVENT_ENDGAME 48
#define EVENT_MAPTYPE 49
// Define our FTE platform // Define our FTE platform
#ifndef STANDARD #ifndef STANDARD