CSQC Particle Emitters ( DP only at the moment. )

Discuss CSQC related programming.
Post Reply
Qrv
Posts: 45
Joined: Thu Oct 20, 2011 7:43 am
Location: Stuck in a Slipgate.

CSQC Particle Emitters ( DP only at the moment. )

Post by Qrv »

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.
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.
Qrv
Posts: 45
Joined: Thu Oct 20, 2011 7:43 am
Location: Stuck in a Slipgate.

Re: CSQC Particle Emitters ( DP only at the moment. )

Post by Qrv »

SSQC Side:

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;
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:

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);
};
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.
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.
Qrv
Posts: 45
Joined: Thu Oct 20, 2011 7:43 am
Location: Stuck in a Slipgate.

Re: CSQC Particle Emitters ( DP only at the moment. )

Post by Qrv »

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:

Code: Select all

float	ENT_PARTICLE_EMITTER	=	4;

.float	enttype;
.float  delay;
.float	wait;

.float emitter_active;
.float emitter_fx;
Ok, now in my "CSQC_Required_Functions.qc", my version of "CSQC_Ent_Update" looks like this:

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 );
	}
}
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":

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;
	}
//	}
};
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:
Image
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.
Post Reply