News | Forum | People | FAQ | Links | Search | Register | Log in
Coding Help
This is a counterpart to the "Mapping Help" thread. If you need help with QuakeC coding, or questions about how to do some engine modification, this is the place for you! We've got a few coders here on the forum and hopefully someone knows the answer.
First | Previous | Next | Last
Profile 
There's a built in way to check how fast qc runs on an engine, in case you're optimising things. The command is "profile", and it lists the top 10 functions in terms of commands executed since the last profile command or server start. As an example on unpatched quake:
e1m6 at 72fps
425616 PlayerPreThink
270814 WaterMove
241542 PlayerPostThink
202689 ImpulseCommands
177480 CheckRules
165648 CheckPowerups
125843 ai_stand
94928 StartFrame
76144 door_touch

Things worth noticing:

*ImpulseCommands is certainly up there. Almost certainly the most important optimisation would be to skip everything if self.impulse is 0. I'd agree with doing that check before calling the function ImpulseCommands(calling functions costs a few operations).
Further optimisations would be the else if Lardarse suggests, and storing self.impulse in a local float at the start of the ImpulseCommands function - to avoid looking it up each time, which is more expensive if you have to go through a layer of self.something first. But optimising the very rare cases where there is a change of weapon isn't gonna yield anything like the benefit of skipping the whole thing in 99.9% of frames.

* CheckRules places highly, even though that function is entirely pointless in single player games. I think the calls to cvar are pretty expensive here.

* Results from this kind of assessment will be different at different framerates. The playerprethink type functions occur every frame, but since monsters think in 0.1 second slices, that's only 1 in 7 frames. Playing at 15fps, I'd imagine ai_stand could top the charts. 
Boy 
I've learned that if you create a new monster with a new model, the $frames still have to be named in the progs.dat urnewmonster.qc file in the same order they're in the .mdl.

I assume the compiler converts frame names to integer offsets and the names of the frames themselves are irrelevant? 
 
I assume the compiler converts frame names to integer offsets and the names of the frames themselves are irrelevant?
yup. 
And 
Is it relevant in any way how many frames there are per line? I've had no problems, but then again I haven't experimented much ie. tried to break it.

Is there anything stopping me from having, say, 20 frames on the same line in the .qc? 
I Went Up To 18 With Frikqcc 
and it didn't seem to mind 
Schnurps 
whenever i coded new monsters, i just looked up the frame number in QME and just typed the number, rather than having to list all the $frame bollox at the top of the file 
Well 
isn't that arguably more confusing? if I reexport the model with new anims in it it's kind of nice to just be able to insert them in the list at the top than have to replace all of the numbers in all of the frame functions ... 
I Grudgingly Agree 
If you're thinking about an end user for the code that's a nightmare. Also depends on having the anims in one seqeunce rather than seperated.

It's interesting hacking up all this stuff. 
Profile Is Going In The Reference Book 
and storing self.impulse in a local float at the start of the ImpulseCommands function
Probably not worth it. Except maybe for RuneQuake, or anything else that has impulse overloading...

The frame macros can be called anything you like. It's usual, though, to call them something similar to the model, as it makes using them easier. And as how how many you can fit on one line, I think your most sensible limit is the right edge of your text editor... 
Temporaries 
Yeah, I agree it's not useful in ImpulseCommands once you have the other improvements. But once you sort out the needlessly expensive functions like that, you start to notice that functions like visible() and range() begin to show up in profile. And in those, you do get (theoretical) performance improvements by using locals. This is my current optimised range function

float(entity targ) range =
{
local vector spot1;
local float r;

spot1 = self.origin + self.view_ofs - targ.origin - targ.view_ofs;
//PREACH: this only uses two temps
r = spot1 * spot1;
//
//PREACH: kept in a local since we use it twice
//PREACH: it would still be stored in a temp local float
//PREACH: but would re-evaluate each time in case spot1 had changed. We know it can't, so we save the result.
if (r < 500 * 500)
{
if (r < 120 * 120)
return RANGE_MELEE;
else
return RANGE_NEAR;
//PREACH: nesting the ifs like this takes at most 2 comparisons, down from 4 in the original
}
else
{
if (r < 1000 * 1000)
return RANGE_MID;
return RANGE_FAR;
}
}

Skips the expensive calls to vlen, and nests the ifs for a slightly shorter path to RANGE_MID or RANGE_FAR. Note that in frikqcc and fteqcc with the flatten constants optimisation on, 120 * 120 and the others are evaluated at compile time. If you're not using that optimisation, then you should work out those values ahead of time. I leave them like that to make clear where the magic numbers come from.

You can get a little more juice out of visible if you use

float (entity targ) visible =
{
traceline (self.origin + self.view_ofs, targ.origin + targ.view_ofs, TRUE, self); // see through other monsters
if (trace_inwater)
{
if (trace_inopen)
return FALSE; // sight line crossed contents
}

if (trace_fraction == 1)
return TRUE;
return FALSE;
};

