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
Yeah 
I want blinking lights for the enforcers, that was a good suggestion, whoever made it.

It looks from the models that the head gib was originally going to come from inside the main model, but it didn't work out.

Simple as export c+p import to put the pain head onto the normal guy for a dead skin. 
 
I was replying to Willem about the additional face parts etc. on id1 skins. Those are pretty weird. 
So Was I 
It seems the extra faces are there because the head and model were originally going to be combined, until they realised that hiding the entire body inside a copied head would be more wasteful. 
Just Think.... 
how awesome it would look if they did hide the body inside the head gib, now that we have engines that do model interpolation? 
Vampires! 
Back in the day before I worked on mods that people would play, I made a quake mod where you fought vampires - aping Buffy and that kind of thing. One of the driving things behind it was to make the death sequences look cool (by quake standards), so when you staked a vampire through the heart they'd look agonised for a beat, then explode into a puff of dust.

Coming back towards relevance, when you killed them with fire the flames burnt upwards from their feet while their arms flailed. In order to make their legs disappear, the polygons were just folded up into the upper legs, then the torso, with the flame graphic concealing the interpolation which arose. I imagine the final effect is not too dissimilar to what metl's thinking of.

(ps: the head gibs are probably separate for the more mundane reason of giving them a blood trail, which is a per model effect...) 
Railgun In Quake 
is it possible?
i'd appreciate any help 
 
Sure, there were some mods with railguns I think. 
Ahem 
And I just remembered I once successfully completed this tutorial: http://www.inside3d.com/showtutorial.php?id=167 
 
Spy Parboil once change the qwprogs.dat to railmod i think... since u are russian is easy to ask in is forum :)

http://parboil.quakeworld.ru 
 
zomggg are you the spy from the old Parboil board?

hehe 
Quakec Question... 
what is the proper way to make an entity toggle between visible/solid and invisible/nonsolid?

So far, what i have tried is calling self.modelindex = 0, self.solid= SOLID_NOT to make it invisible, then doing SetModel(self, self.model), self.solid=SOLID_BBOX again to make it visible.

Visually, it works. But, once it's been invisible, it can never turn solid again.

So, is there a better way? 
Trinca 
what are you talking about?

psssst. trinca don't tell anybody that i'm russian it's a secret :))

p.s. thanks spirit for the link....... 
Zomggg 
i think i'm drukn again 
(in)Visible 
To make it invisible and non solid, the following should suffice:

self.model = "";
self.solid = SOLID_NOT;

Not sure if adding self.modelindex = 0 adds anything to that, it might stop it being sent over the network.

To restore it again, use:

setmodel(self, *modelname*);
self.solid=SOLID_BBOX;

If you don't know *modelname* at compile time(eg a func_wall where the model is loaded from the map), then you need to stash the model info somewhere safe. Put a line like

self.mdl = self.model;

somewhere in the spawn function, and then use self.mdl in place of *modelname*.

If you're still having problems, try adding a call to setsize after setmodel 
Thanks For The Help... 
i finally got it to work, and here's how.

1) to enable being solid after turning inivisible and then visible again, i only ever set the model using setmodel once, and from then on alternate between setting modelindex to 0 or the actual index.

Since setmodel is never called, the entity stays linked into the world with the original physics bounds. self.solid can be changed without issue.

2) for entities that are set to START_OFF, there was still a problem where they'd never appear at all, and never become visible. My hack solution is to wait 1 second, then make them invisible/nonsolid in a special think function, rather than in the spawn function. 
Good 
explanation to make a monster invisible.

I'm still trying to find the right colours to improve Spectre in its translude way.
And as it is a sprite it has a funny way of walking. I think more darker colours on the outside and transparant inside.

http://members.home.nl/gimli/spectr.jpg 
Can Be Shot But Doesn't Collide With Walking? 
I'm probably going to think of the solution 5 seconds after asking this but ... let's say I want a monsters corpse to be shootable but I don't want it to collide with other corpses or the player. I just want it to react to gunfire (both instant hit and missiles).

Doable? 
Look At The Corpse, It's Voodoo QC Dancing... 
It can't be done easily. I say that because darkplaces has an extension for just this purpose, where you can make an entity SOLID_CORPSE, which behaves like you say. The following is ugly, not only in itself, but also in the dark secrets about standard ID1 code it exposes. Read on if you dare...


The key thing is that you can't change the .solid status of things during touch functions, it can cause engine crashes. So, the vast majority of the time, you want your corpse to be the same .solid type as a trigger_multiple entity, which is SOLID_TRIGGER.

We're going to use the grunt as our test dummy here. This is the kind of thing you need to do in your death sequences, during the frame that usually makes the monster SOLID_NOT:

void() army_die3 =[ $death3, army_die4 ]
{
self.solid = SOLID_TRIGGER;
setmodel(self, self.model);
self.touch = corpse_touch;

self.health = 20;
self.takedamage = DAMAGE_AIM;
self.th_die = GibPlayer;
self.th_pain = SUB_Null;

self.ammo_shells = 5;
DropBackpack();
};


We make it SOLID_TRIGGER, then call setmodel in order to link it, which prevents a "trigger in clipping list" crash. We make it's touch function corpse_touch, see below. We also make it damageable again, give it 20 hp, and define both th_die AND th_pain, the latter being important if you don't want to reanimate your corpse.

So here's the deal. In this state(SOLID_TRIGGER), when a missile collides with one, you get a touch event where the trigger is self and the missile is other. You do not get one where the missile is self and the trigger is other! I'm not sure if this is an optimisation just for missiles, or that any entity touching a trigger doesn't set off its own touch function. In any case, we're about to cheat:

void() corpse_touch =
{
local entity oself;
if(other.movetype != MOVETYPE_FLYMISSILE && other.movetype != MOVETYPE_BOUNCE)
return;
oself = self;
self = other;
other = oself;
self.touch();
};


See what we're doing? The first bit of code is just so that we don't bother doing anything if we aren't hit by a missile. You might want to use a different criteria for what is a missile, I went for something that required no changes elsewhere. Then we create a touch event on the missile by the trigger, but by hand instead of from the engine.

Once you've done that, you might think we're done for missiles now, and you can give it a try by lobbing a grenade into a corpse. It'll explode on contact as desired (you can of course change the corpse's .takedamage to DAMAGE_YES if you want to prevent grenades from detonating on contact, I set it like this for illustrative purposes). However, if you fire some nails into your corpse, you might be surprised to find nothing happens.

The reason for this is due to some entirely redundant code in the original QC, which must have been moved engine-side for performance, but then remained in the QC source by mistake. Look at the start of spike_touch in weapons.qc:

