Yeah
Don't call variables 'a' or 'b' - give them a name that reflects their meaning in the program.
Ah - Good Idea.
#731 posted by RickyT33 on 2012/03/25 00:21:37
$wholeArray
$wholeCumbrianRD
$wholeRrestaurant
$wholeKey
Craving Your Indulgences Again
#732 posted by Mike Woodham on 2012/03/25 16:32:25
I don't do much qc but whenever I do I find myself running into brick (BRICKL_0 or similar) walls.
I have a procedure that I want to run x times as selected from the editor (self.something = x). I want to say along the lines of:-
void () Proc1
do some stuff
while (self.something >0)
do this
wait random y to z seconds
self.something = selfsomething - 1
ewhile
do some other stuff
endproc
(I know I am mixing languages, I am just trying make it look obvious what I am doing)
When looking at 'think' and 'nextthink' examples, they all seem to act on an external procedure/function before returning to the calling procedure/function. I also see use of recursion i.e. a procedure/function calling itself.
Is it necessary to use 'think' and 'nextthink' for timing purposes in my ditty above or is there some other construct available. If I need to use 'think' and 'nextthink' is it therefore advisable to separate the 'while/ewhile' (or whatever loop is used) into its own proc/func?
#733 posted by necros on 2012/03/25 16:43:40
you are thinking about it the wrong way.
best to think about what gets done in a frame and what doesn't.
nextthink and think is what you need to delay over multiple frames.
doing a while loop will do everything in the same frame.
in this case, you don't want a while loop at all.
void() proc1
{
//some stuff here
self.think = proc1; //run this again
self.nextthink = time + (some random amount here);
}
the way nextthink works sort of is that the engine looks at what nextthinks are > current time and when they become < current time, the engine sets nextthink the 0 and runs the .think function with self as the 'thinking' entity.
Necros
#734 posted by Mike Woodham on 2012/03/25 16:58:26
So the engine is not waiting for my procedure but it will have my 'thinks' queued up? Also, do I not think of this a true recursion?
By the way, I just ovelayed the draflam2.spr with the bigexp.spr (just via an entity choice in the editor and using two entities) and it looks pretty smart. I am not sure what it will look like to a player seeing it for the first time in a game - you know how fussy Quake people are :) But it looks good enough to blow the head of a Vermis!
#735 posted by necros on 2012/03/25 17:12:48
no, it is not recursion.
recursion is when a function calls itself.
you can accomplish this in qc with the usual method:
void() proc1
{
//stuff
proc1();
}
this also does everything on the same frame, just like for and while loops.
when you use thinks, it is the engine that is calling the function when nextthink ticks by.
for the explosion, if you're feeling adventurous, you could try throwing a bunch of MOVETYPE_TOSS invisible entities out of the explosion point that spawn explosion sprites themselves. ;)
looking forward to seeing it.
Queuing
#736 posted by Preach on 2012/03/25 19:39:36
To add, there is no queue with think functions, you only get one. So if you have code like
self.nextthink = time + 0.5;
self.think = CleanTheDishes;
self.nextthink = time + 2;
self.think = CookDinner;
the second think function overwrites the first one and so you end up eating off dirty plates. One way to fix that is to move the code which sets up CookDinner into the CleanTheDishes function, chaining the functions one after another.
This worked because our two functions were dependent. If you need your entity to think about two independent things one solution is
to have a master think function. This should think regularly, and check timers on your entity manually. The way that playerprethink and playerpostthink handle powerups running out is an example of this pattern.
If that's too complicated, the other way is to have two separate entities, as each one has its own think function and nextthink timer.
Preach
#737 posted by Mike Woodham on 2012/03/25 22:07:25
Nice analogy; because we don't want to be eating off dirty plates now, do we?
It is falling into place, so thanks.
QCC
#738 posted by sock on 2012/04/04 16:52:51
I have been compiling my own progs.dat file using the tools from the ID FTP. Is there a good base of .QC files? Has the community got an agreed on base? Any recommendations? Something to start from?
All Can Be Found Here:
#739 posted by rj on 2012/04/04 19:11:37
Door Key Use Sound Troubadour Verily
#740 posted by Kinn on 2012/04/04 19:41:45
Ok, this is fairly what-the-christ.
So, I'm running around my mod opening doors with keys. Use a key, door opens - it just plays the door open sound. I'm thinking hang on...didn't it used to play a "use key" sound before when it opened?
I then check vanilla quake. But no, even playing vanilla quake - no mods - key doors are only making the door open sound when they open.
I look in the QC - vanilla QC - the door is *trying* to make a "use key" sound, but it's immediately getting overridden by the "door open" sound (same channel). Looks like that's how it behaves all the time. So, another id bug to fix then. But why do I have a very clear memory of hearing the use key sound in vanilla Quake??
And more creepily, why did Scampie hear it on some compiles of his map, but not on others??
What is this door devilry??
#741 posted by necros on 2012/04/04 20:37:56
sock: and definitely use a new compiler.
you can use more c conventions like for loops and increment and decrement operations.
also removes arguably useless stuff like needing semicolons after function braces.
kinn: not sure, but maybe because czg's progs from his last map pack had a fixed variation where the key sound plays and the door opens after a short delay?
More Door Bollox
#742 posted by Kinn on 2012/04/04 20:38:09
ok, so i've fixed the key use sound thing, and also for visual effect added a small delay between the key being used and the door actually opening. This introduced me to something that I never knew anything about before. Namely this:
in door_touch, if I replace the call to door_use(), with:
self.nextthink = time + 1;
self.think = door_use;
The door actually opens after about 10 seconds, not 1 second. (whu?)
if I write:
self.nextthink = self.ltime + 1;
self.think = door_use;
The door opens after 1 second, as expected.
do doors play by totally different rules when it comes to time and nextthink shizzle?
Hah
#743 posted by Kinn on 2012/04/04 20:40:05
wrote that before seeing necros' post :}, didn't know czg did this but maybe that's where i'm remembering it from...
#744 posted by necros on 2012/04/04 20:42:59
preach made a fantastic post about this explaining how nextthinks work on MOVETYPE_PUSH entities:
http://celephais.net/board/view_thread.php?id=60097&start=330&end=330
Append /\
#745 posted by necros on 2012/04/04 20:45:43
also of note, setting .velocity on a MOVETYPE_PUSH entity without setting a .nextthink will do nothing.
.nextthink must be set higher than .ltime in order for the engine to update position based on .velocity
Compilers
#746 posted by sock on 2012/04/04 21:32:26
@rj, thanks, I am still learning where everything is hiding! :P
@necros, I got the qcc compiler from ID and it would not work under 64win so I got hold of FTEQCC instead. The original ID files produced a ton of warnings and crap messages. I don't suppose any of that has been cleaned up and a new qc pack of files is anywhere? Or should I not worry and just add my own stuff to the pile?
Are there any QC libraries? Luckily czg released his source so I can see how he did the trigger spawn routines. Is there other things that everyone uses?
Necros/preach
#747 posted by Kinn on 2012/04/04 21:33:41
thanks, that was useful. Preach - never stop quakeing, you are an asset.
So, from what I gather, because time was not advancing from the door's point of view, when I set self.nextthink = time + 1; it still had a self.ltime of like 0.1 or something, so the "ten second" delay i mentioned earlier was basically due to however long the game had been running up until that point + 1 second. lol.
Funny that I'm still learning new things about quake, but then again I've never dicked around with push ents before, which is why i've never come across this :}
Sock It
#748 posted by Preach on 2012/04/04 22:17:44
Sock: http://www.btinternet.com/~chapterhonour/clean%20source.zip ought to be handy.
It's my cleaned out copy of the qc source, only for the purpose of defeating all those warnings. I might have made some other minor tweaks but I'm pretty sure it's faithful to the basic game. The only real compromise I added to it is a pointless, vestigial function at the bottom of DummyFunction which defeats the warnings about .wad and .light_lev not being used in the progs, even though you must define the fields.
Broken Link
#749 posted by sock on 2012/04/04 23:12:09
@Preach, that is awesome what you have done, but I can't get the link to work?
Arrgh
#750 posted by Preach on 2012/04/04 23:55:24
Forgot to test the link, turned out the ftp failed because the server dislikes filenames containing spaces. Renamed to
http://www.btinternet.com/~chapterhonour/cleansource.zip
Awesome
#751 posted by sock on 2012/04/05 00:09:00
Thanks Preach that has saved me a ton of effort trying to work out what is my mistake and what is broken already.
Not Sure If This Has Been Covered Before...
#752 posted by necros on 2012/04/06 05:08:59
if i have two entities, one with .nextthink = time + 0.01 and another with .nextthink = time + 0.02 will the order the thinks occur be based on nextthink or just the order the entities are stored in the list in the engine?
my gut tells me it's the latter, and that makes me sad. :(
#753 posted by necros on 2012/04/06 05:10:48
didn't say, but it should be obvious, i'm assuming the next frame will be longer than 0.02 seconds after. say 0.1 seconds after to be clear.
time = 0
->a.nextthink = time + 0.01;
->b.nextthink = time + 0.02;
time = 0.1
a fires first, guaranteed? or it might be a or b based on it's position in the list.
i should think before i post. -_-
You Are Correct
#754 posted by Preach on 2012/04/06 10:47:13
If the frame takes longer than 0.02 then it is strictly in the order that the entities appear in the entity list.
Interesting fact though, say that instead we start at time 17
a.nextthink = 17.01
b.nextthink = 17.02
Now at 10fps the next frame starts runs at server time 17.1. However, during the think function for "a" time will be set to 17.01, and during the think function for "b" time will be set to 17.02. This change literally applies only to the QC variable, not to anything else in the server.
Back to the original problem, a lot of the time with the think intervals that you have chosen players today will see a think before b because the cap on fps is 72, which is high enough for a frame between 0.01 and 0.02. Beware of the effect in the previous paragraph though, if you are setting the nextthink values from within individual think functions for "a" and "b" it may not be the case that
a) b.nextthink - a.nextthink = 0.01 (even accounting for floating point error!)
OR
b) that b.nextthink won't occur in the next frame!
That second one needs a bit more explanation and in particular would benefit from concrete figures. We will say that the server issues a frame every 0.014 seconds.
Frame i:
server time = 17
b.nextthink = 17.003
Frame ii:
server time = 17.014
b's think at 17.003 executes and contains the qc:
b.nextthink = time + 0.02;
Since the engine sets time = 17.003 during the think, we now have
b.nextthink = 17.023
Frame iii:
server time = 17.028
So b executes a think in consecutive frames!
If you really need execution in order you might have to concoct a "priority queue" style system, with a new fakenextthink field. That would mean a linked list of entities so that the following pattern holds:
self.fakenextthink <= self.nextent.fakenextthink
You insert into the queue by traversing the list until your fakenextthink is <= the next fakenextthink. Finally you'd need a function that runs from startframe and traverses the queue, running fakethink on all the entities which are due. Since the queue is in order of fakenextthink, they run in the desired order and as soon as you reach one which is not yet due to run, you know you can stop traversing.
Two tips: firstly don't forget to purge the entities from the queue before you fire their events! It's very likely that entities will want to reinsert themselves into the queue from their own fakethink calls, so you need to be aware of that. Secondly, I'd keep up the practice of setting time = fakethinktime for the duration of the fakethink call (then you MUST reset it to evaluate the next entity on the queue).
These two tips combine for an interesting effect: you can have multiple fakethinks occur in a single frame, since they get reinserted into the queue with a fakenextthink that might still be lower than the server time. I suppose to be sure of correctness we need to make sure the loop of execution is less traversing the linked list, so much as always looking at the head of the list and popping it if execution time has arrived.
I have another use for this priority queue in mind which is less technically involved and more generally applicable, so I might go away and write it up with some nice complete code over the weekend...until then!
|