In FTE, putting the arguments straight into the traceline function call is actually faster than not doing so. I also put the trace_inwater check first, as I figure that's more likely to fail most of the time, so you don't need to bother checking trace_inopen(which under the same assumptions is usually likely to succeed). Doing && in an if statement always evaluates both statements in qc, so nesting ifs can make faster executing code where it matters. 
So Why Not Just 
make the profile command show more than 10?

And of course, format things so they make sense to whoever is writing the code; for me this means not putting things on multiple lines for the hell of it :-) 
Also Visible 
Isn't this the function that you suggested changing in another thread (can't find it) to make monsters see you while in water? 
Suggested Is A Strong Word :-P 
I wouldn't say the water vision change was something that should always be changed, someone was asking how it could be done, so I posted it. Really it's a circumstantial choice, if it causes problems in a specific map then visible is the place to change things, but in general keeping water opaque is a good idea for fish if nobody else. 
Ok, Then... "mentioned" 
Selective based on monster type, though. 
Gnurps 
isn't that arguably more confusing?

hehe, well I admitted it for the comedy value really; I'm aware that it is an atrocious way of coding monsters :) 
NULL 
Did I ask this before? I don't remember, honestly.

Anyway, is there a concept of a NULL value in QuakeC? Like say I want to define a local entity variable and set it to NULL before doing some other processing and at the end do a check like:

if( myEntity == NULL) or if( !myEntity )

Is that possible? I think I'm missing something obvious because I can't get the compiler to accept null, Null, NULL or 0 (zero).

Help? 
Pointer To World 
Most things tend to use world as the "NULL pointer". find() and variants usually return world if nothing is found, or end a linked list with world. I believe traceline() sets the hit entity to world if nothing is hit (the general way to detect no hit is if(trace_fraction == 1) although sometimes testing for hitting world is acceptable).

Usually, world is entity 0 (maybe eprint() might be able to tell you), although the compiler probably doesn't accept the implicit cast from float to entity.

Other NULL values are 0, "", and '0 0 0'
Yeah 
Just to add that

if( !myEntity )

is valid qc and equivalent to

if(myEntity == world)

If world can be a valid return type you need to deal with that as a seperate case somehow. 
 
Ahh alright. Thanks! 
Removing Entities And It's Effects On Other Things 
so, say i have ent1.owner = ent2
and ent2 is removed. what happens to my pointer on ent1.owner? is it set to world or is it now corrupted junk?

can i do a check if (!ent1.owner) now and it'll pass? 
No 
The entity field continues to point to the same "slot" that the original entity occupied. Worse, after two seconds that entity slot can be reallocated when a new spawn() request is received, so the field now points to a valid entity, but an entirely unrelated one.

It's also worth noting that most of the fields on the removed entity will still be readable, and contain the same values they did before the entity was removed. The engine resets a few fields on removal, like solid, model and origin, but doesn't zero all of them until it's reused by the spawn() command. You can observe a side effect in fitzquake if you turn r_showbboxes on; a removed entity creates a bbox of that entity's size at the origin.

You could exploit the above fact to write code which can tell if an entity is removed or not:
Before removing an entity, set a float .isremoved on that entity to TRUE. The value can still be read, and will be reset when the entity is respawned as something else. You have to pinkie-swear never to modify that value outside the remove() function for it to be reliable.

I wouldn't recommend actually doing that though, because it's kinda relying on "undefined" behaviour in the engine - that removed entities can still be read. Engines which had more advanced entity management might not enforce that.

It's probably better to have a destructor function for the entity which is being removed which knows which entities will refer to it and sets them to WORLD. This would be practical if it's a master-slave kind of thing, not so much if you're worried about a monster getting removed and other monsters suddenly having an invalid .enemy... 
 
"You can observe a side effect in fitzquake if you turn r_showbboxes on; a removed entity creates a bbox of that entity's size at the origin. "

Hahaha, so THAT'S what that is. I wondered what that box at the origin of the world was. Thanks! I thought my code was doing something wacky... 
Jesus 
9_9 
Mind Melting Code 
Ok, I dug out the monster I wrote a few months back, here's some code which actually tracks all of your removed entities in a linked list. The file is pretty thoroughly commented, with a big block of text at the start describing how you might use it, and how to integrate it into a standard qc source.
http://www.btinternet.com/~chapterhonour/chaintrack.qc

The main application is a new field on entities you can read called .reused. Positive/zero values of .reused mean the entity is currently spawned, negative values mean it's currently removed. The absolute value of .reused gives you the number of times the entity has been removed previously. This gives you an index you can compare to see if a stored entity differs in number of removals from the value it used to have - i.e. it has been removed and restored.

I've tested it with fitz, bjp and dp, and it seems to function correctly in all 3. If anyone wants to know how it works, I can post that up too... 
Custents 
I was trying to add some doombirds to my map, and couldn't find the right movement for the thing. If I use a func_train it worked, only the sight of a plane flying backwards is a little queer.

So I took the custents and saw the map with the falling wall. It has a four way movement train that uses func_rotate. I integrated the doombird in it and it worked out fine again.

One thing I can't get is
why can I jump on the func_train in custents,
and why do I drop through it in my own version? 
First | Previous | Next | Last
You must be logged in to post in this thread.
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.