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, got that one. It also seems to do something strange to the bbox - I've seen this before where the size isn't what'd be expected.

I imagine that can be fixed by changing the movetype and putting an invisible helper in there - that's similar to our pushable solution. 
Can Anyone Confirm? 
if i set .nextthink = time + 0.001 (or any very very small number), that should guarantee it will be called on the next frame, right? 
Almost Surely 
Most of the time that will be fine. Most engines enforces a 72 fps limit on physics, because once you get into higher framerates you start to see physics glitches. I assume these arise because of floating-point errors once you start evaluating collisions with increments that small. If you want to see it, try increasing the max_fps in fitzquake to the 300-400 range before riding on a lift (I may have remembered the command wrong, but there is one that lets you change it server side).

One circumstance where you might have a problem is if someone sets host_framerate very low to create a slow motion effect. It's not too much of a concern though, because it is someone messing with console variables, and you can't always prevent people breaking stuff if they use em.

However, there's an even simpler way: set .nextthink = time, or even .nextthink = 0.01.

I used to think that quake ran think functions if the following inequality held

time - frametime < .nextthink <= time

But actually, the left hand inequality does not exist. From the engine source:

�thinktime = ent->v.nextthink;
�if (thinktime <= 0 || thinktime > sv.time + host_frametime)
����return true;
�if (thinktime < sv.time)
����thinktime = sv.time;
�ent->v.nextthink = 0;

The engine resets nextthink to zero every time it calls a think function, and so ignores think functions only if nextthink is in the future or <=0. You should be able to get things to run every frame just by setting nextthink to something <=time.

There's an interesting additional bit of info to be gained from this section of the engine code. We see that the QC value time is set by the following line

�pr_global_struct->time = thinktime;

The previously quoted source code set thinktime from the entity's original .nextthink value. So during the QC think function, time is always reported to be the moment that the entity intended to think, rather than the server time at the end of the currently processing frame. The one caveat is that time is bounded below by sv.time, so if we set self.nextthink = 0.01, we won't suddenly be miles back in the past when think is called.

Some of that explanation might be a bit garbled, I was figuring it out myself as I went. So maybe this timeline will help

We assume that at this time the server is running at 50fps.
This is the "old time", the first point in time not processed in the last frame. Time is set to this value for the following things:
StartFrame, think functions for .nextthink <= sv.time.

sv.time + delta
As long as delta < 0.02, then the think for that entity must occur in this frame. Time is set to this value for the following things:
think functions for
sv.time < .nextthink < sv.time + 0.02

sv.time + 0.02
Anything with a think greater or equal to this is ignored, it will happen in the next frame instead.

In conclusion, I'd go with self.nextthink = 0.01, because time will be set to sv.time anyway. I've not tested if there are any side effects of that though. self.nextthink = time should also work and might look less weird. 
Thinking Without Thinking 
Personally, I prefer self.nextthink = time; although feel free to follow it with // Next frame if you want your code to be understood by others.

And note that the code checking for .nextthink being non-zero means that the think function is only run once (unless .nextthink is reset). But setting it to 0 will cause it to stop thinking. Which is what causes the statue bug in id1.

Reading that through has taught me something, though. Namely that time is relative during a think function.

I do have one question, though: What happens first: think or touch? 
thanks, preach. as always, very informative!
while you're here, could you tell me if i understand .ltime correctly?

it seems to basically be time, except it only counts up when a bsp model is moving.
i believe it only works when the solid is SOLID_BSP or if the movetype is MOVETYPE_PUSH.
when it stops moving (comes to rest OR is blocked), the timer stops. this is how calcmove can work with a static nextthink even if a door or train gets blocked. 
Oh, Yeah 
I should have mentioned straight off that the rules are different if the entity is MOVETYPE_PUSH. But it's worth looking at, because it also lets us explore how physics timing relates to this new QC-think timing.

You are right about how ltime works, it's "local time" for the MOVETYPE_PUSH entity. It advances at the same rate as the normal clock except if
a) The entity is blocked, in which case time is not advanced
b) The entity's .nextthink will occur before .ltime + host_frametime(within this frame) in which case ltime is increased only as far as .nextthink (bounded below by 0)

The latter case is important because when ltime only advances by as much time as it needs to equal nextthink, the physics run on the entity this frame are calculated so that it only travels for this amount of time, rather than for the full length of the frame.