void() spike_touch =
{
if (other == self.owner)
return;

if (other.solid == SOLID_TRIGGER)
return; // trigger field, do nothing
...


You'll actually notice the first line in the code for almost all quake missiles. It's totally pointless, as the engine never makes collisions between entities and their owners. Just think how many times that code has been called in a million deathmatches across the world, and not once has it been true...

As we discovered near the top of this section(and to be honest I never knew about this before I tried today), SOLID_TRIGGER entities don't generate touches on missiles either, so the next line is similarly pointless. To get things working, comment both statements out, and do the same in superspike_touch. If you're bored, get rid of the self.owner lines in the grenade and the rocket.

If all has gone to plan, at this point all the missiles will damage the corpse.

Join us after the break when we investigate the shotgun and other tracelines... 
You Can't Talk To A Man With A Shotgun In His Hand... 
Before we start the business proper, I should alter you to two bugs with the code which only became apparent once the grunts started hitting corpses with their shotguns. The first is that when monsters begin infighting, they don't realise that their target monster has died when they become a corpse. This is because they consider their enemy dead if their health is below 0, but the corpse has positive health. The fix for this is left as an exercise.

Also, if the corpse gets damaged by another monster, it will get resurrected. To prevent this, add the line

self.flags = self.flags - (self.flags & FL_MONSTER);

when creating the corpse.

~~~~~~~~~#~~~~~~~~~

Ok, so the question is, how do we deal with weapons that use a traceline. And the solution is we make the corpse solid only for long enough to perform the tracelines. We in fact toggle the solid state of all the corpses in the map. Although in theory we might end up changing the solid status of something during a touch function(if a touch caused a shotgun to fire, for example) we hopefully* put everything back before the engine can notice.

So the simplest way I could think of to change all of the corpses to solid and back was: give all of your corpses the classname "corpse", then use the following functions to toggle the solid status:

void() corpse_solid_on =
{
local entity corpse;
corpse = find(world, classname, "corpse");
while(corpse)
{
corpse.solid = SOLID_BBOX;
setmodel(corpse, corpse.model);
corpse = find(corpse, classname, "corpse");
}
}


void() corpse_solid_off =
{
local entity corpse;
corpse = find(world, classname, "corpse");
while(corpse)
{
corpse.solid = SOLID_TRIGGER;
setmodel(corpse, corpse.model);
corpse = find(corpse, classname, "corpse");
}
}


The trick is then to surround calls to traceline with these functions. The axe is very simple, just put corpse_solid_on(); on the line above the traceline and corpse_solid_off(); on the next line after it.

For the shotgun, it's worth remembering that there are lots of calls to traceline in a single blast, and since toggling the solid status is fairly expensive in terms of performance, we should do it once per blast. So sandwich the functions around the

while (shotcount > 0)
{...}

block.

The lightning gun is left as a small exercise, but it's only really worth wrapping the first traceline in the function, the other two are basically enormous bugs which speedrunners exploit to kill things through walls.

That's almost it, but there's one finishing touch. If you go up point blank to shoot a corpse, you'll find that your shot disappears, or attack does nothing. This is because the traceline starts inside an entity, and so the returns from the traceline are a bit weird. The telltale signs of this happening are:

trace_fraction == 1 (not 0 as you'd expect)
AND
trace_ent != world (if it is world, then the trace really did hit nothing)

So what we do is add a bit of extra code which can cope with this case to the various weapon attacks. As an example, add the following code just below

if (trace_fraction != 1.0 )
TraceAttack (4, direction);

in the shotgun firing code:

else if (trace_ent != world) //if so, we are stuck inside what we have hit
{
trace_endpos = src + direction*16; //set some reasonable values
trace_fraction = 16/2048;
trace_plane_normal = -1 * direction;
TraceAttack (4, direction);
}


The code for the axe/lightning gun is similar, but if people get stuck on that exercise I can supply good fixes.


Well, that wraps things up for today. I would say that this latter part is quite an ugly hack, but the code in the first post is actually fairly clean. In all cases you should ask yourself "Would I show my mother this code" before you do something you might regret.

As a final parting thought, you might want to adjust the height of the bounding box of the corpse from the default, so that shots at head height don't blow up the corpse. If you do, remember that you need to do this in both of the corpse_solid toggling functions.


*I say hopeful, but I'm pretty sure it should work. I'm just not sure how careful the engine is at checking, and it's such an awkward thing to create a test case for. So I'm gonna be cautious in my endorsement. 
 
Preach, you are fucking awesome. :) Thanks man, I'm off to digest all of that. 
 
Absolutely beautiful, that all worked. Thanks again! 
Yeah 
That's very nice, thanks for the explanations. 
Bit Of A Backtrack 
A few pages ago I said QC only checks the first component of a vector when evaluating boolean logic on them. This is not always true.

if((!'0 1 1'))

will work correctly - notice the double brackets. If you take the inner brackets away, then it stops functioning correctly on compilers which perform optimisations. Here is why:

When an if statement is compiled, it gets boiled down into an instruction to skip over the next n lines conditional on the value of an integer fed to it. QC actually supports two operations: IF and IFNOT. The former skips a number of lines when the integer is non-zero, and the latter them if it equals zero.

When the original QC compiler encountered code like:

if(!my_number)

where my_number is a float in qc, it would actually break it up into two instructions: The first one would apply the logical operator ! to my_number, and store that in a temporary. The second instruction would then feed that temporary to an IF_NOT.

One of the first created QC compilers saw a chance to optimise there, change the IF_NOT to an IF, and skip the ! instruction entirely. This sounds reasonable enough, except that you must not do it for a vector. There is a special operation for applying ! to a vector, which actually applies it to all three components. If you don't use it, then the first component of the vector gets mistaken for a single float, and you get bugs.

In conclusion, the original QC design was perfect and Carmack remains a god. The bug lies in the newer compilers, which should not optimise in the vector case.

Coming up soon is a description of the sequence in which things occur in a frame, which is the reason I was poking about in the source. 
 
Are optimizing compilers really even necessary? I mean, has Quake ever been slow when fed reasonably coded QuakeC? 
Possibly 
QC probably used to be a potential bottleneck, back in the day when r_speeds of 800 was considered unacceptable. The engine contains a "profile" command to see the 10 most demanding QC functions, so they were being optimised back in the day. Probably this is where some of the builtin functions come from, like find and findradius, which could be written in pure qc given the earlier builtins.

Nowadays modern limit-pushing maps increase the number of faces by a greater factor than they do number of monsters/entities(most of which are idle and no serious drain anyway). In addition, much of the greater rendering cost gets push onto the graphics card.

So in that sense bad QC is unlikely to cause a framerate drop or anything like that. But there are other limits that have to be fought when it comes to quake. There is a maximum size that a progs file can be, for instance, and compiler optimisations which eliminate duplicated strings in the file have helped large mods compile.

Also, the runaway loop counter in qc is 100000 instructions in a function. If you're coding some kind of loop, then any instructions you can eliminate from the inner loop section are invaluable in avoiding the runaway limit. The findradius builtin mentioned above swaps what would have been a loop through all the entities with a pass through a linked list of the relevant ones.

Of course, there's also some personal satisfaction involved with some optimisation. I've been playing a kind of "programmer's golf" with the ID1 progs, trying to get as few instructions as possible in the monster ai functions like "visible" and "range". At the moment I'm trying to resist writing them directly in assembler to eliminate another temporary variable...they're the best targets for optimisation if you'd like to make a 20,000 monster map support 25,000 instead! 
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.