Hello,
A couple of things to note about this post, this is code from my current project which I've tried to clean up and make in to a "tutorial" of sorts, because Motorsep
asked me to release the code in IRC.
I'd also like to say both thank you and sorry to Spike, as his help via IRC with some of the CSQC was excellent, and I did want to make this work for FTE aswell as DarkPlaces, but unfortuantly, I just dont have the time at the moment to look into that. If anyone does get it working for FTE tho, before I get a chance to have a look at making it work in FTE, *please* post in this thread the changes needed.
This is not going to be a decently written tutorial, as such, beginners to regular QuakeC might want to look at some other stuff before reading this.
There's two-sides to implementing this code, as I see it, so I'l post the steps for each, in an attempt to make it as simple as possible to understand.
EDIT:
Spike has just informed me using particleeffectnum on the SSQC is bad, which made the first version of this code kinda bad. It has been update to code that only uses particleeffectnum on the CSQC side.
CSQC Particle Emitters ( DP only at the moment. )
CSQC Particle Emitters ( DP only at the moment. )
Last edited by Qrv on Mon Dec 05, 2011 8:09 pm, edited 3 times in total.
I'm looking for a Mapper, Modeller/Animator and a Sound effect/Music person, to work on an exciting project. PM Me here, or catch me on IRC for further info.
Re: CSQC Particle Emitters ( DP only at the moment. )
SSQC Side:
Firstly, in "defs.qc", at the bottom:
Now, I'm using DarkPlaces release win32 build from 28th, June, 2011, and my SSQC uses the dpextensions.qc from that release.
From the reading of Urre's code on CSQC Rockets, I had to add the following to get this working. I actually added mine to the bottom of dpextensions.qc,
but this is a bad idea. Really, you should make a new file, "dpextensions2.qc" or something, and add it after "dpextensions.qc" in the "progs.src".
"dpextensions2.qc":
Now, the following block of code wants to go in a new file, for the sake of this "tutorial" lets call that file mapfx_particles.qc, save it with your other SSQC files.
It should go underneath "misc.qc" in the "progs.src".
mapfx_particles.qc:
Ok, couple of things you should note here, the "mapfx_particle_emitter" is the Map Object, ie, this is the name for use in your map editors, the keys/flags are explained above the function. As I keep saying, this isnt a good tutoral as it assumes you know some things.
Also, "mapfx_walltorch" is another map object, no properties for it, it just display the wall torch model with a csqc particle emitter thingy above it.
Oh, for the map editors, i use box ( -8 -8 -8 ) to ( 8 8 8 ) for the map entity.
At a first writing, this is it for the SSQC side, I aint tested it, but it should complie if you got it all working.
Now on to the CSQC Side of things, again, I'm assuming you've got a CSQC codebase of some sort, and are able to compile it.
I'm not giving a tutorial on how to get the CSQC codebase up and running, because I'm not entirely sure I've done my correct, or if I'm even using
the most "recent" of whatever. Theres a wiki page on doing a basic CSQC hud somewhere on google, my codebase came from that.
Firstly, in "defs.qc", at the bottom:
Code: Select all
// QRV : Particle Effect Emitters ( mapfx )
.float emitter_active;
.string emitter_fx;
// QRV : CSQC Stuff
float ENT_PARTICLE_EMITTER = 4;
.float enttype;
Now, I'm using DarkPlaces release win32 build from 28th, June, 2011, and my SSQC uses the dpextensions.qc from that release.
From the reading of Urre's code on CSQC Rockets, I had to add the following to get this working. I actually added mine to the bottom of dpextensions.qc,
but this is a bad idea. Really, you should make a new file, "dpextensions2.qc" or something, and add it after "dpextensions.qc" in the "progs.src".
"dpextensions2.qc":
Code: Select all
// Entity Sending
.float(entity viewer) SendEntity;
.float Version;
float MSG_ENTITY = 5;
It should go underneath "misc.qc" in the "progs.src".
mapfx_particles.qc:
Code: Select all
//=============================================================================================
//
// QRV : Map Effects : Particles
//
//
// Handles particle emitters, should also be linked to CSQC cuz its "better"
//=============================================================================================
//.float emitter_active
//.string emitter_fx
// QRV : Map Effects : Particle Emitter : SendToCSQC
float() mapfx_particle_emitter_sendtocsqc =
{
WriteByte( MSG_ENTITY, ENT_PARTICLE_EMITTER );
// Emitter origin
WriteCoord( MSG_ENTITY, self.origin_x );
WriteCoord( MSG_ENTITY, self.origin_y );
WriteCoord( MSG_ENTITY, self.origin_z );
// Emitter velocity ( TODO: This could be avoided, at the cost of an extra byte sent, or an extra ENT_* thingy )
WriteCoord( MSG_ENTITY, self.velocity_x );
WriteCoord( MSG_ENTITY, self.velocity_y );
WriteCoord( MSG_ENTITY, self.velocity_z );
// Emitter Re-Emit time
WriteCoord( MSG_ENTITY, self.wait );
// Emitter Particles to emit per emit burst
WriteByte( MSG_ENTITY, self.delay );
// Emitter active flag
WriteByte( MSG_ENTITY, self.emitter_active );
// Emitter ParticleEffectNum
WriteString( MSG_ENTITY, self.emitter_fx );
// o.O
return TRUE;
};
// QRV : Map Effects : Particle Emitter : Use
void() mapfx_particle_emitter_use =
{
// Toggle on/off for the emitter, this re-sends the whole emitter info,
// which can be avoided, but I need to do some more testing of how ssqc <-> ccsqc works
// to make sure my idea doesnt balls up.
if ( self.emitter_active == 1 )
{
self.emitter_active = 0;
self.nextthink = time + 0.01;
return;
}
// Default to turn on
self.emitter_active = 1;
self.nextthink = time + 0.01;
};
// QRV : Map Effects : Particle Emitter : Think
void() mapfx_particle_emitter_think =
{
// This is used to delay sends to csqc, i cant remember why i did it like this
// but it made sense at the time, can probably be changed/removed
// csqc thingy
self.SendEntity = mapfx_particle_emitter_sendtocsqc;
self.Version = self.Version + 1;
self.nextthink = -1;
};
// QRV : Map Effects : Particle Emitter
//
// .message = EffectInfo.txt effect name
// .origin = Position of emitter
// .velocity = Particle emit velocity
// .wait = Particle emit speed.
// .delay = Amount of particles to emit per emit burst
// .targetname = For turning emitter on/off
//
// spawnflags & 4 = start turned off, wait for trigger to turn on
//
// QRV : Map Effects : Particle Emitter : Spawn
// this isnt used by the map editor object, but is for spawning via code
// This is hardcoded to always be active, but making it dynamically triggerable wouldnt be hard
void( vector t_org, vector t_vel, float t_wait, float t_delay, string particle_fx_name ) mapfx_particle_emitter_spawn =
{
local entity pfx;
// QRV : Clientside particle emitters? o.O
pfx = spawn();
pfx.enttype = ENT_PARTICLE_EMITTER;
pfx.origin = t_org;
pfx.velocity = t_vel;
pfx.emitter_active = 1;
pfx.emitter_fx = particle_fx_name;
pfx.wait = t_wait;
pfx.delay = t_delay;
pfx.light_lev = 1;
pfx.pflags = PFLAGS_FULLDYNAMIC;
pfx.think = mapfx_particle_emitter_think;
pfx.nextthink = time + 0.05 + random() * 0.2; // This staggers the send to csqc some, maybe change it
};
// QRV : Map Effects : Particle Emitter
//
// .message = EffectInfo.txt effect name
// .origin = Position of emitter
// .velocity = Particle emit velocity
// .wait = Particle emit speed.
// .delay = Amount of particles to emit per emit burst
// .targetname = For turning emitter on/off
//
// spawnflags & 4 = start turned off, wait for trigger to turn on
//
// QRV : Map Effects : Particle Emitter
void() mapfx_particle_emitter =
{
// Sanity - We must have the name of the EffectInfo, everything else we can make defaults for
if ( ! self.message )
{
dprint("mapfx_particle_emitter: No 'message' field.\n");
remove(self);
}
// Setup some stuff
self.emitter_active = 1;
self.emitter_fx = self.message;
// Without this, for whatever reason, CSQC wont get the entity sent to it ( from dp anyway )
// Urre had this problem in some code of his, and might know a solution now.
self.light_lev = 1;
self.pflags = PFLAGS_FULLDYNAMIC;
if ( self.targetname )
self.use = mapfx_particle_emitter_use;
self.think = mapfx_particle_emitter_think;
self.nextthink = time + 0.05;
// Do we start turned off?
if ( self.spawnflags & 4 )
{
// More sanity
if ( ! self.targetname )
{
dprint("mapfx_particle_emitter: No 'targetname' field for triggerable emitter.\n");
remove(self);
}
self.emitter_active = 0;
// if we start turned off, we dont need to tell csqc till we turn on
self.nextthink = -1;
}
// Ok, we could check more stuff from the map editor, but meh
};
// This is from another file in my project, but its an example of how i do my "flaming walltorches" ( my maps are q3bsp based also, but this shouldnt matter )
//=========================================
// mapfx_walltorch
//=========================================
void() mapfx_walltorch =
{
// Pre-cache model
precache_model ("progs/flame.mdl");
// Set the model
setmodel (self, "progs/flame.mdl");
// Sound effect
FireAmbient ();
// Now, we make a second entity :O
// this wont be static, as its a particle flame emitter... :/ ( CSQC at some point? )
mapfx_particle_emitter_spawn (
self.origin,
'0 0 0',
1,
1,
"MAPFX_FLAME_WALLTORCH"
);
// Make static on this model
makestatic (self);
};
Also, "mapfx_walltorch" is another map object, no properties for it, it just display the wall torch model with a csqc particle emitter thingy above it.
Oh, for the map editors, i use box ( -8 -8 -8 ) to ( 8 8 8 ) for the map entity.
At a first writing, this is it for the SSQC side, I aint tested it, but it should complie if you got it all working.
Now on to the CSQC Side of things, again, I'm assuming you've got a CSQC codebase of some sort, and are able to compile it.
I'm not giving a tutorial on how to get the CSQC codebase up and running, because I'm not entirely sure I've done my correct, or if I'm even using
the most "recent" of whatever. Theres a wiki page on doing a basic CSQC hud somewhere on google, my codebase came from that.
Last edited by Qrv on Mon Dec 05, 2011 8:06 pm, edited 1 time in total.
I'm looking for a Mapper, Modeller/Animator and a Sound effect/Music person, to work on an exciting project. PM Me here, or catch me on IRC for further info.
Re: CSQC Particle Emitters ( DP only at the moment. )
CSQC Side:
Now, the filename's I mention here could and probably are different to the filenames of what your CSQC codebase is, you should not have much problems finding
your version of the file I refer to, if you've managed to follow whats going on so far thru this "tutorial".
Ok, so, at the bottom of "CSQC_Defs.qc", I added this:
Ok, now in my "CSQC_Required_Functions.qc", my version of "CSQC_Ent_Update" looks like this:
Yours may well look different, so you'l have to work out how to adjust my code to fit with yours, but it should be quite obvious what my code above is doing.
Ok, now add this code to a new file, inside your CSQC folder or whatever, I would call it "csqc_mapfx_particles.qc"
Dont forget to add this file to "csprogs.src". Mnes added above "CSQC_Required_Functions.qc" in my csprogs.src, if you dont put yours above your "CSQC_Ent_Update" containing file, you'l want to declare "void( float isnew ) Mapfx_Particle_Emitter;" above CSQC_Ent_Update. Or something.
"csqc_mapfx_particles.qc":
You should now be able to compile both SSQC and CSQC, but I aint tested this on clean code, but I dont think I forgot anything.
If you have not modified my pasted code at all, you should be able to tell if you got it working in your map by seeing messages saying
"recieved emitter..." every time CSQC gets info turning on or off a particle emitter, or you enter a PVS with new emitters in, technically.
Spike raised a good point to note, you dont want to go mad and have hundreds of emitters in one PVS area, because it wouldnt really help the network savings of using CSQC, as you'l be hit with a billion of entity updates from the server. These emitters are used sparing in my project, so it's not something I worry about, but something others probably should know about tho.
The code can be improved in a few ways I'm aware of, but I dont consider it bad code to use from a "tutorial point" of view either.
Not that this screenshot proves anything, but this is my CSQC Particle Emitters actually in-action, boring as it is:
Now, the filename's I mention here could and probably are different to the filenames of what your CSQC codebase is, you should not have much problems finding
your version of the file I refer to, if you've managed to follow whats going on so far thru this "tutorial".
Ok, so, at the bottom of "CSQC_Defs.qc", I added this:
Code: Select all
float ENT_PARTICLE_EMITTER = 4;
.float enttype;
.float delay;
.float wait;
.float emitter_active;
.float emitter_fx;
Code: Select all
void CSQC_Ent_Update (float isnew)
{
self.enttype = readbyte();
if ( self.enttype == ENT_PARTICLE_EMITTER )
{
print("recieved emitter...\n");
Mapfx_Particle_Emitter( isnew );
}
}
Ok, now add this code to a new file, inside your CSQC folder or whatever, I would call it "csqc_mapfx_particles.qc"
Dont forget to add this file to "csprogs.src". Mnes added above "CSQC_Required_Functions.qc" in my csprogs.src, if you dont put yours above your "CSQC_Ent_Update" containing file, you'l want to declare "void( float isnew ) Mapfx_Particle_Emitter;" above CSQC_Ent_Update. Or something.
"csqc_mapfx_particles.qc":
Code: Select all
void() Mapfx_Particle_Emitter_Think =
{
if ( self.emitter_active == 1 )
{
pointparticles( self.emitter_fx, self.origin, self.velocity, self.delay );
self.nextthink = time + self.wait;
} else {
self.nextthink = -1;
}
};
void( float isnew ) Mapfx_Particle_Emitter =
{
local string temp_fxname;
// if ( isnew )
// {
// Emitter Origin
self.origin_x = readcoord();
self.origin_y = readcoord();
self.origin_z = readcoord();
// Emitter Velocity
self.velocity_x = readcoord();
self.velocity_y = readcoord();
self.velocity_z = readcoord();
// Emitter re-emit time
self.wait = readcoord();
// Emitter particles to emit per burst
self.delay = readbyte();
// Emitter active flag
self.emitter_active = readbyte();
// Emitter ParticleEffectNum
temp_fxname = strzone(readstring());
self.emitter_fx = particleeffectnum( temp_fxname );
if ( self.emitter_fx == -1 )
{
print("no effectinfo for ", temp_fxname, "\n");
strunzone(temp_fxname);
remove(self);
return;
}
strunzone(temp_fxname);
/* QRV : debugging info
print( "Mapfx_Particle_Emitter:\n",
"Origin: ", ftos(self.origin_x), ", ", ftos(self.origin_y), ", ", ftos(self.origin_z), "\n");
print( "Effect Num: ", ftos(self.emitter_fx), ", Active: ", ftos(self.emitter_active), "\n");
print( "Interval: ", ftos(self.wait), ", Count: ", ftos(self.delay), "\n" );
*/
self.think = Mapfx_Particle_Emitter_Think;
if ( self.emitter_active == 1 )
{
// To try to avoid the "restarting particles" visual, we'l draw 2 calls to the particle effect
// probably not wanted by other people tho
pointparticles( self.emitter_fx, self.origin, self.velocity, self.delay );
pointparticles( self.emitter_fx, self.origin, self.velocity, self.delay );
self.nextthink = time + self.wait;
} else {
self.nextthink = -1;
}
// }
};
If you have not modified my pasted code at all, you should be able to tell if you got it working in your map by seeing messages saying
"recieved emitter..." every time CSQC gets info turning on or off a particle emitter, or you enter a PVS with new emitters in, technically.
Spike raised a good point to note, you dont want to go mad and have hundreds of emitters in one PVS area, because it wouldnt really help the network savings of using CSQC, as you'l be hit with a billion of entity updates from the server. These emitters are used sparing in my project, so it's not something I worry about, but something others probably should know about tho.
The code can be improved in a few ways I'm aware of, but I dont consider it bad code to use from a "tutorial point" of view either.
Not that this screenshot proves anything, but this is my CSQC Particle Emitters actually in-action, boring as it is:
I'm looking for a Mapper, Modeller/Animator and a Sound effect/Music person, to work on an exciting project. PM Me here, or catch me on IRC for further info.