I should add that again, this is only applied to MOVETYPE_PUSH entities. Other entities always move for the entire length of the frame, host_frametime. There is also a strange kind of time travel which can affect these entities. Think functions are calculated first, and when they are called the QC variable time might be anywhere between sv.time and sv.time + host_frametime, depending on the exact value of .nextthink. Once the think is resolved they will get moved, but if they collide then the QC time is always set back to sv.time for the .touch functions.

As a final thought, it is worth remembering that entities in quake are processed by the physics engine in sequential order, and setting the QC time variable does not interpolate any entities between their positions at the start and end of the frame. All the entities before the current entity will be at their end of frame position*. All entities afterwards will likewise be in the position they occupied at the end of last frame. Knowing how the QC time variable is set is only of interest to resolve seeming paradoxes where you are sure two events occur in the same frame, but the QC reports different times for them.

* Ok, this might not be their final position, because something might collide with them or teleport them in QC or something. But in terms of their own physics, they are done for the frame, no more moving or thinking. 
b) should really have read:

"The entity's .nextthink is less than .ltime + host_frametime(before the time at the end of the frame) in which case ltime is increased only as far as .nextthink (bounded below by 0) "

This would make it clear that ltime does not advance when nextthink <= ltime. 
Back To Ltime 
is there any reason why ltime would run slower than normal time?
i can't really explain it better than that, other than when i watch both ltime and time displayed next to each other (for example, bprinting both every frame), ltime counts up at a slower rate. 
Only thing I can see which might account for it is that sv.time and host_frametime are both double-precision floats. Since ltime is stored in a QC field it only retains the precision of a regular float. In fact, the increment gets cast to a single-precision float before it's added to the single-precision ltime value. So my guess would be that it's a byproduct of the lower level of precision in that calculation. 
i tested a bit more and slow frametimes seem to slow it down a lot? o.0
if i set fitzquake's maxfps to 10, it counts extremely slowly. i don't really understand what's going on here. 
Sorry Lardarse 
I do have one question, though: What happens first: think or touch?

I totally missed this question first time round, here goes:

For a player:
PlayerPreThink always comes first.
Next is the .think function (for frame animation etc)
Then comes whatever kind of physics the player has, so if there's a collision .touch happens now
Finally PlayerPostThink is run.

If it's a MOVETYPE_BSP, it moves first (and so any .touch and .blocked calls occur), and then calls .think functions after that*.

MOVETYPE_NONE won't create collisions by itself, so it only runs .think. It's worth considering that a MOVETYPE_NONE can still be involved in collisions, and in that case the .touch may be called before or after the think, depending on whether the colliding entity is before or after this one in the list. The same is true for any entity when they are the second party in the collisions.

MOVETYPE_NOCLIP doesn't generate collsions either, it's just .think when it comes to processing.

MOVETYPE_STEP is for monsters, and they have a weird order. If they're free-falling from a jump, then that gets processed first, and any collision there produces a touch before the think. However, most of the time monsters only move during think functions. One of the two navigation builtins are called in the think, which hands control back to the engine for physics to be run on the "step". If it collides, then the touch gets called on top. So the think will begin before any touch, and finish after the touch!

Finally, all the other "projectile" movetypes run .think before moving, and thereby having a chance to .touch anything.

In conclusion, it's a mess! The entity might always be the "other" entity in a collision, so you can't really say concrete stuff about whether the touch will happen before or after a chance to think. I think the list here is still useful though, for knowing when the physics runs. This means you can always be aware of whether an entity has already moved or not while in a think function.

*There is an argument here for rewriting the hipnotic rotator code here. If you make a function which is called from StartFrame which loops through all the blocker entities and sets velocities for them, you have changed their velocity before physics runs on them, so they'll move into place this frame. The current code sets it in the think, which means they're always lagging behind the target for a frame. You would also be able to set .nextthink to (.ltime + framtime * 0.5), and use a doubled velocity to ensure exact motion.
a little bit more on SOLID_BSP and .velocity

a SOLID_BSP entity won't move unless it's .nextthink is non-zero. it doesn't matter what it's set to, although, obviously if it's less than ltime, it will be set to 0, just as long as it's set. if you set .velocity without setting .nextthink, no movement occurs (although the entity retains .velocity setting). 
Preach, Re: 332 
preach, could you correct me if i'm wrong?

in post 332, i mention that time seems to more slower on ltime than real time.

say, on your bsp entity, you set:

self.nextthink = self.ltime + 0.01;

to loop a function over and over via .think.

if your framerate was really low, like 10fps. according to what you said above: The entity's .nextthink will occur before .ltime + host_frametime(within this frame) in which case ltime is increased only as far as .nextthink

if time = 0.
one frame goes by at 10fps:

if your nextthink is 0.01, but you are getting 10fps, then .ltime will be set to 0.01 even though real time = 0.1

we get ready for the next frame and set self.nextthink = self.ltime + 0.01; //next frame
.nextthink is 0.02 now.

now more frame at 10fps:

time is 0.2, but .ltime wil only be 0.02.

and so on and so forth.

ltime is only incrementing by 0.01, even though actual time is incrementing by 0.1.

am i getting this right? 
Yeah, that's right. Remember it will also only move for 0.01 seconds per frame, so you need to set its velocity carefully if you are relying on velocity for motion. 
i made accelerating/decelerating movers but i couldn't find an acceptable way to make them accurate.
from what i have seen, it seems the only way to make it 100% accurate would be to externally set velocity via a helper entity. unfortunately, this would nullify the point of .blocked and ltime as a blocked mover wouldn't pause and would risk becoming out of sync.

as such, i've just left the acceleration with ltime and accepted the inaccuracy. it unfortunately means if you are getting really bad frame rate 10-20fps, you will see a marked increase in time for movers to complete their movements.

otoh, maybe if i accepted a lower precision by setting nextthink to ltime + 0.1 (use slower refreshes), it would decrease the disparity between time and ltime...

i'll have to see how that works out i guess. :S 
Accellerated Progress 
I think that there is a way to overcome these difficulties without a helper entity, so long as you're happy with a bit of not-really-calculus behind the scenes. You can get a version with one frame lag just by using a few entity fields to store some floats(and the one frame lag could be eliminated using the trick mentioned a few posts above of pushing updates to a mover's velocity during startframe, in order to occur before physics).

The key is to increment one of the variables by frametime in every frame which you are blocked. I'm writing out the details in my notebook currently, but it's the kind of thing that would really be best served with an external page full of diagrams and equation notation rather than a hurried post on this board. Perhaps even some genuine, complete QC code to prove I'm not chatting out my arse. Watch this space... 
well, i will definitely want to see what you are talking about, but bear in mind i'm not that much of a mathematician, so if it's as complicated as you are implying, i probably won't do it. ^_^;; 
Running Dry 
Hey Preach, did you receive my email?
I'm rather stuck in the monster toppic. 
Send it again, don't think I have it. 
I ment the Monster thread.
I'll send it again. 
Set to time + something when the player is in sight, but movement straight for
him is blocked. This causes the monster to use wall following code for
movement direction instead of sighting on the player.

in ai.qc

is this true? it sounds like a lie. :P 
The Hunt Is Over 
The only instance of "hunt_time" in the qc source is in the comment about ideal_yaw. I'm guessing it's something they used to do in ai_run. It may have been taken out because movetogoal tries the direct path to the enemy before using the wall following code. This would suppose that originally the function would call walkmove if hunt_time wasn't set, set hunt_time if walkmove returned false, and called movetogoal while hunt_time was active. 
ah that would make sense.
one thing i noticed about the movetogoal code...
it always seemed to me that doom had much better wall following code. quake monsters often get caught in areas when trying to path to the player, but doom monsters quite often turn up in surprising places especially if you watch them in the automap. quake AI seems to 'give up' wall following very early, usually before the wall following has a chance to get around a particular corner or whatever.
mm, that was random. :P 
Well Then 
we should just see how D00M does it,
and port it to,
(_)uake :D
Monster Only Clip... 
i wonder if it would be feasible to take the func_togglewall and do this:

rename it to func_monsterclip
make it non-solid
movetogoal -> movetogoal_builtin
and then we make a new movetogoal that loops through all func_monsterclips, makes them solid, called the movetogoal_builtin and then afterwards, makes all the func_monsterclips nonsolid again. 
First | Previous | Next | Last
Post A Reply:
Website copyright © 2002-2022 John Fitzgibbons. All posts are copyright their respective authors.