 First Post!
#1 posted by metlslime [71.202.113.29] on 2007/08/08 05:08:46
So, here's what I'm trying to do right now, and probably nobody knows the answer but here goes. What I'm trying to set up is an entity that emits a constant sound, but is also toggleable. So when you turn it off, the sound goes silent, and when you turn it on, the sound starts up again.
Problem 1: if you are out of hearing range when it turns on, the object will be silent when you get close.
Problem 2: if you save and reload the savegame, the object will be silent.
The basic cause is that sound() calls are fire-and-forget and quake assumes the sounds are short-lived so the above situations won't be much of a problem.
I've tried various solutions, the latest is various versions of "restart the sound every 1 second or so." The problems with that are first, when you approach the object its sound starts up suddenly instead of fading in, and second, you can hear obvious repetition when standing next to the object.
Increasing the frequency of sound() calls reduces the sudden start, but worsens the repetition. I'm going to try and improve this hack by having 3 or four different sounds, and playing them at random, so that you don't hear any obvious repetition of sounds.
#2 posted by necros [99.244.15.189] on 2007/08/08 05:35:35
it is possible, but it's slightly hacky.
unfortunaly, i don't remember who told me about this method... it may have been lordhavoc, but i'm not sure.
anyway, the engine can only load ambient sounds (true ambient sounds that the engine keeps track of regardless of player position) properly when the map is loading up.
you can trick it by using an unused SVC (29). unfortunatly, you can't specify a filename for this, you need to actually feed in the # of the sound in the order as it was loaded into cache. ¬_¬
the best way to do this is to precache your ambient sounds first, this way you don't have to worry about anything being precached before it and mucking up your ambient sounds.
the first sound precaches are located in weapons.qc (W_Precache). load any new ambient sounds in there at the top of the function.
float SVC_SPAWNSTATICSOUND = 29;
Define the constant for convenience, and write a little function to use it:
void(float soundnum, vector org, float ambvolume, float atten) spawnambient =
{
WriteByte(MSG_ALL, SVC_SPAWNSTATICSOUND);
WriteCoord(MSG_ALL, org_x);
WriteCoord(MSG_ALL, org_y);
WriteCoord(MSG_ALL, org_z);
WriteByte(MSG_ALL, soundnum);
WriteByte(MSG_ALL, ambvolume * 255); //translate this into a value between 0 and 255...
WriteByte(MSG_ALL, atten * 64);
};
now just call your new function with whatever sound # you want.
this will spawn a true ambient sound into the map after it was loaded, and won't stop playing when you move out of range.
you'll notice the lack of any channel specification... i'm not sure if it's possible to turn it off again after it's been turned on. i can't check right now, but i'd guess using a non-looping sound will give you that 'non-looping sound for ambient sound' error, since it's not just a normal sound call.
but you will at least be able to turn it on. :x
maybe someone else can build on this?
 Necros....
#3 posted by metlslime [71.202.113.29] on 2007/08/08 09:29:10
Interesting, it would only work if the it could be turned off again somehow. Hmm....
 Also...
#4 posted by metlslime [71.202.113.29] on 2007/08/08 10:07:07
my hack with the multiple random 0.1 sec sound effects works fairly well, but the problem is you can hear a sort of distortion/clip sound when the new sound overrides the old (the same sound you hear when a sound isn't looped correctly, meaning the waveforms don't line up and there's a sort of audio seam.) I may have to live with it. The sound effect for this has a lot of white noise in it (it's a steam jet) but I also wanted to do this for forcefields, and i'm worried that sound won't hide the clipping artifact as well.
 Savegame Function
#5 posted by Preach [86.146.19.13] on 2007/08/08 11:59:20
As it happens I was writing some code yesterday that might help you out. What it does is provides you with a function that is run once when you load a saved game, but not when the game starts first time round. Replace the StartFrame code in world.qc with the following:
nosave float loadflag;
void() LoadGame =
//runs if player has just loaded a game
{
dprint("*****new game loaded******\n");
}
void() StartFrame =
{
teamplay = cvar("teamplay");
skill = cvar("skill");
framecount = framecount + 1;
if ( framecount == 3)
loadflag = 1;//started a new game, not loaded a savegame
else if(!loadflag && framecount > 3)
{
LoadGame();
loadflag = 1;
}
};
You'll need a compiler which supports the nosave keyword for variables, frikqcc and fteqcc are the two I've tried. The idea is that the loadflag isn't saved in the savegame, and so gets reset to 0 when you load a game. The reason you have to wait until the third frame before you set it originally is because when quake loads a savegame, it runs the whole worldspawn procedure to rebuild the precache lists. The worldspawn procedure includes running two game frames, to give things a chance to droptofloor etc. So if you change a nosave variable during those frames, it'll have that value when you load savegames - which might be different to what value it had when you saved - but it's not what we want here.
So that gives you a way to restart the sound after someone loads a game, replace the dprint line in LoadGame with a function that searches for all the sounds that should play and restarts them.
As for the other problem, one possible solution would be to calculate the distance at which the sound can just be heard, assuming you started in range of the sound. Then have the entities search for a player within this radius regularly and start playing when they just enter it. Might be more difficult to make it work in coop.
If you do that, then you don't really want to use findradius for the job, it's a bit excessive to have 10 findradius calls every second per entity if you have a few of these things about. Better to loop through the first maxplayers worth of entities with the nextent command, since the players are always the first entities on the server. Then just test the distance for each of those that turns out to be a player. Whether
vlen(vec) < d
is faster than
vec_x*vec_x + vec_y*vec_y + vec_z*vec_z < d*d
is something I've been meaning to test. One is trying to calculate the square root of a quantity, but it is a builtin, so it's faster than qc code. Probably not necessary to get that kind of saving here if you're testing at most 16 entities, and most of the time just 1, it's just an interesting question.
 Preach....
#6 posted by metlslime [204.15.3.5] on 2007/08/08 20:39:29
yeah, this findradius idea is the direction i'm thinking of going next. One challenge is, the code to play a sound will not bother playing a sound if both right and left stereo channels can't hear it, and the calculation to determine the volume at each "ear" (channel) is somewhat complicated. Not sure if i'll need to recreate that in quakec or if it will suffice to estimate the radius in some other way.
 Toggleable Sound
#7 posted by Mike Woodham [81.158.239.232] on 2007/08/09 00:01:20
The music in FMB_bdg is toggleable and plays everywhere. Being a looped sound, it would play forever if not switched off by the player (by operating the music button) or when the player unwittingly moves through an off-trigger.
It doesn't play from a savegame even if it was playing when the game was saved. But the savegame must be recording the state of the entity because you have to operate the music trigger twice after a savegame if you want the music to play - the first time turns the music off even though you can't hear it, and the second time turns it back on, and you then hear it.
I created the entity following an idea by Preach.
// if the player has not touched the 'toggle-music' button then this must be
// a triggered event, which means....
if (mechanism.classname != "func_button") etc...
Could this be developed to check the state of the entity when opening a saved game and play the sound if TRUE? Or am I completely off-track understanding what you want to do?
 Mike:
#8 posted by metlslime [204.15.3.5] on 2007/08/09 00:33:33
well, it sounds like preach's save game code would fix your problem, but I have a second problem that you don't have, which is that the sound won't play at all if you are too far away at the time it is first triggered. Your music is always audible, so there's no worry about being too far from the emitter at the time that it starts up.
 Model Import
#9 posted by Lunaran [76.208.69.54] on 2007/08/09 05:18:15
I started messing with the quake model gen source to make it parse ascii files instead of .TRI, so that I could export to text from my modeler of choice (ie maya). I got all that working fine as far as I can tell, but the .mdl that gets written out crashes Fitzquake and every model editor I've found to try. Stepping through the exporter in debug doesn't really seem to show anything wrong, as the data is all seemingly correct before it gets written.
I took a break bashing my head against this a long time ago (like the end of 2006 now that I think about it :( ), so if I started poking at it now it might become obvious to me, but does anyone have any tips or suggestions? Are there any quake ports or other bits of software that I can try to load a model in that will actually tell me what part of the file is horked rather than just pooping?
 Model Import
#10 posted by Preach [217.42.220.35] on 2007/08/09 12:59:25
A good starting place would be:
http://tfc.duke.free.fr/coding/mdl...
This gives you a big chunk of c code that will read a mdl file. You can skip the chunk about rendering it and just write a quick function that prints out the header data once it's loaded it, to check whether that's been written correct or not.
You might also want to look at
http://people.pwf.cam.ac.uk/~ajd70...
it's an export script for blender written in python. I wish I could remember what I did to make it work though, I had the same problems as you are and spent a few hours staring at a hex editor to find out what went wrong. If anything jogs my memory I'll let you know. Shame I never got round to writing the corresponding import script, it'd probably be good for diagnosing the problem...
 Yeah
#11 posted by Lunaran [24.158.1.74] on 2007/08/09 18:12:03
I've looked over all that in the quake and modelgen sources, but I'm not very confident in writing a new piece of software to check why the last piece of software I wrote doesn't work, because clearly there's no guarantees the new one's not going to be fucked in some tiny significant way also, especially if they're both doing the same things to the same data structures. I'm just a simian level designer.
Part of the problem is that I had to do so much to get the thing to a testable state - .bmp import for the skin and then my ascii reader thing, because there's no program left on earth to write TRI files and there's no program left on earth that'll write whatever silly-ass image format modelgen looks for.
 Hmm
#12 posted by Preach [81.153.26.88] on 2007/08/09 18:35:02
Well, upload an example model that the tool exports and I'll take a look at it, try and figure out what's wrong with it.
 Okay...
#13 posted by metlslime [71.202.113.29] on 2007/08/10 07:17:30
The steam jet sound problems seem to be solved.
First, I play the looping sound whenever the object is turned on, and the "fade out" sound whenever it is turned off. This was the original naive implementation.
Second, to deal with the problem of steam jets turning on when you're far away, I re-trigger the sound every 0.1 seconds when you are between 300 and 350 units away. The clipping sound artifact is there, but since it's so quiet at that distance, you barely hear it. When you get closer, it stops re-triggering it so you can hear a perfectly looped sound.
Third, I used Preach's loadgame callback idea to retrigger all sounds in case you are standing within 300 units when you save and reload the game.
Problem solved! Thanks for the help preach.
 Oh Wow
#14 posted by Lunaran [24.158.1.74] on 2007/08/11 02:34:17
thanks Preach! It might take me a bit to gather all the code and stuff back up and actually produce one, but I'll hit that this weekend.
 Pointers Anyone?
#15 posted by Mike Woodham [81.158.239.232] on 2007/08/11 23:46:40
So I thought I would try Preach's code from above and simply put a call to my music_play_tune routine for any savegame but got this super message from aguirRe's engine.
This Onion
CDAudio: drive not ready
CL_SignonReply: 1
CL_SignonReply: 2
INDIRECT 28(self)entity 0 481(distance).distance 21507(?]
STORE_V 28(self)entity 0 4(?]
STORE_V 323(CHAN_VOICE) 2.0 7(?]
STORE_V 21505(?] 10(?]
STORE_V 21506(?] 13(?]
STORE_V 21507(?] 16(?]
CALL5 489(sound)sound()
ADDRESS 28(self)entity 0 147(use).use 21508(?]
play_music.qc : music_play_tune : statement 51
world.qc : LoadGame : statement 0
world.qc : StartFrame : statement 16
PR_ExecuteProgram: assignment to world entity
Host_Error: Program error
The idea was to play whichever tune was currently set. Clearly, I am not understanding qc here.
 In
#16 posted by aguirRe [213.101.73.100] on 2007/08/12 00:18:44
function music_play_tune in file play_music.qc, there's an entity propery ("use" I think) that you're assigning some value at statement 51.
This entity is world (= 0) at the reported occasion and this is not allowed, you may not change the world's properties.
It's probably an un-initialized entity variable that causes this issue. If I saw the corresponding QC code, it'd be easier to explain.
 Hehe
#17 posted by aguirRe [213.101.73.100] on 2007/08/12 00:21:15
Does this mean you're working on "This Onion II - The Sequel"? ;)
 AguirRe
#18 posted by Mike Woodham [81.158.239.232] on 2007/08/12 12:45:15
Does that mean I would have to intialise my music sounds in world.qc instead of calling the function play_music where they are usually precached? I can get around the "use" statement.
Oh, and according to my publicist, I have to give a "no comment" to your last question :-)
 From The
#19 posted by aguirRe [213.101.71.95] on 2007/08/12 13:18:58
debug output above, it's a
self.use = something
statement that's run when self == world which is not allowed. It's not the precaching that's at fault here.
Since the function is called from StartFrame initially, I'd assume that the world is indeed self at that point.
Music_play_tune is normally the think function for the play_music entity, you can't call that directly from another entity's (here: world) think code. The sound calls are also invalid then.
I'd guess you'll have to trigger the play_music entity's think function somehow from StartFrame.
 Thanks
#20 posted by Mike Woodham [81.158.239.232] on 2007/08/12 13:26:48
I'll play a little more with this to see if I can figure out a workaround.
 No, I Can't Figure It Out And I'm Going Round In Circles
#21 posted by Mike Woodham [81.158.239.232] on 2007/08/12 18:43:01
I thought I could set up a flag to say whether or not the music was playing at the time of the savegame (state = TRUE/FALSE). I know the piece of music playing by tracking it with a variable (cnt = 1/3/5/7, for 4 set pieces of music). I know both of these are saved in the savegame file (actually FALSE appears not to be saved), so presumably are loaded with loadgame.
But world.qc baulks at everything I try, usually with a 'types function and field not allowed'. So I cannot figure out how to either read the entitie's 'state' and 'cnt' and make use of them; or how to call the 'music_play_tune' function from world.qc.
Do you think I am trying to do the impossible or is it just that the logic is beyond me?
 This Might Not
#22 posted by aguirRe [213.101.69.144] on 2007/08/12 19:10:17
be a very good idea, but just to make the music_play_tune "callable" from StartFrame, you could assign a global entity var music_entity the value of self in play_music.
In StartFrame, you'd then just set music_entity.nextthink = time to make it execute as soon as possible. You should probably add a check for music_entity being not-world, i.e.:
if (music_entity)
music_entity.nextthink = time;
Maybe someone else can suggest a better way to actually achieve whatever you're trying to do.
 Btw
#23 posted by aguirRe [213.101.69.144] on 2007/08/12 19:13:50
savegame files don't contain vars that are 0 (= FALSE), so that might be why it's "not saved".
 I Don't Think It's Impossible
#24 posted by necros [99.244.15.189] on 2007/08/12 19:20:54
are you waiting long enough for all the entities to be spawned before you go searching for the music entity?
it should just be a matter of using a while loop to .chain through your entities until you find your music ent, checking the entity fields and then giving it a .nextthink and .think to call it's function.
music_play_tune likely has stuff with self in it, so that's why you'd need to do musicent.think = music_play_tune; instead of directly calling music_play_tune();.
or am i misunderstanding the problem?
 ?
#25 posted by necros [99.244.15.189] on 2007/08/12 19:26:34
savegame files don't contain vars that are 0 (= FALSE), so that might be why it's "not saved".
false = 0, and i'm pretty sure variables in quake are automatically initialized to 0 if they have no value, so technically, they are 'saved' whether it's actually in the save file or not.
 OK
#26 posted by Mike Woodham [81.158.239.232] on 2007/08/12 20:19:16
I didn't have a problem not seeing 'state = 0' in the savegame because I would have been looking for 'state = 1' to tell me if the music was playing at the time of the savegame.
No, my "problem" is that I cannot get a compile with any statement that uses something like 'musicent.think = music_play_tune;' in world.qc as I get an error of "Types function and field not allowed". I don't know how to copy one entity into another so that the world.qc can use it.
The reason I am having difficulty is that I am only an occaisional user of QC and don't really have enough depth of knowledge about the language or protocols in use. I get by, by reading other people's code and adapting it to my use or by plain old trial and error. But I don't think we have had music in Quake in the way in which I have implemented it, so nobody has yet written the code for me to adapt, and all my trials (Lord, soon be over) have ended in failure. Well, at least I'm consistent ;-)
If someone wants to take this on, I would have no problem passing my play-music code on. It's not a work of art but it does work.
And, "at the end of the day" I am only trying to tidy up something I have already finished.
 Didn't
#27 posted by aguirRe [213.101.69.165] on 2007/08/12 20:46:30
my suggestion work? To copy self in play_music, you just have a new global var
entity music_entity;
and assign self to it:
music_entity = self;
Then you set
music_entity.nextthink = time;
in e.g. StartFrame. You shouldn't set the think function, that's AFAIK already set in play_music.
You only make sure music_entity's think function will be run as soon as possible (you can't control exactly when, though).
 AguirRe
#28 posted by Mike Woodham [81.158.239.232] on 2007/08/12 22:54:23
No, it didn't work.
If I place the 'entity music_entity;' outside of world.qc I get a compile error "world.qc(359):error: unknown value 'music_entity'".
Line 359 is where 'music_entity.nextthink = time;' sits.
So I place it in world.qc immediately following the line, 'nosave float loadflag;'
I've added 'music_entity = self;' inside play_music, thinking that it needs to be there otherwise the new entity doesn't know who 'self' is.
Finally, 'music_entity.nextthink = time;' is in LoadGame, which is called from StartFrame.
Ah, but... I have just re-read everything and added '+ 3' to 'music_entity.nextthink = time;' and now it works. I had written a note in play_music that the sound will not play for one second - I read that somewhere but don't know why it is so.
Cool! Lookin' good!
Thanks.
Now I can get to work on checking which piece should play (or not). Watch this space!
Thanks again.
 Long Standing Bug?
#29 posted by Preach [81.153.30.11] on 2007/08/12 23:10:12
Is this, in the eyes of those present here, a bug? (and if so, one worth fixing?)
self.th_pain (attacker, take);
// nightmare mode monsters don't go into pain frames often
if (skill == 3)
self.pain_finished = time + 5;
You might expect that this piece of code makes sure that there is a minimum 5 seconds between each pain animation on nightmare, in the same way that self.pain_finished = time + 2; in the pain function puts a minimum of two seconds gap. But this isn't quite right.
You see, most pain functions only set pain_finished times once they've already concluded that pain_finished < time. Otherwise they'd constantly set pain_finished further into the future as long as the monster kept taking damage within the pain_finished period. Nightmare mode is, in effect, doing exactly that. You have to go 5 seconds without damaging the monster at all, rather than 5 seconds since you last caused it to go into pain.
So you end up with very odd behavior. If you pour nail after nail into an ogre, it'll only go into pain on the first one, but if you spread each nail 5 seconds apart, it will go into pain from every single one. Of course, the bug doesn't manifest itself that noticably, as 5 seconds is about enough time to kill any monster if you're focusing exclusively on it. Still, it might becomes more important with new high hp enemies, like in Quoth or the like. So, thoughts?
 Mike
#30 posted by aguirRe [213.101.74.142] on 2007/08/12 23:32:25
The reason you get a compile error is because of the order in progs.src file. Since world.qc is before play_music.qc, the compiler needs to know what it is before you can use it.
You can get around this kind of problem by declaring the global var in defs.qc or another file that's early in progs.src.
Or just declare the var in all files that you use it in. It'll just be one var anyway, as you can't have two global vars with the same name.
Good that it works now.
 Well
#31 posted by ijed [201.222.202.214] on 2007/08/12 23:51:39
In Quake2 the enemies never went into a pain animation in NM - they just ignored the shots, feeling no pain.
And it felt right, they are cyborgs. In Quake it is a bit strange that I can always count on a Vore (for example) yowling on the first hit, and have enough time in the animation to throw four grenades (1 to cause it to go into pain, three more to kill it) without reprisal.
Is it possible to add a randfloat value to the factor for pain - with the skill number deducting from this? (Yeah, just exhausted my coding knowledge).
What I'm suggesting is recoding the monster pain sequence, which can most likely cause alot of headaches, but I can see:
Create random Variable 1-10, deduct (skill level).
Is Variable higher than 5? If yes, play pain animation.
Which should give the engine less to think about for each particular monster currently in combat, since they're not all accumulating delays. This could be useful if, for example, the player is spraying nails into a horde of Vorelings.
It could also gives an organic feel to the enemies, making them less predictable and robotic.
 Sorta
#32 posted by Preach [81.153.30.11] on 2007/08/13 00:13:26
Some of the quake monsters already do that, for instance the pain decision code for the fiend is:
if (self.touch == Demon_JumpTouch)
return;
if (self.pain_finished > time)
return;
self.pain_finished = time + 1;
sound (self, CHAN_VOICE, "demon/dpain1.wav", 1, ATTN_NORM);
if (random()*200 > damage)
return; // didn't flinch
demon1_pain1 ();
So any hit of less that 200 damage has a chance of being ignored(apart from the sound). This tends to only be done for the tougher monsters though. I imagine that the vore always goes into pain because it's a more frail thing - although it does get a 3 second gap between attacks to compensate. The wizard gets a flinch value of 70 hp because if it does stop in pain, it's probably gonna die because the player can get a bead on it. Setting it that high gives it a good chance of ignoring nails. I don't think it's something that should be applied across the board though.
#33 posted by necros [99.244.15.189] on 2007/08/13 02:56:33
could make it take into account it's current health when determining whether to go into pain or not. then add a random time to it, and wait until that time expires to add more delay.
 Preach:
#34 posted by metlslime [71.202.113.29] on 2007/08/13 10:08:18
do you know if that "nosave" keyword is reliable? If i sit there and spam the save and load buttons, every once in a while the sound won't be playing after I load. If i then keep loading that one save file, it will be missing the sound 100% of the time. This suggests that something is wrong with that save file (like the loadflag variable is occasionally getting saved)
 Aghhhh!
#35 posted by metlslime [71.202.113.29] on 2007/08/13 10:37:15
now i'm finding that certain quakec changes are causing the level to not load, giving errors like
"couldn't spawn server maps/info_player_start.bsp"
"couldn't spawn server maps/samelevel.bsp"
almost as if strings in the progs are getting all jumbled up. Is frikgui27 a bad version to be using? Maybe it's got some bugs or something?
 Okay....
#36 posted by metlslime [71.202.113.29] on 2007/08/13 10:48:04
Anyway, i think i solved the issue; I guess i just needed to delay a bit before trying to play a sound. I guess sometimes if you try to play a sound too soon after starting a level or loading a save, the game just swallows it and you don't hear anything. I've noticed the same is true with printing messages to players in spawn functions.
As for the crazy errors in post 35, no idea what happened there but it's gone after making more changes.
 Yeah...
#37 posted by Preach [86.150.195.95] on 2007/08/13 11:53:07
I've had that string bug happen to me before, usually it goes away if you restart frikgui. Must be some kind of bug - if it keeps recurring you might want to use fteqcc instead, or mention it to frikac. I'm pretty sure that nosave is a reliable feature, although it sounds like you've fixed it anyway.
 Just To Close The Loop
#38 posted by Mike Woodham [81.158.239.232] on 2007/08/13 21:49:10
I put a flag in play_music set to true when music is playing and then read it in LoadGame. It works a treat.
Thanks.
 Loading DPM Model Files In Darkplaces
#39 posted by Zylyx [82.26.154.167] on 2007/12/10 13:35:48
Hi!
I posted thsi thread last week on the Inside3d forums, but I didn't get any replies, so I wnated to try my luck here, with you nice folks ;).
I'm having some trouble loading a DPM model for Darkplaces.
This is the code I have, minus the comments (added to weapons.qc)
//model precache (added at the beginning of the //file
precache_model("models/weapon...
//Set the weapon up in players viewport
if ((self.weapon == IT_SHOTGUN))
{
self.currentammo = self.ammo_shells;
self.weaponmodel = "models/weapons/ak/v_ak47.dpm...
self.weaponframe = FALSE;
self.items = (self.items | IT_SHELLS);
}
When I try this out, the model doesnt show up, and the console reports "cant find <tex name> for mesh <tex name>, using default grey_checkerboard" (or something along those lines)
I'm using the the test v_ak47.dpm model that came with the dpm model viewer utility.
The model directory is as follows:
mygame/models/weapons/ak/v_ak47.pm
Inside the ak directory I have the following files (including the textures that the engine reports it cant find, in TGA file format):
-v_ak47.pdm
-10 of the TGA textures corresponding to the model
I would really appreciate if someone could tell me how to load and display DPM models in darkplaces.
Thanx again in advance!
-Zylyx
 Hmm
#40 posted by Preach [81.152.233.94] on 2007/12/10 20:09:46
I don't really know much about dpm models, but I guess the first thing to check would be that the filenames are correct. Can you give a specific example of one of the paths? If they came ready named with the example file then it's unlikely to be the problem. Also, see if a shorter path helps, like putting the model in mygame/progs. Again it sounds unlikely but it's worth a shot.
Otherwise I'm gonna have to pass you on to another forum again. The best place to ask/check out would be the nexuis forums. Nexuis uses dpm for all it's models IIRC, so checking out their source code/mod to see how to do it might help. If even that fails you might post on their forums and hopefully a coder there can help.
#41 posted by Zylyx [82.26.150.253] on 2007/12/10 22:59:25
thnx!
My model directory code is as follows (in the "mygame" directory in the Quake directory, coz that where my progs.dat file get's called from for my mod):
precache_model("models/weapon...
and for the viewport setup:
self.weaponmodel = "models/weapons/ak/v_ak47.dpm...
I'll have a go at the Nexuiz forums, and I'll hopefully be able to get some info there.
 C++ Coding Help
#42 posted by necros [99.227.108.217] on 2008/03/30 08:14:01
i was wondering if someone could give me a hand with this...
basically, i wanted to create some kind of time system in a c++ game i'm working on. well, actually, i'm working on someone else's code, and currently, there's no sense of time at all, all timing is done by frames, which, to me anyway, seems like a Bad Thing.
ideally, i'd like to have a simple counter that starts at 0 and goes up in seconds like quake's 'time' float so that i can do things similar to self.nextthink = time + 5.2; (obviously, wouldn't be identical)
btw, i'm a c++ noob. ;)
 Necros:
#43 posted by metlslime [98.210.181.179] on 2008/03/30 11:04:43
you basically have a global time variable, and have the code update the value at the beginning of each frame. Probably, you'd do it in the main loop that runs your game. And the time you can get by using the appropriate system call, for example windows has one, SDL has its own, etc. See Sys_FloatTime() in the quake source for an example of how it's done.
Once you have a time variable that is available to any piece of code, you can do simple tests like: if (this.nextthink <= time) this.think();
 Or
#44 posted by megaman [84.63.76.38] on 2008/03/30 15:22:46
you hand the last frame time delta into all your updates
 Ioh
#45 posted by megaman [84.63.76.38] on 2008/03/30 15:24:24
and, upon reading metl's answer a bit more:
you'd also have the main game loop decide when to render a frame and when to update the gamestate (ie. seperate render() and update() methods).
 Also...
#46 posted by metlslime [98.210.181.179] on 2008/03/31 01:33:56
like megaman said, some functions (such as physics/movement) will want a "frametime" delta in addition or or instead of the global time. So you should have both be available.
 Well,
#47 posted by necros [99.227.108.217] on 2008/03/31 04:46:09
i found out that clock() returns what looks like a simple integer in msec starting at 0 when the game starts, so i'm using that.
but what is this frametime delta you are talking about? like the time between frames? cause i haven't a clue on how to calculate something like that.
i realise though, that i'll eventually need something like that. i changed the movement system to work with velocity vectors but i won't be able to accurately update positions without some way to tell how much time is taking place between frames.
 Necros:
#48 posted by metlslime [98.210.181.179] on 2008/03/31 05:48:52
"frametime" i.e. time "delta" i.e. time between frames is easily calculated: just currenttime - previoustime. It is useful for things like physics, for example:
entity.position += entity.velocity * frametime;
entity.velocity += entity.gravity * frametime;
note that the second line above is responsibly for jump height being framerate-dependant!
 My Understanding Is That Timing Is Messy Stuff
#49 posted by bear [127.255.255.255] on 2008/03/31 09:13:07
Basically there are a number of ways of getting the time and some of the timers that give you the data aren't that exact.
You should be able to find some good info in the http://www.gamedev.net forums and I think I remember seeing it discussed on the http://www.shmup-dev.com/ forums too in the past.
 Thanks :)
#50 posted by necros [99.227.108.217] on 2008/04/01 20:52:51
i think i've got it working, or at least, it seems to be.
every frame:
time_currentFrame = clock();
time_previousFrame = time_currentFrame;
time_frameDelta = (time_currentFrame - time_previousFrame) / 1000;
this gives me the fraction of time per second, and i have one function that does:
nextPosition = currentPosition + (velocity * time_frameDelta)
for all movements.
for actual velocity calculations, i capped that at 10 frames per second (every 100ms) to keep cpu usage down for needlessly precise velocities.
 Umm
#51 posted by bambuz [91.152.87.250] on 2008/04/02 02:23:49
you have to reverse the order of the first two lines?
 Yeah.
#52 posted by necros [99.227.108.217] on 2008/04/02 04:47:02
:P
 Function
#53 posted by necros [99.227.108.217] on 2008/04/04 22:11:32
how do you call a function outside of the class you're in atm? is it even possible?
i know you can do object->doThis() but that requires you to first find the pointer for that object, and then that function usually means you want to perform the function on that object...
i wanted to do the old style C thing where you just have a function you call from anywhere (like how T_Damage and T_RadiusDamage work in quakeC).
i'm kind of shit at this whole OO stuff, so am i going about this the totally wrong way?
 Make It A Static Method
#54 posted by czg [83.253.254.12] on 2008/04/04 22:25:30
#55 posted by Willem [24.199.192.130] on 2008/04/04 22:29:47
Or just place the function outside of any class definitions. Functions at the global level work the same as C functions.
 How?
#56 posted by necros [99.227.108.217] on 2008/04/05 00:10:17
how exactly do i make it static? or how do i declare the function outside a class definition? do i make a new cpp file or something or..?
 Static
#57 posted by bambuz [91.152.87.250] on 2008/04/05 01:02:27
is another modifier, kinda like private or public.
A static thing is something you can not make instances of, kinda.
So you have classes like Moose and you can make moose1, moose2 etc... But there can be a static method in the Moose class that is not particular to any moose1 or moose2 instance.
Like double Moose.mass_of_the_sun() which then returns a static constant that is always the same no matter what you do with the moose instances.
Kinda like that. Damn it's been too long since I've coded.
#58 posted by Willem [75.177.185.17] on 2008/04/05 12:23:49
necros
You're asking some pretty basic questions here in all honesty. Maybe reading a primer on C/C++ would be in order here.
 Yeah
#59 posted by BlackDog [58.7.102.12] on 2008/04/05 16:45:03
C++ is a painful and unforgiving language. If you dive right in, you're going to hit something hard and spiky.
I would go so far as suggesting a different language, if that's practical (pygame is pretty sweet). If it isn't, then you really need to read up on the basics.
#60 posted by Willem [75.177.185.17] on 2008/04/05 17:22:05
C++ is fine and gets far more flack than it deserves.
C# is far superior however. FAR superior.
I guess it would help to know what specifically you're trying to code and for what platform.
#61 posted by BlackDog [58.7.102.12] on 2008/04/05 18:10:42
C++ does get treated unfairly - google Erik Naggum's usenet rants sometime for a hilarious example - but I wouldn't call it "fine".
I don't really want to get into a flamewar over C++'s fucked up design: I'm just going to note that there are plenty of better languages to choose from.
#62 posted by necros [99.227.108.217] on 2008/04/05 23:14:37
at the same time, i always learnt best by doing. it's more frustrating, but it sticks better.
i've tried reading some lit on c++ but they annoy the hell out of me. i'd rather they just give me the syntax for it and give me a brief overview of how to put it in the code instead of going into all the long winded crap about stuff i never remember anyway.
otoh, the more i look at this code, the more i feel it needs to be rewritten almost from scratch. there are some things in here which i don't like at all.
ie: instead of treating ships and missiles as inheriting from the same class, they are completely different classes, and neither of them can 'see' the other.
ideally, i would have liked to override the basic 'update' function of a base entity class for specific ones (ie: for missiles and ships) and then be able to call it by just iterating down one big list of entities and calling the same function.
instead, i'm trying to hack my way around it by making a static function in a new class so that i can call it from inside one class that normally can't see the other class.
also, for, apparently no reason, missiles aren't instanciated objects, but are regular structs which is also causing other problems.
i'm going back to school next week, so i'll probably ask these questions to my instructors instead and see what they think...
 Heh
#63 posted by necros [99.227.108.217] on 2008/04/05 23:15:07
the title for the above post should have read "that may be true, but"
 If This Is Your First C++ Program...
#64 posted by mwh [118.92.130.30] on 2008/04/06 10:45:31
you should entirely expect to have to throw it away and start again.
(Or your 10th or 100th, to be fair: "build one to throw away" is a long standing slogan of software engineering).
#65 posted by Willem [75.177.185.17] on 2008/04/06 11:35:49
The problem is that hardly anyone ever does that. :) That's where horrible feature bloated abominations are born.
 Unarmed Ogre
#66 posted by ijed [190.20.122.225] on 2008/06/01 03:03:43
I've been stuggling to make an Ogre variant and I'm having troubles with ai/fight/ogreb.
Basically the fat cunt keeps trying to go to th_missile when he has no grenades (crash). I must be missing something obvious here but I don't know what.
The error returned is on his run sequence (has all his own functions) but if I replace his th_missile linking it to eg.melee then I'm ok, except it takes him ages to close the distance because he's swinging chainsaws about.
NULL function
Basically I don't want him to ever th_missile - but looking over ai and other monsters sheds no light - dogs / knights / fiends all use ai_run without problem.
I'm going in circles, it seems. If anyone can shed any light on this I'd appreciate it.
 C++ For C Programmers
#67 posted by inertia [24.164.67.55] on 2008/06/01 09:18:27
http://www.4p8.com/eric.brasseur/c...
I find that documents called "X For C Programmers" tend to hit the spot. This is mainly for necros.
 Ijed
#68 posted by Supa [70.226.31.207] on 2008/06/01 11:14:34
OgreCheckAttack sets their attack state to AS_MISSILE, which leads to ai_run calling ai_run_missile, which leads to self.th_missile.. which leads to the crash.
You'll need to comment out the monster_ogre/OgreCheckAttack lines in CheckAnyAttack (so they'll use CheckAttack instead) and make sure you don't give them a .th_missile, else CheckAttack will convienently call self.th_missile for you. :)
 An Ogre By Another Name
#69 posted by Preach [131.111.213.35] on 2008/06/01 11:39:17
Have you tried changing the classname from monster_ogre to, for example, monster_ogre_unarmed? I have a feeling that might make it work.
The reason why is hidden in fight.qc and ai.qc. fight.qc is the place to start, have a look at the two functions CheckAttack and OgreCheckAttack. The former is the generic function for any monster, and it's careful enough to check whether self.th_missile is set or not before deciding to make a missile attack. OgreCheckAttack is more presumptive, it knows that an ogre has a missile attack and so goes straight ahead with setting AS_MISSILE.
This is all quite deceptive, as OgreCheckAttack never explicitly uses th_missile - it just sets AS_MISSILE and lets ai.qc do the th_missile part. ai.qc is also the place to find out how the game chooses between CheckAttack and OgreCheckAttack. The function that decides is called CheckAnyAttack, and as you can see it decides based on the classname. So if you give your melee ogres a different classname then it should use CheckAttack only, which is safe to use without th_missile set.
The downside to this method is that, like with monster_ogre_marksman, your ogres will be able to infight with each other. So you could just make the check in CheckAnyAttack more stringent:
if (self.classname == "monster_ogre" && self.th_missile)
return OgreCheckAttack ();
For neatness of code, this way is probably better, as it means you can have a single spawnfunction for both grenadiers and berserkers. This is good code sharing, as any change you make to an ogre you probably want to make for both of them.
The third solution would be to modify OgreCheckAttack to properly check for th_missile, but I don't think that's the best way to go, it could end up complicated.
 Infighting
#70 posted by rj [86.1.160.132] on 2008/06/01 15:10:32
it's pretty easy to get around the infighting problem above when setting up a new classname. you just need to set up 'classes' (a la quoth) and modify the infighting code slightly
here's how i did it..
- add .string class; to the monster ai in defs.qc
- add self.class = "ogre"; to the different ogre classnames
- then in combat.qc under the '//react to damage' part, change this line:
if ( (self.flags & FL_MONSTER) && attacker != world)
to read this:
if ( (self.flags & FL_MONSTER) && (self.class != attacker.class) && attacker != world)
i think that was it. you can use it to group allied monster types or groups, either in the qc or simply by adding 'class' to any other monster entity (say if you had one particular battle where you wanted the monsters to focus on the player only)
 Excellent
#71 posted by ijed [190.20.65.61] on 2008/06/01 18:16:41
Thanks for all the answers, I should be able to close this issue sometime today.
I wanted to maintain the original ogres so made the new one a different classname - ogreb (berserk) already.
As to infighting, I'm thinking of modifying all Ogres to act the same as grunts. This might sound like a cheap dodge to not have to fix the issue, but I never did see the Ogres as particularly polite, and I'm a fan of chaos.
Thanks again.
 Argh
#72 posted by ijed [190.20.65.61] on 2008/06/01 18:24:37
Solved!
Turns out I was calling ogrecheckattack for the new ogres inside ai.
You live and learn.
 Monster Won't Get Killed
#73 posted by MadFox [84.26.60.178] on 2008/09/09 00:06:00
While coding for a new Q1 monster I made up a little qc where I placed all args like stand walk run etc.
Having the monster standing in game all looks well, untill I try to kill the moster.
Having two monsters in game there's always one that don't die.(?!)
I hear the kill sound but the monster just goes on turning into stone to my bullits.
I have the regular death subroutine.
http://members.home.nl/gimli/Imp.q...
#74 posted by necros [99.227.108.217] on 2008/09/09 00:27:45
lol...
if (random() < 0.5)
imp_die1 ();
these lines mean if random() (random number between 0 and 1) is smaller than 0.5, then it will play the animation sequence.
so half the time it will play the animation and half the time it won't.
 Well
#75 posted by MadFox [84.26.60.178] on 2008/09/09 00:44:57
cll me a fool, but what should I number 0.5?
 Right
#76 posted by MadFox [84.26.60.178] on 2008/09/09 00:52:05
if(me a fool)half the time < I ain't).
thanks for pointing me out.
 Impulse Command Queuing.
#77 posted by necros [99.227.108.217] on 2008/09/20 23:17:10
impulse command queuing.
is that handled by the engine?
in ImpulseCommands there's the if statement:
if (self.attack_finished > time)
{
self.impulse = 0;
return;
}
which seems to me to mean "if attack_finished is still ticking down, do not accept ANY impulses (set to 0) and then break out of the function altogether.
yet, when you attack with say the rocket launcher and then press 8, the impulse 8 command seems to get queued up and is executed after attack_finished is done.
is there something going on behind the scenes here? i'm actually trying to stop the queuing from happening, but can't really see even where it's happening.
 Hmm
#78 posted by Preach [86.146.19.8] on 2008/09/21 00:05:45
I don't see that line in the version posted at
http://www.inside3d.com/browse.php...
I'd probably say the place to look is in W_WeaponFrame, where any call to ImpulseCommands is skipped if self.attack_finished > time. If you really wanted to block impulses, then putting self.impulse = 0 just before that return should do the trick.
 Hm...
#79 posted by necros [99.227.108.217] on 2008/09/21 05:41:57
that's really odd. that means the if check in ImpulseCommands is completely redundant. it will never get to that point because an identical if check is done prior to even getting in that function. o.0
 Random Coding Oddities In Quake?
#80 posted by mwh [118.93.20.2] on 2008/09/21 09:40:41
Surely not!!
 Yeah, But
#81 posted by Preach [81.153.85.157] on 2008/09/21 11:39:38
Yeah, but that if() check isn't actually in the standard quake source code for ImpulseCommands, I don't know where you got it from : -p
 Fire And Switch?
#82 posted by Lardarse [62.31.165.111] on 2008/09/21 19:52:00
If you want to stop the queueing, then putting that the return in the if() in W_WeaponFrame would do it. If however, you want some impulses to always happen, then you need to put a separate check in, to happen before that check.
Of course, an even more intricate fix (which goes in the other direction from where you want to go) is to track what the impulses would make the weapon change to if you were not firing (setting .weapon appropriately, so the hud light shows what you will use next), and then switch to it when the player has stopped firing. Of course, there is another issue again, in that I don't think you can switch away while firing a nailgun, because the way they fire is handled differently is handled to other weapons.
 I Sorted It Out
#83 posted by necros [99.227.108.217] on 2008/09/21 19:56:38
i just moved the self.impulse = 0 to the end of W_WeaponFrame and removed the if check with the return. i have no idea where the if check in ImpulseCommands came from but i took it out as well and it works fine after re-arranging the way firing and impulsecommands was handled.
 And I Forgot To Mention A Couple Of Things
#84 posted by Lardarse [62.31.165.111] on 2008/09/21 20:00:06
You should probably use else if in ImpusleCommands(), so that it can go faster, May not make that much difference these days, but it's probably a good idea to anyway.
Even more of a timesaver, though, is skipping all of the impulse checks completely if self.impulse is 0. I don't actually know when impulse gets truncated to the lower 8 bits, but I think it's before the QC sees it (and possibly it only gets sent over the network as a byte), so you don't have to check for it being anything else that resolves to 0.
 Profile
#85 posted by Preach [81.153.85.157] on 2008/09/21 23:59:23
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
#86 posted by Lunaran [97.87.13.222] on 2008/09/22 03:25:26
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?
#87 posted by necros [99.227.108.217] on 2008/09/22 03:41:44
I assume the compiler converts frame names to integer offsets and the names of the frames themselves are irrelevant?
yup.
 And
#88 posted by ijed [216.241.20.2] on 2008/09/22 15:33:34
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
#89 posted by Lunaran [24.158.1.74] on 2008/09/22 16:34:06
and it didn't seem to mind
 Schnurps
#90 posted by Kinn [86.153.225.202] on 2008/09/22 21:44:44
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
#91 posted by Lunaran [97.87.13.222] on 2008/09/23 01:31:31
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
#92 posted by ijed [190.20.69.159] on 2008/09/23 05:58:25
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
#93 posted by Lardarse [62.31.165.111] on 2008/09/23 09:13:01
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
#94 posted by Preach [217.44.87.165] on 2008/09/23 10:39:19
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
#95 posted by Lardarse [62.31.165.111] on 2008/09/23 12:01:25
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
#96 posted by Lardarse [62.31.165.111] on 2008/09/23 12:04:56
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
#97 posted by Preach [217.44.87.165] on 2008/09/23 12:15:23
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"
#98 posted by Lardarse [62.31.165.111] on 2008/09/23 15:58:43
Selective based on monster type, though.
 Gnurps
#99 posted by Kinn [86.153.225.202] on 2008/09/24 22:35:12
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
#100 posted by Willem [75.177.185.17] on 2008/09/26 12:38:01
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
#101 posted by Lardarse [62.31.165.111] on 2008/09/26 13:50:17
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
#102 posted by Preach [86.148.9.7] on 2008/09/26 14:18:22
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.
#103 posted by Willem [75.177.185.17] on 2008/09/26 14:29:04
Ahh alright. Thanks!
 Removing Entities And It's Effects On Other Things
#104 posted by necros [99.227.108.217] on 2008/10/05 23:00:36
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
#105 posted by Preach [81.153.28.98] on 2008/10/05 23:41:48
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...
#106 posted by Willem [75.177.185.17] on 2008/10/05 23:45:03
"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
#107 posted by necros [99.227.108.217] on 2008/10/06 01:03:51
9_9
 Mind Melting Code
#108 posted by Preach [81.153.24.252] on 2008/10/09 15:54:07
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/~chapter...
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
#109 posted by MadFox [84.26.60.178] on 2008/10/10 08:07:10
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?
 I'm Guessing
#110 posted by necros [99.227.108.217] on 2008/10/10 09:23:20
you mean func_rotate_train and not func_train?
in which case, you need the oft-posted czg rotation tutorial which, ironicall, i don't have the link to. :P
 Append
#111 posted by necros [99.227.108.217] on 2008/10/10 09:24:17
that's to say, the tutorial explains how to get collision on your rotaters. it's kind of wierd and annoying.
 Thanks
#112 posted by MadFox [84.26.60.178] on 2008/10/10 10:00:53
necros, the turning goes al right.
it is just that funny behaviour that when I play the map falendoor of custents I can stand on the func_train and I rotate in eyesight.
when I transponse the map coordinates of that func_rotate_train to my own the train isn't solid anymore. (?!)
 Oops
#113 posted by MadFox [84.26.60.178] on 2008/10/10 21:18:46
forgot to include func_movewall. Now it's solid.
There's an odd point where the func_rotate suddenly behaves like a counter-turn in the opposite. Can't fly the thing without Doom patents.
 Just
#114 posted by ijed [216.241.20.2] on 2008/10/10 22:27:29
To throw a spanner in the works -
Would it be possible to have the plane as a model, with all it's takeoff animation (turning etc.) done in Qme?
Or would the size of the animation break the model - look in the wrong direction and the game assumes you can't see it because it's in a different visleaf.
 There's Something Worse That What You Mentioned
#115 posted by necros [99.227.108.217] on 2008/10/10 22:40:10
about doing it that way.
the way that vertex coordinate info is stored is completely relative.
this means that no matter how big or small your model is, the only thing important is how far from the origin all the vertices are.
this has an impact on the resolution of the model.
in quake, you could make a miniscule model of a bolt and it'll look completely fine.
but if you made a huge wall with the same bolt in the middle, the bolt would be messed up.
this is because the huge wall caused the scale of the vertex 'snap' to be larger.
if you made a plane model that was fully animated to fly around in, say an area of 1024x1024, your plane's vertices would probably be so low resolution as to not even be able to make out what it was.
as an example, look at the amount of vertex dancing on the vermis model. compare it to something like the fish model (which hardly moves) to get a good idea of how it impacts vertex position.
 Another Way To Look At It.
#116 posted by necros [99.227.108.217] on 2008/10/10 22:42:33
imagine that a vertex in a model can only be on a grid of 256x256 units. it can rest on any even number from 0 to 255.
but that this grid can be stretched in all directions.
so if your model was 1024 units tall, it's vertices could, internally, only snap to a 256 tall grid.
that is to say that in the game world, the model's vertices would fit on every 4 grid units, instead of every 1.
#117 posted by Willem [75.177.185.17] on 2008/10/11 01:25:55
Ahh right. I was going to call you out on that resolution thing but then I remembered Carmack's 256x256x256 cube compression stuff. Yes, quite the ugly issue.
 Ijed
#118 posted by MadFox [84.26.60.178] on 2008/10/11 03:21:41
this is aa far as I have come with the the doombird and custents.
Coding a mechtech is somewhat harder, but would be better.
http://members.home.nl/gimli/dbird...
 Ha!
#119 posted by ijed [190.20.93.238] on 2008/10/11 15:06:11
That's pretty fun. The collision seems to go a bit nuts and just do its own thing, but even so.
Maybe some Qc to add grenade style smoke to the jets?
 As To The Model Thing
#120 posted by ijed [190.20.115.33] on 2008/10/11 19:01:42
So to do it with a model would require a func_modeltrain of some sort then, so it wouldn't just dissolve into a mess.
That explains why all the monsters seem to have Parkinsons in their idle animations though.
#121 posted by Willem [75.177.185.17] on 2008/10/11 20:15:10
The larger ones anyway. The Shambler has way more error in his verts than, say, a soldier. The rest of the jittering comes from the fact that, I believe, Quake only supports integer locations for vertices.
 I'm
#122 posted by ijed [190.20.72.178] on 2008/10/11 20:37:40
Used to engine side vertex interpolation so don't usually notice it so much, except when looking at the models in raw state, as it were.
 Swung
#123 posted by MadFox [84.26.60.178] on 2008/10/11 21:27:50
custents support the func_rotation and the func_rotate_train.
I'm trying to understand these options to get a better flyer.
that strange swing don't seem related to the func triggers.
Haven't found out where it comes from.
Reason that in custents a part of the func_rotate is hided.
 Armor
#124 posted by MadFox [84.26.60.178] on 2008/10/14 10:35:53
I succeeded to create a health bowl and give it a health strength , by adding a health function in the qc.
I thought giving it armor points would be that easy. But it isn't.
There are only three IT_ARMOR types.
By substituring one I get my extra powerup object of ten armor points.
But not as alike the health paks I can only take one at the time,
the rest stays non solid as if I have taken a full armor .
 To Clarify
#125 posted by Preach [86.153.44.107] on 2008/10/14 12:49:50
I want to check what you're trying to do here. You would like an item which gives a boost of, 10 points of armour to a player who picks it up. So far, you've managed to make an item which gives the player 10 points of armour if they pick one up, but then subsequent ones will only top the player back up to 10, and if they are already at 10, then it doesn't increase.
To me, that sounds exactly like what would happen if you rewrote the green armour to have 10 armour points rather that 100. Have I read your aim and current result right?
 Yes
#126 posted by MadFox [84.26.60.178] on 2008/10/14 12:59:29
I trie to reproduce the armor powerup with skulls, like in Doom
 Items
#127 posted by MadFox [84.26.60.178] on 2008/10/14 13:05:41
I was rewriting in the items.qc
+---------------------------------...
void() armor_touch =
{
local float type, value, bit;
if (other.health <= 0)
return;
if (other.classname != "player")
return;
if (self.classname == "item_armor1")
{
type = 0.3;
value = 100;
bit = IT_ARMOR1;
}
if (self.classname == "item_armor2")
{
type = 0.6;
value = 150;
bit = IT_ARMOR2;
}
if (self.classname == "item_armorInv")
{
type = 0.8;
value = 200;
bit = IT_ARMOR3;
}
if (self.classname == "item_skull")
{
type = 0.1;
value = 10;
bit = IT_ARMOR1;
}
if (other.armortype*other.armorvalue >= type*value)
return;
+---------------------------------...
};
void() item_skull =
{
self.touch = armor_touch;
precache_model ("maps/skull.bsp");
setmodel (self, "maps/skull.bsp");
self.skin = 0;
setsize (self, '-16 -16 0', '16 16 56');
StartItem ();
};
+---------------------------------...
 It's The Last 2 Lines
#128 posted by Lunaran [97.87.13.222] on 2008/10/14 15:35:18
they skip the pickup entirely if the armor you're touching is weaker than what you have. you need to add a special exception there for skulls that add 10 armor and keep the preexisting armortype.
 Additionally
#129 posted by Lardarse [62.31.165.111] on 2008/10/14 15:52:25
You should check for the player having no armor, and if this is the case, set their armortype to green (0.3 and IT_ARMOR1)
You may also want to set a limit for both health and armor shards. Probably at 250 or soemthing like that. I don;t know if the 3 numbers at the bottom are limited to 8 bits, but even if they're not, you might want to not let them get out of hand...
 Addition
#130 posted by Preach [86.153.44.107] on 2008/10/14 17:54:08
There's another problem with this code, which is the way armour and health packs differ in increasing stats. A health pack adds on a certain amount of health to the amount you started with. Armour always sets the value to the same amount, regardless of how much you had, it's not adding anything on. Code like "add on 150 points to his armour, but then if the total is more than 150, cap it there" would be redundant.
So you're going to have to write something more complicated.
 This
#131 posted by ijed [216.241.20.2] on 2008/10/14 19:41:56
http://qexpo.tastyspleen.net/booth...
Might help some, it's Dr Shadowborg's revised health system tutorial.
 Thanks For Your Explain
#132 posted by MadFox [84.26.60.178] on 2008/10/14 23:46:40
I'm no coder, so the fact I get any outcome is a wonder to me.
With health it was easy, adding a new b_model with args and the code run.
With this armor code I can let the strength deplenish untill it reaches under 10 before it powers up.
But I can change to any value, only 10 counts.
Eventually I can understand the options you mention,
but I miss the ability to write them down.
 Adding New Weapons
#133 posted by Killa [70.126.212.240] on 2008/10/15 10:54:58
hey, was wanting to know if you guys knew of a good and easily understood tutorial for adding a new weapon, that also shows me how to add it to where its usable when added to a FGD "not just when you have a shot gun and enough ammo -.-)
 Ok, More Detailed Explanation
#134 posted by Preach [86.150.194.206] on 2008/10/15 11:07:41
I think the best way to do it would be to write an entirely separate touch function for your new item. It needs to:
* Do the usual checks that the other entity is a player who is not dead.
* Deal with the 4 cases: player has red/yellow/green/no armour.
* Add the correct amount to the armour total.
I'd do something like this:
void() armorboost_touch =
{
local float maxarmor;
if (other.health <= 0)
return;
if (other.classname != "player")
return; //usual checks
if(other.items & IT_ARMOR3) //Player has red armor
maxarmor = 200;
else if (other.items & IT_ARMOR2) //Yellow
maxarmor = 150;
else //deal with green and none at same time
{
other.armortype = 0.3;
other.items = other.items | IT_ARMOR1;
maxarmor = 100;
}
other.armorvalue = other.armorvalue + 10; //add it on
if(other.armorvalue > maxarmor)//if we're over max
other.armorvalue = maxarmor;//set to max
self.solid = SOLID_NOT;
self.model = string_null;
if (deathmatch == 1)
self.nextthink = time + 20;
self.think = SUB_regen;
sprint(other, "You got armor\n");
sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM);
stuffcmd (other, "bf\n");
activator = other;
SUB_UseTargets();//fire all targets/killtargets
};
I've not tested it in game, but it should work well enough. Remember to change the line in item_skull to
self.touch = armorboost_touch;
 Hey
#135 posted by MadFox [84.26.60.178] on 2008/10/15 12:07:39
that's another qup of tea Preach.
I already reminded myself to be glad with a strike of luck and leave it that way.
And yes indeed, the powerups go clearly from 0 to 100. Never thought it was possible.
Thanks!
 LIT File Format?
#136 posted by Willem [75.177.185.17] on 2008/10/23 13:44:21
Can someone point me towards what the LIT file format looks like? I've spent 20 minutes Googling with nothing to show for it. Is it documented somewhere?
#137 posted by Spirit [213.39.156.179] on 2008/10/23 16:21:47
According to QIP LordHavoc created the format so just try getting hold of him in #darkplaces on Anynet (IRC). Otherwise you could read through the TomLite or Tyrlite sources (Hmap probably too), you seem to be able to grasp code (unlike me).
 I Emailed Him A Couple Of Weeks Ago...
#138 posted by RickyT23 [86.139.126.162] on 2008/10/23 16:57:59
He helped me out! :-)
 Willem
#139 posted by Lardarse [62.31.165.111] on 2008/10/23 17:05:45
From dpextensions.qc:
//DP_LITSUPPORT
//idea: LordHavoc
//darkplaces implementation: LordHavoc
//description:
//indicates this engine loads .lit files for any quake1 format .bsp files it loads to enhance maps with colored lighting.
//implementation description: these files begin with the header QLIT followed by version number 1 (as little endian 32bit), the rest of the file is a replacement lightmaps lump, except being 3x as large as the lightmaps lump of the map it matches up with (and yes the between-lightmap padding is expanded 3x to keep this consistent), so the lightmap offset in each surface is simply multiplied by 3 during loading to properly index the lit data, and the lit file is loaded instead of the lightmap lump, other renderer changes are needed to display these of course... see the litsupport.zip sample code (almost a tutorial) at http://icculus.org/twilight/darkpl... for more information.
#140 posted by Willem [24.199.192.130] on 2008/10/23 17:21:47
Excellent! Thanks, that should give me what I need...
 Coding Tip Of The Day
#141 posted by Preach [81.152.235.254] on 2008/12/03 01:15:00
Don't expect them daily though, this is just my aggravation of the moment:
if(1)
dprint("1: went down the TRUE branch\n");
else
dprint("1: went down the FALSE branch\n");
This will, as you would expect, go down the TRUE branch. The value in the if() is non-zero, which is true in boolean logic.
if('1 0 0')
dprint("2: went down the TRUE branch\n");
else
dprint("2: went down the FALSE branch\n");
This will also go down the TRUE branch, it looks like any non zero vector counts as true.
if('0 1 0')
dprint("3: went down the TRUE branch\n");
else
dprint("3: went down the FALSE branch\n");
This will go down the FALSE branch! QC only checks the first component of a vector when evaluating boolean logic on them. This is a bit of an oversight, and you should be careful. If you want to properly check if a vector is non-zero, you must do it explicitly as
if(self.angles != '0 0 0')
OK, that's the one that bugged me for tonight, venting over. But while I'm posting about QC and if() logic, here's another related bug that can catch people out:
self.noise = "";
if(self.noise)
dprint("4: went down the TRUE branch\n");
else
dprint("4: went down the FALSE branch\n");
This one goes down the TRUE branch. Why it does is a bit technical, it's because if(self.noise) is checking whether the pointer which self.noise stores is non-zero. The string constant "" is not the same thing as a null pointer, it is a string comprised solely of a null terminator, which actually gets allocated somewhere and so the pointer has a non-zero value.
There are two ways around this. One is to properly null the string pointer, like this:
self.noise = string_null;
if(self.noise)
dprint("5: went down the TRUE branch\n");
else
dprint("5: went down the FALSE branch\n");
This one evaluates to false.
Alternatively you can put
if(self.noise != "")
This will go down the false branch in either of the cases self.noise = ""; or self.noise = string_null.
#142 posted by Willem [75.177.185.17] on 2008/12/03 01:45:54
"This will go down the FALSE branch! QC only checks the first component of a vector when evaluating boolean logic on them."
I'd venture a guess that when it sees a string maybe it's just doing an atoi call on it, which would stop at the first space? I dunno.
 Assembling A Vector
#143 posted by Preach [81.152.235.254] on 2008/12/03 11:00:49
Looking at the assembler output, it looks like every time a vector is passed as a parameter to something, the instruction looks like:
STOREP_V VEC_ORIGIN_x, temp_0;
So it seems to be passing just the first component of the vector. What happens then is that the engine knows that this is a vector, and that the other two components will be in the next two entries in memory. Any operation with _V on the end does this.
The problem arises when you get to the IF operations. There isn't a separate case for vector, or indeed for any types. Every memory location which gets passed by the assembler to IF is cast to int, and then checked to see if it's non-zero.
It wouldn't be easy to fix this on the engine side, because you'd need to read what type of temporary was being stored, so that you could distinguish between
if(self.angles)
and
if(self.angles_x)
because the IF operation would look the same. You'd need to look back a few operations at least, which is basically a no-go.
On the other hand, it wouldn't be hard to write a compiler which always substitutes
if(vector != '0 0 0') for if(vector).
FTEQCC already offers a similar fix for
if(string)
It isn't on by default, because some of the engine extensions which add string concatenation etc. require actual tests for null strings instead of empty ones.
 Mdl Versus Sprite
#144 posted by MadFox [84.26.168.11] on 2008/12/14 12:38:08
Well, I managed to get a sprite in Quake, but I was wondering how to add it to a monster's subroutine.
Simple fact, quake sprites are mostly just one model frame. Except for the orgue which is the only one with his grenade. If I'm looking to the qc I can't find nothing about sprites, so I consume it is engine wise.
When I change the model laser.mdl of the enforcer with a sprite I see only the first frame.
How do I add a sprite to a monster's subroutine?
Is it something you add to the statement of the laser.mdl of the enforcer?
 In Hope To Help
#145 posted by meTch [69.183.41.240] on 2008/12/14 17:15:32
void() s_explode1 = [0, s_explode2] {};
void() s_explode2 = [1, s_explode3] {};
void() s_explode3 = [2, s_explode4] {};
void() s_explode4 = [3, s_explode5] {};
void() s_explode5 = [4, s_explode6] {};
void() s_explode6 = [5, SUB_Remove] {};
void() BecomeExplosion =
i think you have to make a multi-image sprite, like the explosion?
and deal it out above like so, and give it setmodel "ursprite.spr"
or umm, heh <8*
 Well
#146 posted by MadFox [84.26.168.11] on 2008/12/17 01:16:23
For creating a sprite it could work, but not when I try to make it happen in a monster's subroutine like the enforcer.
If I add something at the qc point launchlaser, I first need to declare the sprite frames. And as the enforcer already has it frames declared qc matches out.
 Madfox
#147 posted by necros [99.227.108.217] on 2008/12/17 01:54:27
If I add something at the qc point launchlaser, I first need to declare the sprite frames.
no, you do not. like i said before, just put a number where the $frame macro would be.
 Is It Still Necessary To Use Quakeworld Progs?
#148 posted by necros [99.227.108.217] on 2008/12/22 20:49:09
are there qw clients that can just read the normal netquake progs?
#149 posted by Spirit [213.39.186.184] on 2008/12/23 10:22:11
zQuake can do, both as client and server (so you can serve a "netquake" progs with zquake and have other qw clients connect to it).
FTE can do too.
ezQuake I am not sure, from what I know the singleplayer is broken.
 Zquake Sounds Promising
#150 posted by necros [99.227.108.217] on 2008/12/23 20:07:13
except i have the same problem i have with a lot of other clients with zquake...
the screen is half-filled with giant red pixels (using +gl_clear 1) like someone got decapitated right next to my screen. in other gl engines, like aguire's, the pixels are grey because i set the gl_clear colour to grey.
fuhquake has this problem, but ezquake does not though. (fitzquake doesn't have the problem either).
does anyone know what that's about?
#151 posted by MadFox [84.26.168.11] on 2009/01/07 19:44:06
I made a sprite of the Shambler's bold, this in order to use it in a mod for temperary bold flashes.
Now I'm sofar I can import the sprite in Quake.
I need the sprite to turn on and off.
I don't know if it's possible or I'm missing the logic to understand.
I used this code as bold.qc
$frame 0 1 2 3
void() bold_stand1 =[ $0, bold_stand2 ] {};
void() bold_stand2 =[ $1, bold_stand3 ] {};
void() bold_stand3 =[ $2, bold_stand4 ] {};
void() bold_stand4 =[ $3, bold_stand1 ] {};
void() model_bold =
{
if (self.style >= 32)
{
self.use = light_use;
if (self.spawnflags & START_OFF)
lightstyle(self.style, "a");
else
lightstyle(self.style, "m");
}
precache_model ("progs/bo1.spr");
precache_sound ("ambience/buzz1.wav");
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_NONE;
setmodel (self, "progs/bo1.spr");
setsize (self, '16 16 16', '24 24 24');
self.think = bold_stand1;
self.nextthink = time + 0.1;
ambientsound (self.origin, "ambience/buzz1.wav", 0.5, ATTN_STATIC);
};
Now I have bold sprite that constantly flashes.
I used the selfuse = light_use; to turn it on or off but it don't seem to work.
Is there a way to turn it on or of with a trigger?
 Well
#152 posted by MadFox [84.26.168.11] on 2009/01/13 23:43:52
the logic is probably it is a static entity that can't be moved because it is a MOVETYPE_NONE
Ohter question: I'm coding LostSoul from Doom to Quake. If I give it the normal routines like stand,walk,pain,die and give it a bite like the dog for attack with self.th.melee
all goes right so, I should be glad I can archve it.
The distance the monster reaches while biting to the player is too wide. I don't know how to make it closer.
Then I decided to try the jump scene of the dog. Adding it to the qc worked, but there are some strange sidekicks.
Because LostSoul is a flying monster this doesn't compare with a walkmonster. The lostsoul make a giant jump, but it wants to land onground. And because it is a flying monster this is strange sight.
The monster bumps undergound, than jumps up 64 units, aims to the player, moves 64 units again and then returns to its jump session.
Also the chance of hitting the player is almost harmfull.
I know this is becuase I switch a flymonster with walkmonster code, but would it be possible?
A lostSoul that excellerates its move in air, without the bounce of a walkmonster.
#153 posted by necros [99.227.108.217] on 2009/01/14 00:11:59
to get a flyer to fly toward the player:
take the origin of the enemy (player)
subtract from monster's origin
normalize vector
set monster's velocity with vector * speed you want it to move at.
local vector vec
vec = normalize(self.enemy.origin - self.origin);
self.velocity = vec * 1000;
you'll need to make a touch function to set self.velocity to 0 otherwise, things will get messed up when the monster tries to move from AI.
 Great!
#154 posted by MadFox [84.26.168.11] on 2009/01/15 06:47:31
I had to make some prototyping to get my SolJumpTouch run, but now it is a fast Soul!
Thanks!
 MDL Question (cross Posted From Inside3D...)
#155 posted by Willem [24.199.192.130] on 2009/01/16 15:25:57
OK, so inside the MDL file format there is support for simple frames and group frames. I'm trying to get it straight in my head which is good for what.
It looks like things like the walltorch use group frames. Is this so the engine can start animating them on a random frame? I don't see anything in the QuakeC that uses this information so it must be engine side.
Am I thinking about this the right way? Do group frames have other uses?
 Groupies
#156 posted by Preach [86.156.59.51] on 2009/01/16 19:57:21
Basically a grouped frame is a way to make a looped animation on the client side, so the animation changes frame without any input from the server/qc. The advantages are that it uses no network traffic once it starts, and can be applied to a static entity.
From a QC point of view, the entire framegroup just looks like a single frame. Suppose you had a monster with:
regular frames run1 .... run6
a framegroup [idle1 ... idle40]
more regular frames attack1 .... attack8 etc
Then to get at the run frames you'd set
self.frame = 0...self.frame = 5
To play the idle animation you'd set
self.frame = 6
And to get the attack frames
self.frame = 7...self.frame = 14
One of the disadvantages of this arrangement is that the QC has no way of reading where the animation has reached at a given time. This would make it hard to sync a sound with the animation for example.
Open question here...when you have a QC command like self.frame = 6 on a model with a framegroup at frame 6 and NO random flag set, does that command start the animation from the beginning? I suspect that if the frame was already 6 it wouldn't work, as no network update is sent out for a frame remaining the same. If you could get that to work you could do some nice long idle sequences on a monster using them, without running out of frames for important things
So basically the use of them is to cut down on the amount of QC you need to make a simple looping animation. This is vital for static entities which don't have any qc interaction once created(hence static), and can make other things easier.
Another open question paired with a technical fact...If you poke around in the mdl specs like Willem has been doing, you'll notice framegroups come with a value to control the duration of each frame in the group. From my memory of messing with these thing, I could only ever get it to obey the duration of the first frame in the group, the rest of them just automatically followed that. Is that a long standing bug/just Carmack forgetting to add (frameindex*framesize) to the pointer to retrieve it?
#157 posted by Willem [24.199.192.130] on 2009/01/16 20:54:27
I thought that the animations ran at 10Hz or something and that was that. I know the spec has that array of floats that are claimed to be timings, but nobody ever talks about them so I sort of assumed that they were always set to even intervals or just plain didn't work.
All the fire and stuff in Quake seems to animate at the same speed, at least to the naked eye.
#158 posted by necros [99.227.108.217] on 2009/01/16 21:12:21
i could never get the framegroup animation speed thing to work from within qme3.1 :S
#159 posted by Willem [24.199.192.130] on 2009/01/16 21:17:27
Could someone with engine familiarity confirm or deny if that code works or not? I'll bet Preach is correct that either it's being ignored intentionally or Carmack has a bug in the engine where he doesn't read the values in correctly and never noticed.
 Well
#160 posted by Preach [86.156.59.51] on 2009/01/17 00:24:13
Admittedly when I managed to get different rate framegroups to work I was using darkplaces, so it's possible that regular engines have no support for it and darkplaces had slightly broken support for it, I will go away and check this weekend(hopefully).
As for the 10Hz thing, it isn't hardwired into quake that things animate at that speed, but there are 4 reasons why it is almost exclusively used.
1: It's the rate at which all the existing quake models are animated. So anything new usually follows that pattern.
2: The QC shorthand way to define animation functions in quake like
void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();};
implicitly assume a 10Hz rate, they insert a self.nextthink = time + 0.1; at the start of the function. You could change that in the function body:
void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();self.nextthink = time + 0.05;};
but it's awkward, and also the original nextthink remains before it.
(I think a nice fix would be a way to specify $fps in the $frame macros, and then have the compiler insert the correct nextthink time when expanding animation functions. But I've barely managed to get FTEQCC to compile, so it may never happen.)
3. It is the framerate which the game will guarantee - if the game is being so slow that it can't achieve 10 fps then it will slow down game time. You might set thinks every 0.05 seconds and have them still occur every 0.1 second in game. Of course, in the real world quake is 10 years old and so never runs slow, but it's a nice theoretical.
4. The other limits of the mdl format start to show once you start animating at higher framerates. 256 frames only gives you 12 seconds of animation at 20fps, and you have to cram all of a monster's death/pain/attacks into that length of time. Often doable, but pretty tight. Also, the compression of the vertex coordinates means you get rapidly diminishing returns on increasing the framerate. The result wobbles might be worse than keeping 10fps and letting the engine interpolate at higher precision.
I don't think any of those things is enough to completely kill working at higher framerates though. What I'd like to see is some specified use of it. Keep 10 fps on unimportant/low motion animations like idle poses. But when you have a big sweeping motion like a sword slashing, increase the framerate for that to 20. This gets around both of the problems of point 4 there.
If you do run into problems where you have too many frames to increase the framerate, it may be possible to abuse framegroups to provide the interpolation you need. This does rely on two things - that you can set custom framegroup fps, and that when you enter a non-random framegroup from a different frame, it starts animating from the beginning.
#161 posted by Willem [71.70.208.255] on 2009/01/17 12:25:26
OK, so in further poking around am I right to assume that an MDL will either be using simple frames OR group frames? There's no mix and match going on, correct?
 Combined Force
#162 posted by Preach [86.153.44.164] on 2009/01/17 20:16:06
You can in fact have both in the same model, it is possible to combine the zombie crucifiction frames into one framegroup and make crucified zombies static(but then you don't get the random duration of frames or the sounds, so it's not a PRO TIP or anything). Doing that doesn't affect the rest of the frames in the model, so regular zombies work as normal.
Also, changing the rate is inconsistant: in glquake engines the duration is taken from the first frame, in winquake it reads each frame duration correctly. There's an argument there that the bug should be fixed in glquake engines there, but best practice is probably to use the same rate for the entire framegroup, and set it in each frame so that winquake handles it the smae.
The bad news is that when rendering a framegroup which isn't random, the frame to render is just calculated from the world clock, rather than the time since that framegroup was set (That is assuming I've read the relevant code correctly). This means you can't abuse framegroups to provide increased interpolation with the same number of frames on the QC side.
#163 posted by Willem [71.70.208.255] on 2009/01/17 20:29:31
Ahh OK. Well, good to know then. i'll need to handle it all gracefully then, no shortcuts. :) Thanks...
 Framegroups
#164 posted by MadFox [84.26.168.11] on 2009/01/17 22:58:15
When I tried to combine the skull of Lostsoul with the flame2.mdl I was overtaken by the fact that Quark407 in model import reakts:
can't handle framegroups.
Is there a way to break this options so I can manipulate the modelframes?
#165 posted by necros [99.227.108.217] on 2009/01/18 00:34:33
open flame2.mdl in qme, remove frame groups, save, open in quark.
#166 posted by MadFox [84.26.168.11] on 2009/01/19 13:15:00
framegroups, does this mean
the parts of vertices within a frame like the different flame parts,
or different frames?
The only way to avoid this "no framegroups supported" for me is by exporting a flame2.dxf and import it back as a new model. Then I can reimport the multiple frames, and quark can import it.
#167 posted by Willem [24.199.192.130] on 2009/01/19 14:59:11
Frame groups, as I've come to learn over the weekend, are basically a collection of frames that are special in that they will animate autonomously without having to tell the server about it. Each frame is a complete animation frame, just like the simple frames in other models.
 So
#168 posted by MadFox [84.26.168.11] on 2009/01/19 17:43:16
the different parts in one frame make quark tell it is a framegroup.
I know nothing about servers and how it works in quake.I just wondered why one frame left in qmle after deleting all other frames in flame2.mdl still errored quark with framegroups.
It is the same error I had after decomposing a death frame to divided parts. After saving they were all melted together again.
 Animated Skins
#169 posted by Willem [71.70.208.255] on 2009/01/20 21:25:26
It looks like MDLs have support for animated skins but no MDL that I ever open has one. Can anyone recall a model that had an animated skin on it in a mod or anywhere else?
 No,
#170 posted by necros [99.227.108.217] on 2009/01/20 21:54:38
but i believe you are refering to skingroups?
i've heard people talk about that but never found out how to do it, myself.
#171 posted by Willem [71.70.208.255] on 2009/01/20 22:03:11
Sorry, yes, skin groups. I see them in the spec and was just wondering if they've ever actually been used. I guess once these tools are further along I'll just test them out and see what they are.
 Have You Watched The Cutscenes
#172 posted by RickyT23 [82.20.37.149] on 2009/01/20 22:05:06
on Nehahra? They have like facial animation as in the mouth texture has two frames. Also isn't there some blinking?
#173 posted by Willem [71.70.208.255] on 2009/01/20 22:08:24
Well, how are these skingroups used? I'm going to assume that it's like the frame groups - some sort of automated animation system. If that's the case, I guess they could have had a moving mouth with blinking thrown in here and there to make it work (no lip syncing at all but that's probably a given in the Quake engine).
 Ricky
#174 posted by necros [99.227.108.217] on 2009/01/20 22:20:09
i believe the talking and blinking (not sure about the blinking, that may have just been random?) was handled via impulse commands and that the 'actor' players would do that themselves. or it just occured to me, they maybe did it in post in a demo editor.
 Hmm
#175 posted by HeadThump [4.136.90.159] on 2009/01/20 22:51:59
just to clarify, do you mean something like painskins (sweeeet)?
http://www.inside3d.com/showtutori...
Or, an animated concurrent effect?
 I Like Painskins!
#176 posted by RickyT23 [82.20.37.149] on 2009/01/20 23:12:17
i managed to do that once when i was about 15.
Bring back painskins!
#177 posted by Willem [71.70.208.255] on 2009/01/20 23:38:21
HT
No, that tutorial looks like it's just bumping up to another skin index. The skingroup stuff looks like an auto animate deal.
Preach! :)
 *We* Seem To Be Going Round Twice...
#178 posted by Preach [81.155.192.36] on 2009/01/21 00:16:01
You can auto animate skins, it's pretty much the same deal as the frames, you can designate a collection of skins as a single skingroup, they autoanimate according to timings set in the model. I have no idea if the random flag works on them, or if the same glquake bug for duration exists. If you want an example of a model which does it, dig in the quoth pak1.pak for a model called pickup.mdl. The last two skins, which are for the health and megahealth, have such animations to make the lights blink.
Necros: how you do it in qme is arrange the skins in order, then right click on the first one and select "Append Skingroup To Previous Skingroup". Repeat until all the skins in the animation are combined, and then save the model again.
 Skingroups
#179 posted by metlslime [67.180.230.20] on 2009/01/21 00:19:55
i'm pretty sure these are the analog to framegroups in models and sprites, basically client-side looped animation.
based on the engine code, it looks like glquake has pretty half-assed support for skin groups, for example every skin is given 4 pointers to textures, and almost all the time the 4 pointers are to the same texture, but when loading a skingroup it gives them unique pointers. If you have more than 4 skinframes, it drop some.
So effectively you can only have 1, 2, or 4 skinframes in a group in glquake. If you have 3, it will animate 1, 2, 3, 1, 1, 2, 3, 1... and if you have 5+, it will animate (e.g.) 5, 2, 3, 4, 5, 2, 3, 4...
Also, not sure if the model format supports intervals for skingroups, but glquake clearly only does 10Hz animation for them.
My guess is there's one model somewhere in the mission packs using this feature, and that required them to add this minimum of support to glquake when they wrote it.
 Mother Of God
#180 posted by Preach [81.155.192.36] on 2009/01/21 00:52:14
Christ metlslime, pull the curtain back again, that's hideous!
Informative though, cheers really.
#181 posted by Willem [71.70.208.255] on 2009/01/21 01:01:30
Haha, horrible! Well, good to know that supporting that can go at the bottom of the priority list at least. :P
 Well
#182 posted by ijed [216.241.20.2] on 2009/01/22 16:24:15
Blinking health items at least.
Possibly some really shitty animated soul sphere swirling is possible . . . in four frames.
Can't think of a decent monster application - glowing eyes?
#183 posted by Willem [24.199.192.130] on 2009/01/22 16:27:54
"Blinking health items at least. "
Huh, that's true. OK, I guess that'll be my test case for loading skin groups then.
#184 posted by Willem [24.199.192.130] on 2009/01/22 16:29:00
"Can't think of a decent monster application - glowing eyes?"
I think many cool ideas could be done there. Glowing demon eyes, blinking lights on enforcer armor, blinking eyes, etc. Could be neat.
 Pain Skins!!!
#185 posted by RickyT23 [81.157.18.236] on 2009/01/22 16:31:57
pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!pain skins!!!
#186 posted by Willem [24.199.192.130] on 2009/01/22 16:34:21
To be honest, pain skins don't really excite me in Quake because how often is a monster in pain? You see the monster and it's generally dead within seconds. Would you even see the pain skin kick in?
 Its Just A Daft Thing I Wanna See Really
#187 posted by RickyT23 [81.157.18.236] on 2009/01/22 16:58:51
Monsters are all a bit bruised/bloody anyway, but brown grunt stood up, 2 seconds later red and bloodied grunt on floor. I mean I know that the pain skins would be non-locational, but for monster like a Shambler or a Vore you could have three or four increments of pain, relative to HP, and it would show how close that Shambler you've been picking away at with an SG or SSG for five minutes is to death!
I just think it looks cool. I also wanna see a mod with pain skins and zombie-gib blood splats dribbling down walls from when mosters are shot.
I know its meant to be easy in Quake C, theres a tutorial on Inside3d. Maybe this will be my first Quake C project . . .
(I had it working once when I was about 15, but I cant remember how it worked now, and haven't tried yet)
#188 posted by Willem [24.199.192.130] on 2009/01/22 17:11:35
Dead skins might be a decent idea. The corpse on the ground could be all fucked up and bloody. That might make more sense...
 Ok, Pain Skins In 5 Minutes
#189 posted by Preach [86.148.15.145] on 2009/01/22 20:01:52
Lets say we work with the grunt, and give him a second skin which is bloody and beaten up. Now open up soldier.qc and look for the function
army_pain
Right at the bottom of this function, add
if(self.health < 15) //if we are below half health
self.skin = 1;//turn on the pain skin
This will work a lot of the time, but there's a chance that the grunt goes from above 30hp to dead without going into pain. Also, if the grunt is dead, we should check if he gets gibbed, and set the skin back to 0 if so, because the grunt head only has 1 skin. So find army_die
and put
self.skin = 0;
just after
if (self.health < -35)
{
Then put
self.skin = 1;
after the closing } bracket of that gib section of the function.
You can do a similar thing with pretty much any monster, find the pain function and use that to add the pain skin. Remember that some monsters don't automatically go into pain animations, and may return near the top of the function. This means that you have to think about where to add the line. You could put it right at the top, so that it always gets checked. Or you might decide that the skin should only change if the monster goes into pain, and so put it at the bottom.
 Pain Skins
#190 posted by necros [99.227.108.217] on 2009/01/22 21:17:09
could have a practical purpose...
assuming your were consistent and always used pain skins for, let's arbitrarily decide 50% hp.
you could make like a monster that 'heals' wounded monsters if they are below 50%. you'd have a visual indicator for what's happening and it could be a cool new mechanic.
well, maybe something more complex would be in order like 'heals the monster to 90% of maximum health.' so each time it heals, the max health goes down and you could put in a check where it wouldn't be able to heal if the current max health is less than half of the actual max health.
#191 posted by Willem [71.70.208.255] on 2009/01/23 01:37:41
Oh duhr ... I can't use the health packs as a test case for skin groups in MDLs because those aren't MDLs (they're BSPs). Heh. Oh well...
 After Giving It Some Thought
#192 posted by HeadThump [4.136.90.99] on 2009/01/23 08:55:59
I think I know what Willem's next project is: Iron Man Arena, and he needs the effects on the player models to simulate the repulsor beam socket, and the jet flames coming from the boots. Imagine hovering around firing missiles and repulsor beams at one another with Twig or Gyro2 physics to both impede and improve our navigation. Oh, this is going to be sweet.
Am I close?
 Dead Skins
#193 posted by ijed [216.241.20.2] on 2009/01/23 13:27:21
Is interesting - pain skins doesn't really do it for me since most monsters are bloody anyway, and shooting a fiend in the back for some bloody mess to appear on, say, it's face is counter to what you're trying to achieve with the effect.
You could paint a handful of skins per monster and have them directional, but that misses the point of multiple damages - you'd only be able to have one active skins since you can't apply decals.
Dead skins is more interesting idea - you have the creature vomiting blood and caked in dirt. Problem is it started bloody, will anyone notice?
You could go whole hog and have a Mancubi style death - for example the Shambler's stomach could rupture when he dies, but then it probably wouldn't look very good with a single frame change and no 3d animation.
Healing enemies is good, like a Q2 medic form, but in order to show that monsters are injured I'd prefer to have them bleeding; particles dripping from them.
#194 posted by Willem [24.199.192.130] on 2009/01/23 15:05:16
"Dead skins is more interesting idea - you have the creature vomiting blood and caked in dirt. Problem is it started bloody, will anyone notice?"
On some you would. You would definitely notice a change in a shambler, enforcer or grunt. Creatures like ogres would be tougher but it might still be worthwhile.
 Pins Skins Pain Skins Pain Skins
#195 posted by RickyT23 [81.157.18.236] on 2009/01/23 15:17:40
monster <70 %
Some more blood around the chest and a little on the back, face and arms, some dripping down upper legs
monster <30 %
Big hole in chest, large exit wounds on back (i.e. gored up) more drippind down arms and legs
monster dead
Some blood particles flowing from the monsters midrift and maybe neck/face
monster -howevermuch it might be %
Gibbed!
It makes sense, you know it does. In a game so abstract that you can gib monsters in the style you can already, it would only add pleasure to my experience :D
 Dead
#196 posted by necros [99.227.108.217] on 2009/01/23 19:25:43
You could go whole hog and have a Mancubi style death - for example the Shambler's stomach could rupture when he dies, but then it probably wouldn't look very good with a single frame change and no 3d animation.
for the zdoom md2 models, they did seperate models for the monster deaths. the 3d mancubus death looks pretty good, as does the cacodemon.
i think if someone wanted to take the time, they could revamp the old death animations in that same sort of style for quake with new models.
 Willem And Bsp
#197 posted by Preach [81.153.25.42] on 2009/01/23 19:29:05
There's a mdl version of the health box (and indeed all the ammo pickups at the same time) included in quoth, which would be suitable for your testing. You'll have to extract it from pak1.pak, and it's called pickup.mdl.
#198 posted by Willem [24.199.192.130] on 2009/01/23 19:40:26
Sweet, will do! Thanks for the heads up, Preach.
#199 posted by Willem [24.199.192.130] on 2009/01/23 19:41:25
Oh, and it just occurred to me ... so THAT'S how you supported rotated pickups then. Neat!
 Rotated Pickups
#200 posted by Preach [81.153.25.42] on 2009/01/23 21:40:09
You can rotate pickups in their regular quake form. The only problem is that the bounding box doesn't change when you do that, which is compounded by the fact that the ammo boxes rotate about one of their corners, and not their centre. If you rotate them 180 degrees, the model entirely escapes the bounding box!
The quoth fix is just to put the bounding box in the right place relative to where the model get rotated to. The advantage of using the .mdl version of the ammo is that it saves you up to 10 model precaches, which can be helpful if you're pushing the limit(Stark Monstrosity showcases this feature).
The side effect of the .mdl version is that the crate is lit according to the ground below, rather than the baked in light levels of the regular ones. In order to stop crates getting lost in the dark, we a: disabled it by default and b: added small fullbright markings to all of the ammo. The shells and nails had these already, so it was natural to extend the idea to the other ammo.
 I Drew The Line At Cutting Out Monster Head Gibs
#201 posted by RickyT23 [82.20.37.149] on 2009/01/23 21:52:51
you can cut those out too.
I did however use the combined ammo .mdl flag at the same time as the one preach described. I like the fact that you add the two flags together, seems like a smart system.
 Heheh
#202 posted by Preach [81.153.25.42] on 2009/01/23 22:43:52
If it's a smart system, it was invented by a smarter person than myself. It is in fact the same system that spawnflags use - the spawnflag for "not in easy" is 256, "not in medium" is 512, and so "not in easy or medium" is the sum of the two.
In both cases, the values you have to set are chosen to by successive powers of 2( 1, 2 , 4, 8, 16, 32...)
These numbers are chosen because the sum of the first n-1 numbers is always 1 less than the nth number. That way, the sum of any subset of them is unique, and any number you chose corresponds to some combination of them.
There's also a connection with how binary numbers are stored as integers. So if you know about that, you can think of the nth 0 or 1 digit in the binary representation being on or off according to whether the nth option has been selected.
 Preach
#203 posted by ijed [190.20.66.157] on 2009/01/27 01:18:29
Did seeting them fullbright look bad?
 $frame Question
#204 posted by Willem [71.70.208.255] on 2009/01/27 14:08:24
OK, so I'm trying to get MDLEd fully functional and I have questions about QuakeC and MDL files...
Do you have to have a "$frame blah1 blah2 etc" for every animation frame in the MDL?
Do they have to match the internal structure of the MDL (do the frames have to be stored in the same order as the $frame lines in your QC file)?
What I'm getting at is this ... will I have to allow the user to have full control over the ordering of the frames in the MDL file or can I safely/automatically sort them alphabetically? Does it make a difference?
 Afaik
#205 posted by necros [99.227.108.217] on 2009/01/27 19:36:20
the $frame walk1 walk2 at the beginning just macros those strings to a number, and that number starts at 0 on the first $frame declaration and increments by 1.
so walk1 actually means frame 0, so frame 0 needs to be your walk1 frame.
in other words, the order of those $frame declarations are important and so can't be re-ordered without re-ordering the frames too.
 Fullbright$ And $frames
#206 posted by Preach [81.153.24.36] on 2009/01/27 19:41:58
ijed: as far as I know, there is no standard way to make a .mdl fullbright. It's just one of the differences in the way the two types of model get rendered, bsp have their own lighting info, and mdl use the map's light levels. I do think that medpacks looks pretty cool in .mdl format when placed in dark corners, it makes the lights blinking away on them really stand out.
Willem: You must at least make sure that you don't alter the order in which the frames occur without giving the user control over it. The actual names given to the frames in the .mdl aren't used by the engine/qc. It relies entirely on the enumeration of them in the order that they occur.
The $frame macros are simply a way of defining recognisable constant names for the numbers 0....n in a quick manner which only lasts for one qc file. The qc compiler has no knowledge of what the actual model file contains, so it just substitutes a frame number for each instance of $stand4 with a 3 - assuming that $stand4 is the 4th $frame that it encounters in the header.
So if your program loaded in ogre.mdl, reordered the frames into alphabetical order and then saved them again, the animations would no longer match up, so that would be bad. As long as you ensured that frames were exported in the same order they were imported, you would probably get away with it. But I suspect it would still be a desired feature...
(As a possible side note, people making qc files don't have to define the $frame things for every frame, they can just use the numerical indexes, or a mixture of the two. But just using the numbers can mean a lot of code has to be changed if you extend your first animation loop from 6 to 8 frames, so it's not encouraged to do that for anything with multiple sequences)
#207 posted by Willem [24.199.192.130] on 2009/01/27 21:08:07
"You must at least make sure that you don't alter the order in which the frames occur without giving the user control over it. The actual names given to the frames in the .mdl aren't used by the engine/qc. It relies entirely on the enumeration of them in the order that they occur. "
Beautiful. OK, that settles that then! Thanks.
 Hmm
#208 posted by nonentity [87.194.146.225] on 2009/01/28 07:38:41
Hey coding thread.
Very sorry about this, never post in here since I'm not a coder, so tend to skim read and leave you guys to your scary quote text fun ;p
Just wanted to bump stuff above the flame thread again (I suggest anyone else reading this does same and avoids bumping again pls ;)
Kiss
 MDL Hell...
#209 posted by Willem [71.70.208.255] on 2009/02/05 14:08:57
Man, this file format is fairly jacked up.
Anyway... Quick question:
Is the "onseam" stuff entirely necessary for texture coordinates or is that some sort of concession that was made to allow for easy skin creation back in the day? If I store all texture coordinates as full 0-1 values and set onseam to zero for them all, will Quake throw a fit?
 Onseam Bowling
#210 posted by Preach [86.150.195.44] on 2009/02/05 17:39:00
When making new skins, it's no longer really necessary. If you're trying to do anything more complicated that 2-sided planar then you pretty much have to ignore it, and just eat the extra vertices on the model. The md3tomdl converter does exactly this.
However, it's very important that you keep them for existing models, as it tells you which vertices need to be duplicated on the skin. I think if your program didn't preserve the original quake models like that, people would get confused to say the least.
If you changed models to duplicate vertices which were onseam, and then removed the onseam flag, there could problems in other editors. For example, if you saved one like that, then loaded it and saved it with QMe, then the vertex normals along the seams would be altered compared to the original, which might make the seam more obvious.
#211 posted by Willem [24.199.192.130] on 2009/02/05 18:18:16
Wow, so the whole purpose behind it was to save a vert or three in the mdl file?
 Hmm
#212 posted by nonentity [87.194.146.225] on 2009/02/05 18:21:39
Bear in mind we're talking the first full 3D fps here.
What we now view as insane levels of optimisation was fairly necessary back then...
#213 posted by Willem [24.199.192.130] on 2009/02/05 18:25:21
I question some of the stuff, actually. Carmack did some crazy machinations in places to save very minimal amounts of space.
 Ijed
#214 posted by gb [89.27.229.62] on 2009/02/05 19:13:49
We need to think about skin animations. Blinking lights on enforcers are neat, and I have a couple other cases where this could be useful.
Apart from that. Do the additional mouth/face textures often seen on id skins have something to do with this?
Were they meant for animation purposes?
#215 posted by Willem [24.199.192.130] on 2009/02/05 19:17:13
I think those are for the head gibs.
 Gib Faces
#216 posted by onetruepurple [79.191.236.214] on 2009/02/05 19:53:17
Would be a perfect addition for death skins (if they get implemented somewhere)
 Maybe
#217 posted by Preach [86.150.195.44] on 2009/02/05 20:00:32
I'd guess the whole onseam system was created because at the time it seemed like an unintuitive system to have vertices occupying the same position in the model, but connected to different triangles - when they are supposed to be part of the same surface in the model. The onseam method was a fix to the problem "how do I display the same vertex in two places on the skin?". It's just unfortunate that the solution pretty much ties you to the front/back skins that quake used.
Although I usually advocate the detached vertex solution, detaching things like that can cause problems. For example when calculating the vertex normals at that vertex, you probably want to average all the surfaces meeting at the split vertices, and give that same vector to all the vertices. This is a lot harder to do once you've split the vertices. In fact, if an unrelated vertex just happens to collide then you're scuppered...
Thankfully, .mdl uses precomputed vertex normals, so as long as you're converting from a format that supports merged vertices with split UVs then you can probably get sensible smoothing groups. Also, worrying about smoothing groups isn't the most pressing concern with quake models.
Interestingly enough, I don't know that onseam actually saves memory over all, when you consider how much wasted texture space there is as a result of using it. On models with lots of frames it's probably beneficial as the extra vertices would be recorded on all those frames. But for weapon models and other simple models, onseam probably doesn't make up for the waste.
#218 posted by gb [89.27.229.62] on 2009/02/05 20:19:43
I think headgibs use their own models, which use their own skins.
 I'm Aware Of That
#219 posted by onetruepurple [79.191.236.214] on 2009/02/05 20:29:18
How difficult would it be to copypaste the faces from the head gib skins onto body skins? (I'm no modeller so I can't tell)
 Yeah
#220 posted by ijed [190.20.113.58] on 2009/02/06 00:14:34
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.
#221 posted by gb [89.27.229.62] on 2009/02/06 00:24:48
I was replying to Willem about the additional face parts etc. on id1 skins. Those are pretty weird.
 So Was I
#222 posted by ijed [190.20.113.58] on 2009/02/06 02:03:42
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....
#223 posted by metlslime [64.175.155.252] on 2009/02/06 05:09:37
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!
#224 posted by Preach [86.150.195.44] on 2009/02/06 10:07:26
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
#225 posted by spy [92.47.190.20] on 2009/02/06 10:43:02
is it possible?
i'd appreciate any help
#226 posted by Spirit [80.171.52.172] on 2009/02/06 12:20:07
Sure, there were some mods with railguns I think.
 Ahem
#227 posted by Spirit [80.171.52.172] on 2009/02/06 12:21:03
And I just remembered I once successfully completed this tutorial: http://www.inside3d.com/showtutori...
#228 posted by Trinca [194.65.24.228] on 2009/02/06 12:28:32
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
#229 posted by Trinca [194.65.24.228] on 2009/02/06 12:47:58
zomggg are you the spy from the old Parboil board?
hehe
 Quakec Question...
#230 posted by metlslime [24.130.223.174] on 2009/02/06 12:57:59
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
#231 posted by spy [92.47.190.20] on 2009/02/06 13:18:01
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
#232 posted by spy [92.47.190.20] on 2009/02/06 13:19:18
i think i'm drukn again
 (in)Visible
#233 posted by Preach [86.150.195.44] on 2009/02/06 15:30:46
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...
#234 posted by metlslime [24.130.223.174] on 2009/02/07 11:17:45
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
#235 posted by MadFox [84.26.168.11] on 2009/02/07 17:52:32
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/spect...
 Can Be Shot But Doesn't Collide With Walking?
#236 posted by Willem [71.70.208.255] on 2009/02/13 21:52:05
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...
#237 posted by Preach [217.44.219.227] on 2009/02/14 01:59:02
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...
#238 posted by Preach [217.44.219.227] on 2009/02/14 02:59:57
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.
#239 posted by Willem [71.70.208.255] on 2009/02/14 11:14:18
Preach, you are fucking awesome. :) Thanks man, I'm off to digest all of that.
#240 posted by Willem [71.70.208.255] on 2009/02/14 12:59:11
Absolutely beautiful, that all worked. Thanks again!
 Yeah
#241 posted by ijed [216.241.20.2] on 2009/02/18 14:05:06
That's very nice, thanks for the explanations.
 Bit Of A Backtrack
#242 posted by Preach [86.160.143.42] on 2009/02/19 22:17:00
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.
#243 posted by Willem [71.70.208.255] on 2009/02/19 22:27:00
Are optimizing compilers really even necessary? I mean, has Quake ever been slow when fed reasonably coded QuakeC?
 Possibly
#244 posted by Preach [86.160.143.42] on 2009/02/19 23:52:12
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!
 Tracebox In QC
#245 posted by Preach [81.153.29.95] on 2009/03/07 14:31:16
Nobody asked for it, but here it is anyway: A way to perform a tracebox, similar to traceline but with a full sized object, without engine extensions. Caveats: The emulation of a trace is not perfect; it can throw a false negative in the case where the trace travels through a small but non-zero vertical distance. The test has been designed to prevent false positives. In addition, it's quite expensive in QC operations, although look to the previous post to see whether you should care about that.
"Your mother's a tracer!"
So, the key to being able to trace is, strangely enough, flying monsters. Most of the box-tracing which the engine performs is during physics updates. This makes them unsuitable for testing traces "on-demand", since you have to give control back to the engine first. However, monster movement is performed in discreet steps, moving instantly from one point to another using the walkmove function. Flying monsters have the additional advantage of not being bound to the ground, so we create an entity with FL_MONSTER and FL_FLY set to perform our trace.
The carrot and the stick
It's important to understand exactly what causes a flying monster to change altitude. Every time walkmove is called, the moving monsters decides if it is at the right height, too low, or too high. Accordingly, it attempts to trace a move straight forwards, forwards and up by 8 units, or forwards and down by 8 units. It makes the decision based on the height of its enemy relative to itself. So to control which way our monster goes, we need an enemy entity whose height we can set. This is quite like the carrot on a stick you wave in front of a donkey to guide it onwards, and I shall refer to it as the "carrot". Since our donkey can fly, it will be named "pegasus".
Running the course
The first problem is that we can only climb 8 units at a time. The solution is to call walkmove repeatedly, dividing up the trace up into segments, where each segment has a z component of 8. Since it is unlikely that the trace will have a height exactly divisible by 8, we need to perform an initial run which stops less than 8 units away from the target height, then reset the origin of pegasus so that it lies on the path of the trace, 8 units from the target height. Full marks for whoever spots the tricky boundary case of this method.
Preach's Pro Tip: You can use this trick of calling walkmove more than once to get regular flying monsters to ascend/decend more rapidly!
Jumping the fences
The next problem is what happens when the movement of the pegasus gets blocked. What few people realise about walkmove is that if the trace is blocked, the monster doesn't move at all - I personally imagined it would move the monster as far as it could before being blocked. But if the movement with vertical motion is blocked, the code does attempt a move with no vertical motion before giving up. This could create false positives easily. To make sure that pegasus is properly jumping the hurdles, check that the height of the pegasus changes from one walkmove to the next. This is the simplest test, as it doesn't matter if the trace is going up or down. Of course, you shouldn't do this if the trace is truly flat!
The final straight
A big problem arises if you want a trace which isn't flat, but rises/falls less than 8 units - a shorter distance than a single walkmove. This is the corner case astute readers may have caught earlier. It might be tempting to give yourself a "run-up", to trace from a position behind the desired origin, through the desired start to the finish, so that this extended trace has the right height, and accept the false negatives that the longer trace might cause. The problem is that this might actually cause false positives!
If the pegasus starts within a solid part of the bsp (or possibly other object), then it seems to skip through without colliding. As traces get closer to 0 height, the pegasus takes a longer and longer run-up until it is exceedingly likely it will be outside the level. So instead, the best solution I could come up with for this case is: Increase the height of the pegasus by the desired height, adding it to the maximum height if the trace goes up, and the bottom if the trace goes down. Then perform a horizontal trace. If you look at the 2-D side-on view of the trace, this is effectively taking the rhombus which would be the exact trace, and replacing it with the smallest rectangle which could enclose it.
 Tracebox In QC II
#246 posted by Preach [81.153.29.95] on 2009/03/07 14:35:13
You can lead a horse to water...
Through the steps in the previous post, we've built a reliable tracebox which reports few false positives and no false negatives. So it's time for a little nicety - stopping splash noises. The splash noise occurs when the pegasus crosses a water boundary, but since the pegasus is an imaginary beast, no noise should occur. To avoid it, we test for water by performing a regular traceline from start to end. If trace_inwater i set, then we add 1000 to the height of the trace_start and trace_end, then subtract 1000 from the mins and maxs of the bounding box(thus the box remains in the correct place. Although you could construct a map where a splash would still occur, I think it's unlikely to arise in actual maps.
Photo finish
And that's it. I'm sure I can hear you saying "That's all very well in theory Preach, but it sounds like a bitch to actually implement!" Fear not, for here is a qc file with just such an implementation. In this version, the carrot and pegasus are globally allocated entities, which reduces the overhead of spawning them each time you want to use the function. This could be changed if entities are at more of a premium than function cost.
http://www.btinternet.com/~chapter...
As a final warning, remember that the trace results are invalid if the tracebox starts outside of the level. When determining this, you have to account for the 3 hull sizes which quake supports. Work out which hull the tracebox fits to, and fit THAT inside the level. This almost always means that you need a player sized space!
 Bloody Hell
#247 posted by Lardarse [62.31.165.111] on 2009/04/20 14:07:52
(You missed a trick with the post icon, btw...)
All we need now is tracetoss, and we have a much more dangerous AI waiting to happen for several monsters...
#248 posted by necros [99.227.133.158] on 2009/04/20 19:59:44
you could try to approximate a toss path with a while loop. maybe in just 25% increments as you don't need a ton of precision.
 Adapting For AI
#249 posted by Preach [86.147.249.85] on 2009/04/20 20:18:35
You can use a similar, but much simpler trick for use with a monster, which would allow it to predict if it can navigate to a certain spot in the future. You again spawn an entity, and we'll call it the pegasus here for ease of comparison. Give the pegasus the same movetype, size and origin as the monster, and make the owner of pegasus the monster.
Then just perform some short distance walkmoves with the pegasus - best to do 4-5 each of a distance around the average walk speed of the monster. Then just see if they succeed or fail. The easiest way is just to see if the walkmoves return true, but you might instead want to test if the pegasus is within a certain distance of a target spot after each step.
You can also substitute walkmove with movetogoal, which attempts to navigate around obstacles. Then the question you're asking is more "will the monster be able to get near the goal if he is given a second to wander about?". I remember using this when coding the scripted death sequence for the final Travail boss, to ensure he could actually walk to a spot where the sequence would make sense(and sneakily teleport him if he was stuck!).
You can actually do this without the need for a proxy entity like the pegasus. Just save the original origin of your monster, and then walkmove it as many times as you need to test whatever it's planning. Once you've decided what the best navigational plan is, reset the monster's origin back to where it started.
(If I have the time tonight, I'll do a little post about making ogres shoot at angles without cheating by changing the overall velocity of the grenades. I'll also put tracetoss for grenades into that.)
#250 posted by Lardarse [62.31.165.111] on 2009/04/20 23:57:52
I was more thinking for fiends, so that they won't jump at you if they can't reach you.
 Ok, Some Thoughts
#251 posted by Preach [86.147.249.85] on 2009/04/21 01:27:05
The way a projectile moves in quake can be described by the increment in it's position and velocity per frame.
1. new_velocity = old_velocity + frametime * '0 0 -800'(assuming that sv_gravity is still 800).
2. new_position = old_position + frametime * new_velocity
(Maths people may recognise this as the Euler method - it's applied to each section separately though)
So if you want to trace a path up until the first contact with something, then you just need something that iterates traceline in this fashion until one collides. Here's me writing a qc function straight onto func:
void tracetoss(vector posa, vector vel, entity ignore, float nomonsters, float inc)
{
local vector posb;
local float startz;
if(inc <= 0)
inc = frametime;
startz = posa_z;
//cut off if it falls too far, like out of level
while(startz - posa_z < 1024)
{
vel = vel + inc * '0 0 -800';//lazy way to do gravity
posb = posa + vel * inc;
traceline(posa, posb, nomonsters, ignore);
if(trace_fraction != 1)
return;
vel = vel + inc * '0 0 -800';
posa = posb + vel * inc;
traceline(posb, posa, nomonsters, ignore);
if(trace_fraction != 1)
return;
}
}
Notice that you can specify the increment you want to use, with the default being the current frametime. Also, the duplicated code inside the loop is somewhere between double buffering and loop unrolling, and is solely for optimizing what could be an intensive loop. The idea is to look at trace_endpos to find where you hit. The maximum height could be made a function parameter, I was trying to keep the function simple.
Similarly, the proper way to do the gravity would be
local vector g;
g = '0 0 -1' * cvar("sv_gravity");
OUTSIDE OF THE LOOP
To make this simulate grenades best, use nomonsters = 2, this makes the trace more generous with collisions in the same way that flymissile does.
"But I didn't want grenades!" I hear you cry. What can we do about fiends? Well, you need to do something similar to this, but using tracebox. Now, the first thing I would recommend is not to just call tracebox as a function, because that might just cause you to exceed the runaway loop counter. Instead you should duplicate the qc-side tracebox code, and incorporate this new loop into that, so that you're only setting the size of the pegasus once, letting it maintain it's origin from one step to the next etc...
The other trick that I would recommend is to pick the time increment you use very carefully. In fact, I'd recommend that you pick the time increment so that at every step the z_difference you require is exactly 8 units, so that it can be done in a single call to walkmove. You have to swap the order that you do things in for that to work:
FIRST trace the new position based on the old velocity(using the old velocity to calculate the timestep)
THEN update the old velocity with gravity based on that timestep.
So the timestep you seek is abs(8 / old_velocity_z). Check for vel_z = 0 if you like. One final thing to remember is that when the sign of old_velocity_z changes from positive to negative (if it does) then you need to move the carrot from being waaaay above the pegasus to waaaay below it!
Now, here comes the gotcha. What do you do once you've performed the trace? If you hit something, you somehow need to decide whether that was the player or the world, and qc-tracebox can't do that (it's hard being a quake monster and therefore entirely blind, isn't it?). You also have to remember your ending position doesn't include the last step of the trace, as that one failed! The best suggestion I can think of right now is:
* Once you have your end position where you hit something, work out how far the player is from you ignoring the z position
* Try to tracebox horizontally towards the player to end up 64 units away from them (using the distance just calculated), again ignoring the z axis.
* Once that tracebox is ended, check if you're within, say 128 units. If so then the jump probably hits. Otherwise it probably misses.
Some of those numbers may need tweaking. I was basing it on the maximum seperation between a touching fiend and player being ~64 units (corner to corner ignoring z axis). Again, you want to trace as close as you can get, without actually colliding with the player, because then the trace fails - like going bust in blackjack. You could also split the horizontal trace into a few smaller steps, which might protect you from going bust.
And I totally ran out of time(and characters) to do anything about ogre aiming. Maybe tomorrow...
 Gremlin
#252 posted by MadFox [84.26.168.11] on 2009/05/11 00:20:07
I was trying to add a gremlin to the standard qcc and of course I got a lot of errors.
When I finally had cleaned them up I started the game and reached:
progs.dat system vars have been modified.
progsdefs.h is out of date.
It might sound familiar, but is there a solution?
 Defs.qc
#253 posted by Preach [81.152.234.175] on 2009/05/11 00:37:22
Check the changes you made to defs.qc, you can't add new fields to that file until all of the system fields have been created.
 Typically
#254 posted by ijed [190.20.96.131] on 2009/05/11 01:29:55
It means you've added new stuff too high up - put your changes at the bottom of defs.
 Confirmed
#255 posted by MadFox [84.26.168.11] on 2009/05/11 01:36:54
I added four strings to the defs.qc as they were the errors I recived after compiling.
float visible_distance;
.float gorging;
.float stoleweapon;
.entity lastvictim;
How can I make addittions to the defs.qc , or should I delete all functions in grem.qc that work with doe.qc
 Fixable
#256 posted by Preach [81.152.234.175] on 2009/05/11 01:40:10
Like ijed said, just move those lines right to the bottom of defs.qc, and you should be fine.
 Yes!
#257 posted by MadFox [84.26.168.11] on 2009/05/11 01:45:47
that worked.
You earned your pronounciationmark back Ij!ed.
Alhough I don't see it steal my gun, it works fine.
 Hit That One
#258 posted by ijed [190.20.96.131] on 2009/05/11 02:04:18
Alot of times. When you're learning blind a molehill is everest.
 !
#259 posted by ijed [190.20.96.131] on 2009/05/11 02:04:46
 Gremlin
#260 posted by MadFox [84.26.168.11] on 2009/05/14 11:42:52
Well I trried including a Gremlin in my map without the whole SOA pak. And as I expected was the hipgrem.qc not enough. I had to make a change to the ai.qc and defs.qc as well.
When I was done and could compile without errors the gremlin came in game.
But for some reason the weapon steal trick doesn't seem to go. I did all I thought I need to do changing the needed hipsdef into the normal def.qc but something slipped away.
 Substitute
#261 posted by MadFox [84.26.168.11] on 2009/05/14 11:45:33
it was neg!ke with his pronouncion mark.
 Well
#262 posted by MadFox [84.26.168.11] on 2009/05/15 21:24:30
i guess its a dubious question asking where the gremlin has left my weapon.
 Bit Masks
#263 posted by necros [99.227.133.158] on 2009/05/24 21:21:36
i want to be sure i understand these:
Operations:
self.flags = self.flags - (self.flags & FL_ONGROUND);
this will subtract FL_ONGROUND but ONLY if FL_ONGROUND is present in self.flags.
whereas:
self.flags = self.flags - FL_SWIM;
would just blindly subtract the flag and risk breaking self.flags if FL_SWIM wasn't there when you subtracted.
likewise:
self.flags = self.flags | FL_ONGROUND;
would add FL_ONGROUND but only if FL_ONGROUND wasn't present in self.flags.
whereas:
self.flags = self.flags + FL_SWIM;
would add the flag as well, but would break if FL_SWIM was already present in self.flags.
Conditionals:
if (self.flags & FL_ONGROUND)
is the only way to check if FL_ONGROUND is present in self.flags.
 Correct
#264 posted by Lardarse [62.31.165.111] on 2009/05/24 23:17:31
But the safe versions are strongly preferred, because they don't break anything.
Note that you can work with multiple bits if you need to. The code for picking up armor removes all 3 armor flags before adding the correct one.
The only operation I'm not sure how to do is to toggle a flag (if off, set it; if on, clear it).
 Toggle
#265 posted by Preach [81.152.238.218] on 2009/05/25 01:35:54
The following code will toggle flags:
self.flags = (self.flags | FL_ONGROUND) - (self.flags & FL_ONGROUND);
The right hand side features two halves. In the case that FL_ONGROUND is already set, the first half does nothing, and the second half is equal to FL_ONGROUND, so the flag gets removed.
In the case that FL_ONGROUND is not set, the first half turns it on, and the second half is equal to zero, so has no effect.
There's a nice kind of symmetry going on with this formula. It combines the adding and subtracting flags, but in such a way that only one actually does anything in each case. The whole right hand side is evaluated before the result is assigned to the left hand side, you there is no risk that self.flags changes mid way through.
It's worth knowing that this can be extended to multiple flags:
float IT_TOP3WEAPONS = IT_GRENADE_LAUNCHER | IT_ROCKET_LAUNCHER | IT_LIGHTNING;
self.items = (self.items | IT_TOP3WEAPONS) - (self.items & IT_TOP3WEAPONS);
This code will toggle the most powerful weapons in the player's inventory correctly, regardless of which ones they may possess. In practice, it would be worth following up that line with a call to W_BestWeapon(), in case the player was using one of the weapons you just toggled.
 Thanks
#266 posted by necros [99.227.133.158] on 2009/05/25 02:12:46
didn't know about the toggle one.
 That Sound Bug
#267 posted by jdhack [75.155.104.195] on 2009/05/25 06:11:24
You know, the one where you pick up 2 items more-or-less simultaneously, and hear only 1 sound. I was thinking of fixing it engine-side, but then I realized this: often, 1 sound overriding another is the desired behavior.
I know of the null.wav being used to kill a playing sound, but I suspect there are other situations. Can you guys give me some examples?
 Upon Further Consideration...
#268 posted by jdhack [75.155.104.195] on 2009/05/25 07:44:34
It probably makes more sense to look at it from the opposite side, ie. when don't you want one sound to override another?
So, picking up multiple, different items; using a key which causes a door to open; what else?
 There's Also The Classic
#269 posted by Lardarse [62.31.165.111] on 2009/05/25 08:33:19
"Quad Cancel" sound bug, where you land just after picking up the quad, and the "oof" overrides the quad pickup sound.
#270 posted by Spirit [213.39.146.110] on 2009/05/25 10:19:55
Picking up the MH at DM4 with no sound (picking up the rockets to achieve that) is a deathmatch tactic.
I am all for it anyways.
 CHAN_VOICE
#271 posted by Preach [81.152.238.218] on 2009/05/25 12:07:23
It makes sense that anything a monster/player says overrides what they were previously saying - for example a pain sound interrupting some other noise.
If people wanted multiple pickups to sound, the fix is easy to perform in QC. Just remove the channel specification from the sound command - if set to 0, the sound plays from any available channel.
 Revised Ruleset
#272 posted by jdhack [75.155.104.195] on 2009/05/26 07:01:27
The DM angle is something I hadn't thought about, so I probably should just apply the fix to sp/coop. I agree that changing the QC is the "proper" way to do it, but an engine workaround has the advantage of not requiring any effort from the user(*).
How about only applying the fix to CHAN_ITEM and the powerups on CHAN_VOICE? Or is there a case where you'd want one of these sfx to be stopped before completion? (I'll probably include a check for null.wav, which would kill all sounds on the channel)
*assuming I do eventually release this, and anyone actually uses it
 Say No
#273 posted by necros [99.227.133.158] on 2009/05/26 09:03:40
to cheap hacks. :(
 No Cheap Hacks?!
#274 posted by jdhack [75.155.104.195] on 2009/05/27 05:32:13
Quake as we know it wouldn't exist without them - interpolation, fullbrights, external textures, skyboxes, fog, extended limits, modified protocols, plus a good chunk of the id & hipnotic code.
It's a good philosophy to keep in mind, but it's never a black-and-white issue. The reality is that all code makes assumptions about the environment in which it's run. And making changes to an existing codebase is never as clean as it would be were the features part of the original design. Sometimes you have to bend the rules to get the result you want. And in the end, if it works, people are willing to overlook the messiness of the implementation.
So I take it personally when someone dismisses my attempt to fix a known bug as a "cheap hack". If you've got any particular concerns or informed criticism, I'd like to hear them. Otherwise, don't bother.
And to those who provided useful input, thank you :)
#275 posted by necros [99.227.133.158] on 2009/05/27 10:19:24
except it is a cheap hack. it will cause an inconsistency in how channels work for a specific few cases which can cause confusion and annoyance later down the line for some poor bastard modder who isn't aware of one guy's attempt to 'fix' behaviour that was working as intended. a sound is expected to override the previous sound playing on a channel.
if it bothers you, fix it in qc (channels 5, 6 and 7 on the player are rarely, if never(?) used-- they could easily be used for a second item channel) because it's a qc bug. you can even play the pickup sounds on the items themselves, although the left/right won't be balanced because the sound isn't emanating from the player, but that solution is probably the most easiest to implement.
 Thank You
#276 posted by jdhack [75.155.104.195] on 2009/05/28 06:16:47
That made a far better case for your point than your previous post ;)
I guess my frustration wasn't really due to you calling it a cheap hack (because I'm not disputing that); it was more the feeling that you were dismissing it outright. The truth is I respect you for all you've contributed to the Quake community, so it's people like you I need to hear from to help me see things from a mapper/modder view. So thanks for the followup.
For me, the annoying thing with a bug like this is, here we are, 13 years later, and the QC hasn't been fixed (afaik; maybe you guys did in Quoth, but that would still leave a few hundred mods that are potentially affected, plus the thousands of maps that people don't run under Quoth).
One part of your post I didn't quite get was the "play the pickup sounds on the items themselves". Were you talking about reworking the QC to call sound() in an item-specific touch function rather than the generic one? Or were you talking about the engine making use of the .noise field?
 In Any Case
#277 posted by Preach [81.152.234.252] on 2009/05/28 09:47:35
I'm not actually convinced that it is a bug, because they added the idea of specifying a channel to qc specifically so that the sounds could be overriden. Perhaps a few sounds should be split off into their own channels, like the landing sound and the item pickup playing seperately. But I can see the sense of not playing multiple "item pickup" sounds on top of each other, because it would create a pretty nasty echo effect. The current system of cutting off and resetting the sound on the same channel is probably easier to parse when you listen.
#278 posted by necros [99.227.133.158] on 2009/05/28 10:39:14
sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); from health_touch()
it plays the sound on 'other' which in this case, is the player who touched it.
if you change this (and the other items) to play the sound on 'self' instead, multiple pickups will all play sounds of the item itself, not the player.
the only problem is that if you pick up items from the side, the sound will not be centered like it is when you play sounds on the player. sounds that the player makes are always centered (they come out of the left and right channels equally).
and no, i didn't fix it in quoth (and i don't think preach did, because i don't remember noticing it when i played quoth2 stuff).
this is really a preference thing, but it never really stood out as something that really needed fixing to me. i never even noticed that it would be a problem because i always took it as a matter of course that multiple items being picked up at once results in blocking some sounds out. it's either that or, as preach mentioned, a cacophony of different pickups all at once which is just as difficult to make out.
also, be wary of using CHAN_AUTO (channel 0) because the engine just picks whatever free channel it can find. that can be the weapon channel sometimes so you'll be in the same spot with say your shotgun blocking out the quad pickup sounds now instead of the nail box. :S
this is just my opinion but i think bringing in cool stuff from more modern engines like doom3 such as proper scripting, proper rotating brushes (the rotating entity itself causes collision without needing to hack in invisible collision brushes) and hierarchical entity binding, all done engine side would be completely bad ass. you will notice i'm am avoiding things like real time lighting, bump mapping and such. to me, as a mapper, i'm less interested in those things than i am with stuff that can increase the creativity of the maps being created.
 Damn Real Life Always Interrupting...
#279 posted by jdhack [75.155.104.195] on 2009/06/01 06:35:41
Anyway, I did look into the rotation stuff a bit. Would the Hexen II code be good enough? Very little change on the engine side, but the collision is quite rough (it just expands the bbox so it's big enough to contain the entity at any angle).
Regardless of the method, the tricky part might be deciding when the enhanced code should be used. Maybe a new SOLID_ or MOVETYPE_ constant? Also, there'd have to be a way to check for engine support via QC, probably via the checkextension system (just checked - DP's DP_SV_ROTATINGBMODEL extension may be the one).
The other features you mentioned sound intriguing, but right now I'm really trying to resist the urge to add new stuff, and just finish what's there (not nearly as much fun...). I'll keep it in mind for the future though.
And you don't need to worry about me adding visual fluff - my GL skills are at about a 1997 level :). Plus, I like my Quake to look like Quake, dammit!
#280 posted by necros [99.227.133.158] on 2009/06/01 07:26:53
mostly the reason i suggested that stuff was because i've yet to see it done so far.
once i got into creating things, i always felt the real impressive thing about doom3 was the script engine but i imagine it's probably many times more difficult to create something like that because it means creating a compiler and interpreting as well as making syntax or whatever that's intelligent.
 Although...
#281 posted by metlslime [24.130.223.174] on 2009/06/01 07:54:26
quake had quakec, which required a syntax, a compiler, and an interpreter, too :)
 Well
#282 posted by necros [99.227.133.158] on 2009/06/01 10:24:05
the big difference here is that it's integrated into the engine and that a map doesn't need to be a mod to implement quite a range of custom scripted events.
but implementing it in a custom engine when no one else has done it (custom engine modifications for scripts) is sort of self defeating because although you may not need a mod to run it, now you need the engine. :S
i should have thought it through before mentioning it, hehe. :P
 Scripting
#283 posted by jdhack [75.155.104.195] on 2009/06/03 06:15:28
Thinking about scripting and metlslime's post reminded me of an idea I posted to the Quoth 2 thread (post 347):
http://www.celephais.net/board/vie...
The examples I gave were silly one-step things, but what I envisioned was a chain of these "operation entities" used to perform a sequence of actions; in essence, a script!
Maybe add some conditional operations (if equal, if greater, etc.), and you'd have some rudimentary scripting ability.
#284 posted by necros [99.227.133.158] on 2009/06/03 10:20:33
purely theoretical discussion at this point but:
what you're describing is essentially what any modder does every time they compile a progs.dat.
stuff like, for example in hipnotic with the sound playing entities or the lightning shooters.
scripting in doom3 renders all those things obsolete. you have your rudimentary entities like func_doors and trigger_multiples for stuff you reuse all the time, but if you wanted to make a lightning trap, you don't need to make a whole new entity.
it's maybe not a big distinction, but i never liked the huge amount of scripting 'entities' that you get in say, hl2. it's many times easier to plan and work on a complex scripted event when you have simple lines of code in front of you instead of 5 trigger relays and half a dozen doors and plats all connected together.
but as i think on it, i feel i'm probably in the minority here. a lot of new guys on doom3world never even touch scripting because it has a steep learning curve.
#285 posted by Willem [24.163.61.78] on 2009/06/03 11:20:10
necros
We evolved the UnrealEngine to use a visual scripting system, Kismet, because we were tired of trying to string together triggers and events for complicated stuff. It really is an archaic way to go these days and makes debugging and absolute nightmare.
 I Never Did Much In D3
#286 posted by bear [94.255.215.125] on 2009/06/03 18:19:48
but the scripting did seem like the most interesting new thing to me as well
 REVIVE!!!!!!!!!!
#287 posted by meTch [69.183.70.109] on 2009/07/14 04:54:32
*godly chorus sings*
i r wins
#288 posted by megaman [94.221.103.25] on 2009/07/16 19:34:21
i see how a visual language makes debugging much easier :)
 Walkmove Returns
#289 posted by Preach [94.169.109.218] on 2009/09/21 21:20:30
So occasionally the navigation of quake monsters is inadequate. Although they walk entirely blind, feeling their way along walls, usually they get blown to pieces too fast for the player to even care. As people who have read through the AI Cafe tutorials will know, the monsters can either walkmove() or movetogoal(). The latter function will hug walls and attempt to navigate a bit if an obstacle is hit. This does take a bit of control of the navigation away from you.
Walkmove is more of a direct command, to move in a direction, to a distance. When a monster is blocked while trying to walkmove, it is worth knowing two things:
1. The monster does not move at all, walkmove is all or nothing.
2. The walkmove function returns FALSE, instead of TRUE.
The return value of the function would be very handy, as if it returns false, your monster would know that it is blocked, and perhaps make a decision based on that (to try and jump, or hide, or strafe from side to side maybe).
The trap here is to go and write a function which replaces ai_run, the usual function which instructs a monster to move towards their enemy. The problem being that ai_run does a lot of other important things for a monster - checking that the enemy is dead, or whether they should start attacking, and so on. You might end up copying the entire function apart from the last line, which you change to the new walkmove code (and reaction to it returning FALSE).
Then if you change ai_run in the future, you need to remember to change your copy of it too. And duplicating code is pretty wasteful too. You might try and refactor ai_run to let you choose the navigation after, but doing that is a lot of fuss.
So a fairly neat solution came to me yesterday, which is why I wrote this whole thing up. Just write a basic function which will move in the correct direction, check the return value, and react if you are stuck. It does not need to replicate the other functions of ai_run(). Then the key trick is to call it from only one of the running frames of your monster. The rest of the frames should all call ai_run() like usual. Every 6 or 8 frames your monster thinks about something else, and the rest of the time it's business as usual.
If you really wanted, you could call ai_run() and your new function from the same frame, splitting the distance to travel between them. If so, think carefully about which function you want to override which. If you would prefer for your monster to attack as normal, put the call to ai_run() last, and vice-versa.
Okay, all told.
 Interesting...
#290 posted by metlslime [173.11.92.50] on 2009/09/21 21:37:49
I ran into this exact situation last night. My solution was to call ai_run(0) every frame, and then call my_walkmove() every frame to do the actual movement.
 Heh
#291 posted by Preach [94.169.109.218] on 2009/09/21 23:52:19
Glad to see I'm at least thinking about problems people care about, even if it's too late to be useful : - p
 That
#292 posted by ijed [190.20.115.237] on 2009/09/22 04:46:01
Gets cogs turning.
Ways to get the fiends more agile.
 Preach:
#293 posted by metlslime [98.248.107.212] on 2009/09/22 10:13:54
there is something maybe you can help with... is there any way to prevent flying enemies from sometimes going inside solid walls?
 It's A Real Mystery
#294 posted by Preach [94.169.109.218] on 2009/09/22 21:45:30
If someone could reliably reproduce it, with a testmap and a 20% or so success rate, then it would be helpful. I'm sure it's just a check that isn't done carefully enough - and it should be really easy to spot because of the isolated nature of the code for flyers. It is something that I've wanted to crack, but I don't know yet...
 I Think
#295 posted by ijed [216.241.20.2] on 2009/09/22 22:40:59
Its to do with navigation by point as opposed to bbox - but flyers and swimmers aren't stuck to the floor in order to move.
Points to a coding can of worms though, if it is that.
 Preach...
#296 posted by metlslime [173.11.92.50] on 2009/09/23 00:31:36
i might, i'm currently testing a flying monster in a room with a large crate in the middle. There was at least one test session where they consistently worked themselves inside the crate, while testing on nightmare mode.
This monster has a custom move function which calls walkmove() and if that returns false, calls movetogoal(). In order to control their vertical movement, I give them a self.enemy that is either 1000 units above them, or 1000 units below them (otherwise they will always seek the same horizontal plane as the player and stay there, which i don't think is very interesting for a flying opponent.)
Anyway, if i can get a reproducible case, I'll let you know.
#297 posted by necros [99.227.133.158] on 2009/09/23 06:46:41
it's not possible to detect if the player is pressing movement buttons is it?
something that would be true if he wasn't pushing any buttons but he was sliding down a slope, for example.
#298 posted by necros [99.227.133.158] on 2009/09/23 09:01:17
i may not need to worry about this after all, i just found out the player's velocity is considered 0 when standing on a platform or whatever and it's moving you.
 Chasing
#299 posted by Preach [94.169.109.218] on 2009/09/23 20:02:32
Is this is the for the same mod where you wanted to create a chasecam? If so, the usual trick is to proxy the player. To do this the actual player entity an invisible movetype_noclip entity, and in playerpostthink, keep resetting the origin to that of the camera entity(to prevent visibility problems). Then through reading the offsets of this dummy player, you should be able to deduce the key presses.
This assumes that you have an entity set up as the chasecam viewpoint, and that you're willing to make a 3rd entity which serves as the visible player. But that is usually acceptable for the kind of mod where you'd want to read the inputs.
 Correction
#300 posted by Preach [94.169.109.218] on 2009/09/23 20:03:05
To do this make the actual player entity an invisible movetype_noclip entity...
#301 posted by necros [99.227.133.158] on 2009/09/23 20:10:34
that sounds interesting, but quite ugly. :P
the only reason i asked was because i was worried that when a player was on a lift it would count as if they are moving, if i checked self.velocity, but as it turned out, if a player is standing on something that's moving, his velocity is 0, so it was all good. :)
 There Is
#302 posted by ijed [216.241.20.2] on 2009/09/23 20:27:12
Kascam - AguirRe was experimenting alot with this. It basically spawns a fake client who goes into a kind of auto spectator mode, either chasecamming or snapping to interesting points from which to watch from a la Resident Evil.
Unplayable from these views, so probably not what you want.
 Special Characters
#303 posted by necros [99.227.133.158] on 2009/09/24 01:56:51
preach, how did you do the progress bar in quoth 2 for the flashlight?
 Character Modification
#304 posted by Preach [94.169.109.218] on 2009/09/24 10:39:42
The bar was made up of 4 characters drawn onto CONCHARS in gfx.wad. The characters we replaced were 4 of the box border characters which are repeated half way down the CONCHARS image. It seems like the engine is consistent in using only the upper row copies.
To add them to a string, the qc used escape characters. FTEQCC (and frikqcc I think) supports specifying a character by number, using the format "/{137}" for character 137. From this, 9 preset strings were created, one for each stage of the progress bar.
The hacky part was getting centerprint to behave exactly as it would for normal messages(so that they would last for 2 seconds regardless of whether the flashlight got toggled during the message). For that, we needed two fields on the player, a float for storing the expiry time of the current message, and a string to store the centerprint message. A global float called centerprintcount was also added.
The centerprint builtin was wrapped to set these two fields to (time + 2) and the string it was sent respectively. It then set the centerprintcount variable to 2. It did no actual centerprinting itself. Instead in playerpostthink the function decided if the centerprint message needed to be changed, asking:
• Has the flashlight been turned on or off?
• Has the charge decreased since last frame?
• Has it been more than 1.8 seconds since we refreshed the flashlight?
• Is centerprintcount greater than zero?
• Has the centerprint time expired for the player's message?
If the last condition was true, the player's centerprint message was changed to "". Then if any of these conditions are true, a new centerprint is sent. It would take advantage of the fact that calling the builtin centerprint with two strings will see them printed one after another(in fact it can be overloaded with up to 8 strings, the maximum number of parameters a quake function can carry). The correct string for the flashlight(including no string if it has turned off) would be retrieved and set along with it.
The last detail is that centerprintcount is decreased by 1 in startframe. This works to ensure that every client will get centerprint messages updated correctly. There is actually a bit of oversend when it comes to sending these messages - when ANY client gets a centerprint, everyone gets theirs updated. Similarly if the centerprint is triggered in a playerprethink or player physics event it will get re-sent in the next frame. But this only raises additional network traffic on a few frames, the important thing is to avoid spamming the message every frame.
An alternative method would be to build a centerprint message byte by byte, the way that temporary entities for lightning or gunfire are created. Set msg_entity to the player, send an SVC_BYTE(MSG_ONE, SVC_CENTERPRINT) - SVC_CENTERPRINT can be looked up in the source. Then send the bytes corresponding to the characters making up the progress bar, followed by a SVC_STRING of the centerprint message.
Advantage: you can pick the characters to send procedurally, avoiding the hard coded strings. If you added more characters to your set, you could have sub-character progress marks.
Disadvantage: you can only send one string, because it is null terminated, which ends the centerprint. Although the quoth method I described only supports one string per centerprint, it is possible to see how it could be expanded by adding more centerprint string storage to each player, and sending all the strings.
#305 posted by necros [99.227.133.158] on 2009/09/24 21:36:02
impressive, thanks for the info :)
wouldn't it have been easier to assemble the string with code instead of making presets?
like:
a = "["
b = "="
c = "-"
d = "]"
then just doing string = a + b + b + c + d? or does that not work in qc? i've never worked with strings much in qc.
 Append
#306 posted by necros [99.227.133.158] on 2009/09/24 22:49:01
just got a chance to check and i realised that string manip isn't possible in qc :P
 String Manip
#307 posted by Preach [94.169.109.218] on 2009/09/25 01:15:50
That's why you need the trick which lets you write bytes, it's heavily practised in Prydon Gate with all those text menus. frikqcc introduced a handy addition as I recall, making it so that a single quoted character whould be translated to the numeric byte value you need for writebyte. So if you had a function
print4(float a, float b, float c, float d)
which prints the bytes a,b,c and d, you could call it as
print4('n','a','i','l');
which makes the code a bit more readable.
Now I'm gonna go away and think about the best way to formalise faked strings like that...
#308 posted by necros [99.227.133.158] on 2009/09/27 04:59:25
i've got an interesting problem...
ftos(). a great feature about this stupid thing is that it seems to store the converted string into the same place in memory, so if you use the centerprint trick that concatenates strings, and you were to put multiple ftos() into it, it would print only the last ftos() that was called.
to get around this, i tried making a bunch of temp strings, and then setting each of these temp strings with ftos() before calling the centerprint, but that doesn't work because, it seems, the temp strings are only a pointer to the memory location that ftos() is using, so the end result is absolutely the same.
is there anything i can do?
 Depends
#309 posted by Lardarse [62.31.162.25] on 2009/09/27 11:26:38
What's the numbers that you need to centerprint?
If it's just 2 numbers that are a few digits long then you can convert one of them to strings that are a digit long and then print the other one after it.
Example here (with the functions DigitToChar and NumToString, you may want to use the search function as they're near the bottom of a long file): http://svn.quakedev.com/viewvc.cgi...
#310 posted by Lardarse [62.31.162.25] on 2009/09/27 11:33:08
Except that instead of strcatting the digit strings together, you print them one by one.
Please excuse the evile QCCXness that that code had back then...
(We need a barf icon...)
 Stream Of Conciousness
#311 posted by Preach [94.169.109.218] on 2009/09/27 12:24:43
When it comes to functions like dprint and sprint, the usual trick is to call the print function after each ftos call, to flush the buffered string into the console, eg:
dprint("health: ");
s = ftos(self.health)
dprint(s);
dprint(", height:");
s = ftos(self.origin_z)
dprint(s);
dprint("\n");
This works fine with prints to the console, as the messages are cumulative, written one after the next. The problem with centerprint is that each call flushes whatever was previously printed, so you need to get everything into one message.
This of course leads us down the road of byte by byte messages, where you construct a function which can convert a float to a sequence of characters, which are then sent by SVC_BYTE. So it's possible, just not pleasant.
I've been trying to think of a way to write a "library" which would make writing this kind of centerprint byte by byte more straightforward. I think the best structure would be:
• A buffer of 16(?) floats, enough to store one line-width of a centerprint message
• A function called Flush() which sends all 16 characters in the buffer with SVC_BYTE.
• The concept of a function object called a stream.
• Customisable functions can then be written which read values from streams into the line buffer.
It would be at the last level that layout could be controlled by the coder - deciding what to do if a stream is longer than the current line, whether you need to render a border on the first and last character, etc. The library would come with some examples.
The important idea is the stream. This would be a dummy object with .think set. What you set think to depends on the content of the stream, but the simplest example of a constant string would look very much like a monster animation function. For example, the sequence "hello, world" would begin:
helloworld_1 = ['h', helloworld_2 ] {};
helloworld_2 = ['e', helloworld_3 ] {};
helloworld_3 = ['l', helloworld_4 ] {};
Then reading a character from the stream self is performed by the lines
self.think();
char = self.frame;
(you need to make whatever is thinking self, that's the normal quake rules)
The advantage of this method is that then you can invent a new function which, for example, streams the digits of a float to some precision. As long as you make the interface the same - use .think() to advance the stream a character and .frame to access the current character - then the higher level functions can handle it identically.
It would probably be helpful to add one property to the stream entity: length. That way, the highest level of function can see if a stream will fit on the current line, and if not then consider moving it to the next line. It would also be easy to wrap streams around lines, simply output as much as will fit the current line, flush, then pass the partially completed stream to the next line.
You can also imagine a stream modifier, an entity which could when queried would read a second stream entity, and then either pass on the character it read, or skip that character and go to the next one - stripping out white space perhaps.
 And One More Thing
#312 posted by Preach [94.169.109.218] on 2009/09/27 12:28:07
If you only need three values, and it's ok to print them out one after another, you could use vtos() on a specially constructed vector, with the three values set to _x, _y and _z of the function. A little easier than overengineering stringstream for quake...
 Wait... What?
#313 posted by necros [99.227.133.158] on 2009/10/04 02:51:37
two questions,
1. is the engine hard-coded to give players the axe and shotgun when they first load up a map?
2. (and this is the weirder one) if parm1-16 are global variables, how is it in COOP with more than 1 player, they can retain items and such. are parms special variables that have extra copies for each player? how does the engine know which player's parms we're talking about then?
#314 posted by necros [99.227.133.158] on 2009/10/04 02:56:57
scratch the first question, i did something dumb which made it seem that way.
 A Parm And A Weave
#315 posted by Preach [94.169.109.218] on 2009/10/04 13:30:10
The engine stored parms for each player internally. I believe the way it works is that the QC globals parm1-16 are set to the values the engine saved just before it calls PutClientInServer() for that player. So the parms are only valid to read in coop for the duration of that function (and any functions it calls). Otherwise you will probably be reading the parms of the last client on the list.
If you're removing the axe from the player, make sure you handle the case where he runs out of ammo with all weapons - in the standard code it will switch to axe without checking if you have one.
#316 posted by necros [99.227.133.158] on 2009/10/04 20:01:10
great, thanks for the info. :)
#317 posted by necros [99.227.133.158] on 2009/10/26 03:36:41
this one's more of a math problem...
how can i align monsters with the ground plane underneath their bodies? is it even possible? vectoangles only ever sets x and y, never z, so a full parallel alignment seems impossible.
 Something Along The Lines Of
#318 posted by Lardarse [62.31.162.25] on 2009/10/26 07:29:37
Trace directly downwards, and compare trace_plane_normal to the vertical, then derive the angles needed from that.
You probably need 2 calls to vectoangles, with things swapping around between calls.
 Algorithm
#319 posted by Preach [94.192.82.29] on 2009/10/26 10:47:00
This is roughly what I'd do, not properly converted into qc:
//yaw is the desired facing of the monster
mapAnglesToFloor(entity mon, float yaw) =
{
//do the trace and exit early in easy cases
traceline down
if(trace_fraction == 1 || trace_plane_normal == '0 0 1')
return '0 yaw 0'
//construct a vector perpendicular to both the desired facing of the monster and the normal from the ground, by using v_left
makevectors('0 yaw 0');
//get the actual facing out vector using the cross product
facingvec = crossproduct(v_right, trace_plane_normal);
return vectoangles(facingvec);
}
I'm not sure it actually addresses your concerns about ang_z not being set though. It is also possible that I have the handedness of the coordinate system wrong, and so you need -1*v_right for it to work.
You also might want to consider interpolation of some sort so that the monster doesn't go crazy on broken ground or trisoup terrain. In that case, it is probably best to have a field storing the old "normal", then calculate a new one with
normal = normalise(normal + trace_plane_normal);
You could scale one of the vectors to cause slower or faster interpolation. By interpolating the normal vector, you can be sure that the changes in facing by the monster are responded to directly.
 So
#320 posted by ijed [216.241.20.2] on 2010/03/16 19:43:38
I've got a point entity called a func_door_model. The idea is for the mapper to specify a .bso model in there and circumnavigate a load of issues - breaking max models, patchy lighting, messing around with texture alignment etc.
It seems to work pretty well, the whole idea is to later on extend it to other entities like trains, breakables and so on.
The first problem (of many) is that doors created this way aren't generating their trigger field.
What I have is a 'wrapper' piece of code that specifies a .mdl and replaces the model field with that value - this is a an engine fix for DP that I imported from elsewhere.
As I understand it:
cmins = self.mins;
cmaxs = self.maxs;
and
self.owner.trigger_field = spawn_field(cmins, cmaxs);
Are what create the trigger field, but the first one isn't working when an external .bsp is referenced.
I can touch the door and fire it from a trigger, so the functionality is intact apart from the trigger.
Any ideas?
 Relativity
#321 posted by Preach [94.171.242.186] on 2010/03/16 21:41:35
If you placed your func_door_model at the origin, then I expect that the trigger field would work perfectly. If you enjoy a challenge then work from that hint and ignore the rest of the post...
Otherwise, I'm hoping that the following is the fix to your problem: The assumption that the trigger field works code works on is that the entity is placed on the origin of the map. This is always true for baked-in models like a standard func_door.
For example, if you built a 64x64x64 func_door with a corner closest to the origin at '512 512 0', then door.mins = '512 512 0', door.maxs = '576 576 64' and door.origin = '0 0 0'. It's a bit strange to think about at first, because regular entities like players or monsters tend to have the origin half way between the mins and maxs. (*)
Luckily, this makes the fix very simple, and backwards compatible with a regular func_door.
cmins = self.mins + self.origin;
cmaxs = self.maxs + self.origin;
This should work with regular .mdl files too, although you'll run into another problem with them which is much harder to work around. You could also try swapping self.mins + self.origin with self.absmin, not sure if it's any better for compatibility with dp though.
(*) There is an uncommon exception to this rule, found in rotating entities. Since models can only rotate about a single axis - the origin - the bsp compiler has to play some games with them. It finds the origin of the info_rotate for the model, then moves the brushes of the model so that the origin of the map lines up with that spot.
Once it has compiled that model, it sets the origin value of the entity with the model to equal the origin of the info_rotate - thereby reversing the movement and restoring the original location of the entity. Of course, the lighting and texture alignment are likely shot to hell, but who cares!
 Hm
#322 posted by ijed [190.20.81.89] on 2010/03/16 22:36:12
You're the man.
Challenge is good but the first paragraph was a bit cryptic - I had to read the rest to get the bit about it being the world origin.
Makes perfect sense though, and the added bonus of true .mdl doors is interesting...
I've wondered about why the rotating mesh was moved to the center of the world, but docs there aren't much of.
In any case, this puts me well on the way - also going to try some stuff with trigger_once_bbox working in a similar way, the mapper setting the size of the trigger numerically.
Am I right in thinking you did something similar in Quoth2?
 Just
#323 posted by ijed [190.20.81.89] on 2010/03/16 22:37:10
Don't tell me how you did if you did - there has to be some challenge :)
 Some Bits
#324 posted by Preach [94.171.242.186] on 2010/03/16 23:04:14
We had the model-saving bounding boxes on triggers and external bsp format models. We don't have .mdl format doors yet, there's a little wrinkle with solid types which I don't think can be fixed nicely.
We also didn't fix the bug from #320 in quoth yet, although I expect it'll be in the next release... As a workaround, I suppose people will have to add the door trigger manually.
 MOVETYPE_PUSH Not Bsp
#325 posted by ijed [190.20.81.89] on 2010/03/16 23:11:27
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?
#326 posted by necros [99.227.131.204] on 2010/04/07 06:33:57
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
#327 posted by Preach [94.171.242.186] on 2010/04/07 12:29:58
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.
sv.time
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
#328 posted by Lardarse [62.31.162.25] on 2010/04/07 14:40:43
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?
#329 posted by necros [99.227.131.204] on 2010/04/07 19:34:28
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
#330 posted by Preach [94.171.242.186] on 2010/04/07 21:28:31
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
or
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.
 Clarification
#331 posted by Preach [94.171.242.186] on 2010/04/07 22:20:23
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
#332 posted by necros [99.227.131.204] on 2010/04/10 00:20:02
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.
 Imprecision
#333 posted by Preach [94.171.242.186] on 2010/04/10 00:28:18
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.
#334 posted by necros [99.227.131.204] on 2010/04/10 01:30:05
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
#335 posted by Preach [94.171.242.186] on 2010/04/10 02:33:45
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.
#336 posted by necros [99.227.131.204] on 2010/04/21 02:54:22
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
#337 posted by necros [99.227.131.204] on 2010/04/21 06:09:30
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?
 Yup
#338 posted by Preach [94.171.242.186] on 2010/04/21 10:05:33
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.
#339 posted by necros [99.227.131.204] on 2010/04/21 21:26:56
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
#340 posted by Preach [94.171.242.186] on 2010/04/21 23:04:51
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...
 Lol
#341 posted by necros [99.227.131.204] on 2010/04/21 23:46:49
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
#342 posted by MadFox [84.26.170.230] on 2010/04/26 04:54:39
Hey Preach, did you receive my email?
I'm rather stuck in the monster toppic.
 Hmm
#343 posted by Preach [94.171.242.186] on 2010/04/26 10:34:59
Send it again, don't think I have it.
#344 posted by MadFox [84.26.170.230] on 2010/04/26 21:29:05
I ment the Monster thread.
I'll send it again.
http://www.celephais.net/board/vie...
#345 posted by necros [99.227.131.204] on 2010/05/11 04:37:26
.huntt_ime
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. (sic)
in ai.qc
is this true? it sounds like a lie. :P
 The Hunt Is Over
#346 posted by Preach [94.171.242.186] on 2010/05/16 16:37:21
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.
#347 posted by necros [99.227.131.204] on 2010/05/16 21:05:02
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
#348 posted by meTch [64.148.30.122] on 2010/05/17 01:44:31
we should just see how D00M does it,
and port it to,
(_)uake :D
.|
 Monster Only Clip...
#349 posted by necros [99.227.131.204] on 2010/05/31 21:35:41
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.
#350 posted by necros [99.227.131.204] on 2010/05/31 21:59:19
ran a little test.
instead of a generic monsterclip, i created a method to get larger bbox monsters to move correctly.
you essentially build your 'hull3' out of brushes and turn the entire thing into a single func_clipModel, and then you tie that clipModel to the monster you want to run collision with via clipModel->targetname editor key fields.
this could actually work because it won't impede smaller bbox monsters (that can use normal hull1/2 collision) since the clipModel is only every solid during that single monster's walk frame...
 Necros:
#351 posted by metlslime [67.188.81.46] on 2010/06/01 00:35:07
Nice, I'd really like to see some larger monsters or bosses/minibosses (that can actually move) in quake. This could be a piece of that puzzle.
#352 posted by necros [99.227.131.204] on 2010/06/01 01:06:00
yeah, works pretty decent, but of course it imposes some limitations.
you need to 'clip' any area where the monster will be, of course, and that means you have to visualize the hull expansion yourself and implement it.
also, currently how i do it is to use the same method that hipnotic did for the func_togglewall and that is to just add '8000 8000 8000' to the origin when you want to turn it off, and then subtract the same vector when you want it on.
i don't know how big a deal it is if you had a generic monsterclip entity that EVERY monster in the map would have to toggle back and forth every animation frame. that could be pretty brutal.
it's probably better to somehow localize monster clips so only the monsters most likely to actually touch them will be toggling them (hence why i opted for a clipModel->targetname method instead of just putting it in walkmove and movetogoal). (large bbox monsters use a special wrapper for ai_walk and ai_run).
 Some Problems
#353 posted by necros [99.227.131.204] on 2010/06/01 01:33:09
quake seems to use start the hull2 bbox from the bottom left (mins) of the monster.
this means that if you use a bbox of size (for example) '-128 -128 -24' - '128 128 64' only the mins up to '-64 -64 64' is used when checking collision.
i thought that quake would start the hull2 size from the center of the monster, but this is not the case.
this creates a problem now because collision is still messed up.
if we resize the monster to hull2, call movetogoal and resize back to the new hull size, collision against the world (and func_clipModels) is fine, but the monster is now able to walk inside the player and other monsters.
i'll have to put more thought into this, i guess...
#354 posted by metlslime [166.205.137.59] on 2010/06/01 02:59:10
Wait, how do shub and chthon work? They have large (stationery) bboxes and players seem to collide against them correctly.
#355 posted by necros [99.227.131.204] on 2010/06/01 03:12:01
yeah, the collision of other bboxes is fine.
it's the collision against the world that is messed up.
and that's the big problem:
movetogoal will function correctly vs other bboxes if you set the bbox correctly. 256x256x128 or whatever.
movetogoal will function incorrectly vs world when bbox is set to 256x256x128.
if you set bbox to standard hull2 size before movetogoal and reset to 256x256x128 after, monster will move inside player.
what i've done so far is left the bbox at 256x256x128 and simply 'offset' the func_clipModel so that sides that the mins hits are smaller than sides that the maxs hits.
kind of hard to explain, when i figure everything out, i'll probably make a blog post about it with pictures to explain it properly.
also, another interesting (and annoying) side effect: because of the way the standard ai_run code is sandwiched between func_clipModel toggles such that the clipModel is active when ai_run is called, things like the visible() function fail if the player is inside the clipModel since visible uses a traceline to determine if the monster can see it (and since the clipModel is solid during that period, the trace hits the clipModel and determines the player is not visible).
 Append
#356 posted by necros [99.227.131.204] on 2010/06/01 03:16:11
another way to look at it is this:
for the purpose of bbox vs world, only the first hull2 coordinates starting at the mins are solid.
that is to say:
if the bbox was mins: -128 -128 -24, maxs: 128 128 64
world collision is done from -128 -128 -24 to -64 -64 64 (mins + VEC_HULL2_SIZE)
because VEC_HULL2_SIZE = 64 64 88
 Oh I See...
#357 posted by metlslime [67.188.81.46] on 2010/06/01 07:21:30
if you set the bbox down to standard hull2 size, it's collision against other entities is wrong. If you leave the bbox alone, its collision box is offset from the correct location.
Two ideas:
1. before moving the oversized entity, make ALL other solid entities (or at least all entities within a findradius) larger by XYZ amount, to compensate.
2. have two entities, one oversize and one normal size, and move them both. If either one is blocked, set both entities to the location of the blocked entity.
Not sure if either of these are completely workable.
#358 posted by necros [99.227.131.204] on 2010/06/01 08:12:49
1. could work, except it's just (potentially) a lot of entities to enlarge.
2. this is better, but the problem is that if one is blocked, and we reset the position, nothing happens for that frame and likely movetogoal will try the same thing next frame.
currently what i've done is use bboxes on all the func_clipModels. this means each brush needs to be a seperate entity and we can't have sloped/angled faces anymore (since everything is just a box now).
it works and doesn't seem too slow, but we loose a bit of brush flexibility since we can't have any angles any more. of course, you're really just blocking out the area, so this could actually work. i'll leave it this way for now and test it out for now.
 I'm Not Compensating. Really!
#359 posted by necros [99.227.131.204] on 2010/06/10 22:31:26
http://necros.quaddicted.com/temp/...
the collision stuff has been working well without any further problems so far.
the really nice thing about it is that for monstrously large monsters like the dragon in that shot where you don't necessarily want the bbox to completely cover the entire model, all you have to do is expand the clipModel entities out further from the actual walls.
#360 posted by negke [88.70.250.215] on 2010/06/10 22:36:16
Is that the DOE dragon?
 Yeah
#361 posted by necros [99.227.131.204] on 2010/06/10 22:38:15
i rerigged him and animated him for actual combat and not just flying around path corners.
 Accelerating Back
#362 posted by Preach [77.98.129.22] on 2010/06/18 00:53:17
So I've just spent about a month without internet at home, shame I missed all that stuff about large bboxes, because that's a cool idea. If you're doing a landbound monster with a tall box, then you can create maps which can be bbox-blocked with greater ease. Low walls rarely need to be boxed in this case, just obstructions which are above the head height of regular sized entities but low enough for "the boss" to collide with.
Anyway, that's a digression. In my time away, I had lots of time to get on and finish projects. A fair few of them were quake related, and I'm posting the first one now. It's the tutorial I mentioned a dozen posts up about accelerating MOVETYPE_PUSH objects. Since it involves some formulae which I've done up in LaTeX and a few graphs, I've made a web page for it rather than just copy-paste it straight onto func.
Accelerating pushers
Also means I can fix typos and other problems, so post them here!
#363 posted by necros [99.227.131.204] on 2010/06/18 01:08:25
i haven't really read your above post yet as accelerating movers isn't a pressing issue anymore, but i just wanted to mention: you should compile all your coding tips you've made in this thread and put them up on a site somewhere. some of them are quite useful and others are downright golden.
 No Longer Touching
#364 posted by Mike Woodham [86.176.196.176] on 2010/06/19 19:35:54
I have created a 'trigger' entity. It covers a large portion of the map and instigates certain continuous but randomly timed actions whilst the player is inside the trigger's area. Let's call it an area_trigger.
The events are called by using the trigger's touch function, as in self.touch=do_these_things, so that when the player leaves the area, do_these_things no longer gets called. If he re-enters, the events start again. So far, so good.
I now want to 'enhance' this effect so that when the player leaves the area, a separate single event takes place, and this event takes place each and any time he leaves the area, which could be one or more times throughout the game. This event must not take place at any other time.
Is there a way to read when the player stops 'touching' the trigger so that I can call this exit event. I maybe could set up multiple triggers around the area_trigger and have them switched on by the area_trigger so that the call to the exit event is operated on a one-way basis but I am hoping there is an easier way.
Any views from the coding gurus?
 Not Easily
#365 posted by Lardarse [62.31.162.25] on 2010/06/19 20:13:30
A better solution is to have triggers at the entrances to the area. Two in fact, separated a little. The inside one calls the enter function, the outside one calls the exit function. The functions are coded so that they only actually do something on a state change.
#366 posted by necros [99.227.131.204] on 2010/06/19 23:31:26
actually, it is easy.
on your trigger_area touch function:
self.nextthink = time + 0.1;
self.think = DO_THIS_WHEN_PLAYER_LEAVES;
since touch is called every frame, nextthink will always be set higher than time, so .think will never be called. the minute the player steps out though, and .touch isn't called anymore, nextthink will expire in the next 0.1 seconds and run the .think function.
also, since i believe .touch functions are run first (before .think functions), even if you were getting less than 10 frames per second (such that the next .touch might be AFTER 0.1 seconds) the touch will be run first, thereby resetting nextthink anyway.
 Append
#367 posted by necros [99.227.131.204] on 2010/06/19 23:55:51
first: i forgot to mention in your .touch function, remember to store 'other' in self.enemy or somewhere so that your DO_THIS_WHEN_PLAYER_LEAVES function will know how to have as the activator (just add activator = self.enemy;)
if your trigger_area already needs to have a nextthink and think set for whatever reason, just spawn in a relay entity and do the trick on that entity instead:
if (self.owner == world)
{
self.owner = spawn();
self.owner.owner = self;
}
self.owner.nextthink = time + 0.1;
self.owner.think = DO_THIS_WHEN_PLAYER_LEAVES;
then:
void() DO_THIS_WHEN_PLAYER_LEAVES =
{
...........some code here............
self.owner = world; //break link with the relay entity and the trigger
self.nextthink = time + 0.1;
self.think = SUB_Remove; //delay remove
};
we set up .owner so we have an easy way to get access to the trigger_area's .enemy field, target and whatever else might be needed and we manually break the .owner connection when removing the entity because there's like a 2 second delay before entities are removed in quake.
 Crap
#368 posted by necros [99.227.131.204] on 2010/06/19 23:57:45
void() DO_THIS_WHEN_PLAYER_LEAVES =
{
...........some code here............
self.owner.owner = world; //break link with the relay entity and the trigger
self.nextthink = time + 0.1;
self.think = SUB_Remove; //delay remove
};
otherwise your only erasing self's owner (the relay entity) and not the .owner belonging to the trigger. sorry. :P
 Not Sure If That Works
#369 posted by Lardarse [62.31.162.25] on 2010/06/20 12:12:12
Because when you're in a trigger, I don't think the touch function is called every frame.
 Two Stage
#370 posted by Preach [77.98.129.22] on 2010/06/20 13:19:52
If that is a problem(it certainly would be if you need to detect monsters)can use force_retouch to help you out there. The simplest way would be to set force_retouch = 2 in the touch function as soon as you've touched the trigger. This would end up polling the heck out of all the triggers in your map while someone touched the middle one, but it would fix that problem.
I think the above is the only way to make the trigger responsive within a single frame (assuming you shrink the nextthink time lots). If you're willing to have your trigger less responsive (a minimum of two frames, but the values we pick here will be 0.2 seconds) then we can end up only using force_retouch once in 0.2 seconds.
We need the trigger_detector to have three states:
STATE_EMPTY: no player inside
STATE_READY: detected a player recently
STATE_POLLING: checking if there is still a player
If a player comes into an empty trigger we go from STATE_EMPTY to STATE_READY and wait for 0.1 seconds. After that time expires we go into STATE_POLLING for 0.1 seconds. If a player touches the trigger within that 0.1 seconds, we go back to STATE_READY, otherwise we go to STATE_EMPTY (and trigger the leaving event).
We don't actually need to explicitly track these states, they are just to understand what is going on. We have a think function called trigger_detect_startpolling along the lines of
self.nextthink = time + 0.1;
self.think = trigger_detect_fire;
force_retouch = 2;
When our .think is trigger_detect_startpolling, we are in STATE_READY. When our think is trigger_detect_fire we are in STATE_POLLING. When we don't have a nextthink in the future, we are in STATE_EMPTY.
Finally to hook all of this up, we need the touch function to set
self.nextthink = time + 0.1;
self.think = trigger_detect_startpolling;
which both moves us from STATE_EMPTY to STATE_READY when the player first touches, and back from STATE_POLLING to STATE_READY when the player retouches.
On another topic, force_retouch has an important effect on touch/think order which I hadn't considered before. When set, players will touch things they are in contact with before their think functions run (and then possibly touch things AGAIN after physics has run). So anyone who was intending to exploit the order that functions run had better be careful.
Also, I'm gonna go see if things really can run touches multiple times in a single frame. If that is the case, then it really will be important to make touch functions idempotent. This is a wonderful term from mathematics for a function which doesn't do anything else when you keep applying it to it's output. For example rint(x) is idempotent - once you get a whole number out, applying rint to that whole number just gives you the same whole number.
I'm using idempotent in a slightly weird sense here, the idea being that one of the parameters to our touch function is the value of "time", the frame that we are in. Most of the original touch functions have if(self.nextthink > time) type guards in place to achieve this, but it's something important to think about if you're writing a trigger which can be touched every frame - does it matter if you trigger it many times a frame?
#371 posted by necros [99.227.131.204] on 2010/06/21 05:03:00
i don't know why it has to be so complicated, preach... it works fine the way i said. :P no need for force_retouch or anything.
 Monsterous
#372 posted by Preach [77.98.129.22] on 2010/06/21 19:46:02
It's needed in the case of non-player entities which don't link to the world unless they move. If you were trying to detect a monster, you would need the force_retouch. It is more than what mike asked for though, the simple suffices there...
 Oh Right
#373 posted by necros [99.227.131.204] on 2010/06/21 22:56:54
i missed that you were checking for monsters as well.
 Necros & Preach
#374 posted by Mike Woodham [86.178.42.24] on 2010/06/29 20:20:15
Thanks.
 Findradius Vs Find Vs Nextent
#375 posted by necros [99.227.131.204] on 2010/06/29 20:53:37
are there any differences in how these three work?
ie: is findradius really just doing
nextent(e)
if (distance of e < dist), add to .chain
or is it faster?
for example, if i did a findradius(org, 64) where i'm checking only a small radius, is it faster than if i findradius(org, 1024) or is it the same speed?
and if smaller radii are faster than larger ones, at what point does it become better to use nextent rather than a large findradius?
i guess that kind of thing would also depend on the total # of entities as well...
 Finding
#376 posted by Preach [77.98.129.22] on 2010/06/29 21:26:43
ie: is findradius really just doing
nextent(e)
if (distance of e < dist), add to .chain
or is it faster?
It turns out that it does something extra I'd never known about - it skips any entity that's SOLID_NOT(*). Other than that, the algorithm is as you describe, but because it's written in c skipping to the next entity is a single instruction to increment a pointer, etc. So it does run a lot faster than the QC equivalent, but it doesn't do any culling of the entity list based on the bsp tree or anything fancy.
for example, if i did a findradius(org, 64) where i'm checking only a small radius, is it faster than if i findradius(org, 1024) or is it the same speed?
They are the same speed if they contain the same number of entities. Otherwise the cost of adding more things to the chain is incurred, although that's fairly light compared to the rest of the loop.
(*) Also worth noting: the distance is measured to
origin + (mins + max) * 0.5;
not just the origin as you might guess.
#377 posted by necros [99.227.131.204] on 2010/06/29 22:00:07
It turns out that it does something extra I'd never known about - it skips any entity that's SOLID_NOT
haha yeah, i figured that out the hard way. drove me insane for a while. -_-
Also worth noting: the distance is measured to
origin + (mins + max) * 0.5;
not just the origin as you might guess.
didn't know about this bit though. wouldn't have much of an impact unless you had some kind of weird offset bbox but good to know regardless. it does make getting precise findradius distances annoying though. if i did a findradius from one monster origin looking for other monsters, the find would have been completely accurate if it was going from origins and not bbox centers. oh well. :S
 How Difficult
#378 posted by megaman [91.66.119.75] on 2010/07/20 18:57:49
is it to find out what map a given savegame file is for?
Can someone post code / relevant savegame spec? :P
 Not Difficult At All
#379 posted by negke [88.70.243.227] on 2010/07/20 19:19:21
Information on the map and its entities is stored in the .sav file in plain text format.
 You're Like A Savegame Wizard.
#380 posted by necros [99.227.131.204] on 2010/07/20 21:16:01
interesting that savegames store lightstyles though. one would think that's easily gettable from the progs.
 Yeah It's Plain-text Alright,
#381 posted by megaman [91.66.119.75] on 2010/07/20 23:41:57
but the locations in the files seem to change.
I want to parse it with quakeinjector to enable loading savegames for each map, so I'd need to know how to parse the header.
Host_Loadgame_f() in http://svn.icculus.org/twilight/tr...
seems to be the right function (darkplaces), but it's quite complicated and I'm not familiar with the quake src. For example I have no idea what COM_ParseToken_Simple(&t, false, false); does ;-) It would probably take me an hour or so to get what's going on there.
 Wait
#382 posted by megaman [91.66.119.75] on 2010/07/20 23:53:39
is it always on the same line?
#383 posted by metlslime [159.153.4.50] on 2010/07/21 04:12:01
interesting that savegames store lightstyles though. one would think that's easily gettable from the progs.
The current string for each style needs to be saved because there's no post-loadgame callback for entities to set the lightstyle strings again.
 Looks Like It's The 20th Line
#384 posted by mwh [118.92.175.237] on 2010/07/21 06:00:27
save game version, description, 16 parameters, skill and then the bsp name.
 Qc/engine Question
#385 posted by Spirit [80.171.97.116] on 2010/07/27 13:26:52
if a coder specifies a file "tORch.Mdl" would the engine usually look for tORch.Mdl or torch.mdl or what?
 Different Cases?
#386 posted by Preach [77.98.129.22] on 2010/07/27 21:04:34
From what I can glean from a quick browse through the source:
Models in pak files must match capitalisation exactly or they will not be loaded.
Models in the filesystem depend on the operating system's implementation of fopen in the standard c library. I believe this means that unix type OSs will fail to find the file but DOS and windows will succeed, but I can't find any documentation which confirms this for 'fopen'. So where possible make sure the exact case is used.
#387 posted by necros [99.227.131.204] on 2010/07/27 21:05:07
i have had problems with upper/lower case before.
i found it best to avoid uppercase altogether.
for example, ne_tower had a bunch of custom sounds.
when i was developing it, i had all the files loose in folders and setting keys like 'noise' 'necros/someSound.wav' worked fine. when i packed everything into a pak file, someSound could not be found, even though the filename hadn't changed.
 Preach
#388 posted by necros [99.227.131.204] on 2010/08/03 03:49:39
i know you've posted about this before, but i can't find it again :S
basically, there was a faster way to compare distances without using vlen.
was it something like vec1*vec2 > distance*distance ??
 Necros:
#389 posted by metlslime [159.153.4.50] on 2010/08/03 04:19:56
if (vec_x * vec_x + vec_y * vec_y + vec_z * vec_z > distance * distance) {
//temp is longer than distance
}
#390 posted by metlslime [159.153.4.50] on 2010/08/03 04:20:23
er ...
//vec is longer than distance
 Yup
#391 posted by Preach [77.98.129.22] on 2010/08/03 04:39:28
It's almost like that. You can compare the length of a vector v to a distance d with:
v * v > d * d (notice that on both sides of the equation we have the same variable twice)
If you're testing the distance between two positions p1 and p2, you need to take the difference in positions first:
v = p2 - p1;
v * v > d * d;
It works exactly as metl says, but v * v is a single QC instruction which computes the dot product he has expanded out.
 Very Cool
#392 posted by necros [99.227.131.204] on 2010/08/03 06:27:27
thank you!
when you say it's one instruction, i guess doing it as vec * vec is faster then?
#393 posted by metlslime [67.188.81.46] on 2010/08/03 08:18:25
It works exactly as metl says, but v * v is a single QC instruction which computes the dot product he has expanded out.
Ah, good to know.
 Monster_boss Glitch In Progs 1.06?
#394 posted by negke [88.70.91.228] on 2010/08/10 15:06:28
On skill 2, Chthon's aim prediction fails if the player moves towards him when he's about to throw a lavaball. The result is that he throws it backwards instead (or possibly towards worldspawn). It doesn't occur on 1.01.
Is this a known issue - why wasn't it fixed?
#395 posted by necros [99.227.131.204] on 2010/08/10 20:22:44
it's just to do with the math involved to calculate the throwing vector.
because the lavaball moves so slowly (300u/s), if you jump toward chthon, you'd be behind him by the time the projectile would hit you.
i would assume that v1.01 of the progs didn't have chthon's forward projection targetting code.
 Iterative AI And Tracking
#396 posted by Preach [77.98.129.22] on 2010/08/11 00:04:10
The algorithm is:
1) work out the time it would take to hit the current player position
2) predict where the player will be at that time
3) set the target position to be that spot
There's a logical flaw in this scheme. The time it will take the projectile to reach the new spot will be different to the time it takes to reach the current spot, so there's no guarantee that the new aiming vector will be any better. This would not be significant with a fast projectile, as the two travel times would likely be very similar, and the size of the player's hitbox is a large enough margin of error.
However, the slower the projectile moves the worse the situation gets, compounding the problem necros describes. Moving towards the origin of the shot combines these two problems, and I'll try to give an example of the worst case. The player is moving towards chthon from 600 units away (2 seconds of flight time). We imagine that the player is moving at just over 300 u/s (the player's top speed is somewhere around 400 u/s). This means that in two seconds time, the player would be just behind the knuckle that the rock is thrown from. The projectile is then thrown directly backwards and misses completely.
We realise that this target point is stupid for a few reasons:
• The player will likely not run straight at chthon for two seconds
• More importantly, the player cannot run through the solid body
• Even more importantly, the rock will sail past the target point before even a fraction of a second passes. Even if the player could and did continue exactly to the predicted spot the shot would still miss!
This last problem is something we can fix with iteration! The idea is that we want to calculate a point where the time the rock will take to reach it roughly matches the time the player would take to reach it. If the player is running towards Chthon then this point will be roughly halfway between the two. The iteration is based on
• Taking the current target point
• Calculating the time to strike that
• Calculating a new target point from the predicted position the player would be in at that time
• Feeding the new target point into the top.
In our worst case scenario we described, we'd start with a target point of the player himself. Then we'd get the point right next to the knuckle with a very short flight time, so our third point would be very near to the player, but a bit closer to Chthon than point 1. After a bit of ping-pong of points that are too near to each end, we should work into the middle.
This is quite nice, and I like iteration in AI because you can do one iteration in each animation frame leading up to the shot. It creats an interesting, slightly unpredictable thought process which keeps taking on updates as the world changes. It's worth noting that this actually offers better prediction in other cases as well.
An alternate method for a more reliable forwards-travelling shot would be this: Take the offset of the new target point from the player, a vector called o.
Apply the following equation:
o = o - (o * v_forward)* v_forward;
This removes the component of o in the direction Chthon is facing. Finally, make the target point equal to the player's origin + o. Not as elegant, but perhaps slightly easier.
 <-- Chthon Lavaball
#397 posted by necros [99.227.131.204] on 2010/08/11 00:39:58
that is... fucking cool, man.
 Heh
#398 posted by meTch [99.103.109.226] on 2010/08/11 02:05:36
I wondered why, when I go to hit the button that if I moved a step backwards and then over and than towards him as I stepped on the button I was almost always ensured not to be hit, perhaps I should have looked at him once or twice.
#399 posted by necros [99.227.131.204] on 2010/08/11 04:43:25
if the original chthon fight hadn't been so gimmicky, it would have been a cool trick akin to shambler dancing or nails + fiends.
it's sad that you barely even have to look at chthon to defeat him. :\
yes, i am bitter. it's a great model and texture. yet, so poorly used. :(
the same could be said for shub.
#400 posted by gb [89.27.241.103] on 2010/08/11 17:49:07
killable Chthons are en vogue lately.
 Probably A Silly Question...
#401 posted by necros [99.227.131.204] on 2010/08/18 22:35:28
when multiple entities a touching another entity with a .touch function, each entity runs the .touch with itself as other right?
 Touching
#402 posted by Preach [77.98.129.22] on 2010/08/19 23:09:26
Yeah, although you have to be careful with entities that aren't moving. For example, monsters at rest inside a trigger don't generate touch events, since the monster only checks for collision when moving, and the trigger never does. This is where force_retouch is required. All entities use the same code for checking collisions though, and it is a combination of descending recursively down the tree of nodes intersecting the 'self' entity, along with a for loop to test all the entities within the current node.
#403 posted by necros [99.227.131.204] on 2010/08/20 21:10:45
thanks yet again, preach :)
i was thinking maybe i should go through all your posts in this thread and make like a 'preach's guide to arcane quake facts' :P
 Well
#404 posted by Preach [77.98.129.22] on 2010/08/21 02:49:06
I do have a fun fact about that fun fact: it implies that in a multiple collision, the order that the collisions are resolved is essentially unpredictable - they depend on the location of the entities within the bsp tree that comprises the level.
Speculation: it might be possible to exploit this to glean some information about the bsp tree from QC - using force_retouch with a trigger and some point entities spawned in a region. Knowing that one point is in a leaf higher up the tree(or further left) than another may not be incredibly useful though...
 Path_corner Weirdness...
#405 posted by necros [99.227.131.204] on 2010/09/04 22:25:12
void() movetarget_f =
{
if (!self.targetname)
objerror ("monster_movetarget: no targetname");
self.solid = SOLID_TRIGGER;
self.touch = t_movetarget;
setsize (self, '-8 -8 -8', '8 8 8');
};
void() path_corner =
{
movetarget_f();
};
quoted is the path_corner entity. unlike every other entity, path_corner defer's it's setup code in another function.
as i'm passing by, i frown at it, comment out movetarget_f and just paste the code into the path_corner function and grin at my cleverness.
suddenly, none of the path_corners work anymore. so i o_O and put it back the way it was. of course, i'm left with the question of why? it's just an entity with a touch function.
 Path_corner
#406 posted by Preach [77.98.129.22] on 2010/09/05 20:11:28
I couldn't reproduce this - the monsters seemed to follow their usual paths when this was the only change made to the vanilla source code. Any chance that something else changed at the same time?
#407 posted by necros [99.227.131.204] on 2010/09/05 20:26:56
well... i dunno but it works now...
new comment:
//deprecated. why was it even like this to begin with? (not deprecated, it stopped working when i moved this down into the path_corner function o_O) un-not-deprecated: it ended up working now. o.o
 Hmm
#408 posted by nonentity [188.223.82.21] on 2010/09/19 14:42:31
Just bumping for easier location by new users (coding scares me way to much to be in here for any other reason ;)
 Self
#409 posted by necros [99.227.131.204] on 2010/09/22 22:25:51
if you change 'self' in a think function and forget to change it back, what happens? (beyond just the operations in the same think function)
does it mess up the engine's think iteration as it goes through the list of stuff?
what about entity links? can something cause a .entity link to get broken?
i'm having some weird problem with entities randomly getting messed up but it's so random that tracking it down is proving quite annoying.
 Selfless
#410 posted by Preach [77.98.129.22] on 2010/09/22 22:42:42
The 'self' that you can access from the qc side is not used (at least in the standard source) in any of the engine code, it always begins with it's own reference to an entity passed as a parameter, and then set in the global_qc_self variable (not it's actual name in the source). So I don't think that could be your problem.
Is it possible that some of the entity links you have run through entities which are removed? The quake engine has a 'lazy remove' paired with a 'thorough spawn' function. Only 5 or 6 entity fields are cleared on removed entities, just enough that they are no longer transmitted to clients. When the entity slot is reused, the spawn builtin goes through and zeroes all of the fields.
Although it makes sense to not go to the trouble of zeroing all of that memory until necessary from a performance point of view, it can make bugs intermittent. If your code relies on a reference to an entity which has been removed, then it will more than likely perform correctly until something uses that entity slot. The trouble is now that the cause of the bug is separated from it's first effect by an unpredictable length of time, making it very hard to diagnose.
#411 posted by necros [99.227.131.204] on 2010/09/23 00:37:22
i use
if (self.someEntityLink)
{
self.someEntityLink.think = SUB_Remove;
self.someEntityLink.nextthink = 0.1;
}
which i felt was pretty safe.
the problem seems to be centered around accelerating movers.
i'm still using helper entities to control velocity but it seems as if after a few minutes a helper will just become removed (when i check the helper's edict it is either free or a new entity).
however, this also happens with a custom lightning entity. the custom lightning entity spawns a chain of models to simulate the lightning effect and cleans them up when the effect is over. somehow that master entity just dies sometimes leaving the models in the game.
both of those entity's think loops seem to be solid so i can only guess something outside of their thinks are killing them.
 The Debugging Wrap
#412 posted by Preach [77.98.129.22] on 2010/09/23 11:12:04
You could put a wrapper around the remove function to try and work out exactly what is removing them. In defs.qc rename the builtin definition #15 to
void(entity e) remove_builtin = #15;
You can then define a function called remove which has extra behaviour before calling remove_builtin. I'd probably add a boolean field to the entities you want to monitor.
.float donotremove;
void(entity e) remove =
{
if(e.donotremove)
{
dprint("Entity of class '");
dprint(e.classname);
dprint(' was removed!);
}
remove_builtin(e);
}
This doesn't give you much debugging information though, it still requires you to guess what just happened. It would be better to get a stack trace, but the only way to get one of those is to crash the map. I'm not sure how much of a stacktrace you get by calling error() or objerror(e). I do know that you'll get one by creating a runaway loop though, so something like:
void(entity e) remove =
{
if(e.donotremove)
{
eprint(e);
while(0){};
}
remove_builtin(e);
}
That ought to give you plenty of information as soon as one of these entities gets removed. Just remember to clear the donotremove flag before actually removing them! Also, it should be obvious that this kind of code shouldn't go in a release build...
 Oops
#413 posted by Preach [77.98.129.22] on 2010/09/23 11:13:30
That of course should be while(1) in the code above, so that it loops forwever rather than not at all.
#414 posted by necros [99.227.131.204] on 2010/09/23 23:11:16
that's a great idea, thanks! maybe i should create a wrapper for spawn() so that donotremove is set to true by default, or do the inverse have it as 'safetoremove' defaulting to 0.
anyway, nice idea to abuse the stack trace engine feature. ^_^
it's like teaching progs new tricks but for engines. :P
 Donotremoveanything
#415 posted by Preach [77.98.129.22] on 2010/09/23 23:30:58
I was figuring you'd only apply donotremove in the spawn functions of the two entities you were trying to debug, otherwise you've got to worry about your game crashing every time a missile hits a wall and stuff! Wrapping spawn and remove is a really great trick with lots of uses, I'm really grateful to frikbot for showing me the trick of redirecting builtins.
 More Weirdness
#416 posted by necros [99.227.131.204] on 2010/09/25 00:42:31
you know how, when you're riding a platform that's moving upward, your viewpoint sort of sinks down a bit?
currently, console prints (like bprints or stuffcmd bf) are causing the view sink effect to reset each time. every time there's a bprint, the view height resets to the standard height and starts to sink again.
i have the feeling i've fucked something up pretty badly. :P
 Oh O.o
#417 posted by necros [99.227.131.204] on 2010/09/25 01:21:56
apparently, the bprint thing is happening in stock quake too. this must be some setting in fitzquake because it doesn't happen in aguirre's quake.
will investigate... o.0
 Uh Oh...
#418 posted by metlslime [159.153.4.50] on 2010/09/25 02:10:26
#419 posted by necros [99.227.131.204] on 2010/09/25 05:57:50
i'm still unsure exactly what's causing it... if someone wouldn't mind checking this out, an easy way to test this is to bind a key to 'god' and then load up e1m1. hit the button for the first lift and let it lower.
when it starts to raise back up, repeatedly hit the key you bound to god mode. each time the screen should reset to the normal view height before sinking back down.
this happens in fitzquake and quakespasm.
 Incidentally
#420 posted by necros [99.227.131.204] on 2010/09/25 06:00:52
i only noticed this happening because i have some debug code that outputs a ton of text repeatedly to the screen. with that many outputs, the screen practically shakes when riding a lift up.
it's not like it's really a big deal since when you play normally outside of developer 1 mode, you don't get the outputs and so don't get the shaking.
on a possibly(?) related note, monsters riding a lift up appear to not interpolate their positions if they are walking on the platform?
#421 posted by metlslime [67.188.81.46] on 2010/09/25 19:19:38
on a possibly(?) related note, monsters riding a lift up appear to not interpolate their positions if they are walking on the platform?
yeah, I noticed this recently. Must fix!
As for the view jerkiness on lifts... I've seen this but it didn't notice a connection to console prints. Will investigate.
#422 posted by necros [99.227.131.204] on 2010/09/25 21:41:16
possibly helpful:
it is not strictly bprints that cause this. when i turn the console spam off, every so often the view will jerk rarely.
 Engine/editor/compiler Coders Please Help!
#423 posted by than [180.146.63.212] on 2010/10/03 19:17:48
I got this urge to try and write my own Quake map editor for fun (don't hold your breath) and I am already stuck at the first hurdle (for this reason).
I don't know why, but I thought I would try and load a wad file first. Just so I understand the format I thought I'd read about it and implement it myself. I've also got the code from the engine to look at, so it's not super difficult, but I am having a weird problem.
When trying to read the individual lumps that contain the image data for textures, if I try and read the width or height value I just get nonsense UNLESS I add a weird 16 byte offset to the pointer I am using.
example:
LUMP_t* lump = (LUMP_t*)(waddata + lumpinfos[lumpno].filepos + 16);
cout << "width: " << lump->width << ", height: " << lump->height << "\n";
will output the correct width and height, but if I remove the 16 byte offset, it doesn't work and I get gibberish.
My problem is that I have no idea why I need to add the 16 byte offset, and am worried that it might cause problems down the road if I don't understand why it's there.
Aside from that offset, everything seems to be as I expected.
 Than
#424 posted by SleepwalkR [130.149.148.83] on 2010/10/04 12:03:09
My tip: try and implement some simple editing functionality as quickly as possible because that will keep you motivated. Don't write struts code like reading wad files. Write your internal model for brushes and write a simple renderer for that. Then, improve on that code. If you have to write code that doesn't draw anything on screen for weeks, you will very likely lose your motivation quickly.
 Sleep
#425 posted by than [182.164.25.24] on 2010/10/04 13:00:37
thanks for the tip. I know it can be really boring to write code that doesn't draw stuff, but for some reason I like writing file loaders and figuring out how stuff works. My plan for tonight is to get wad textures displaying on screen and then I want to go onto the exciting brush stuff and improve my 3d math knowledge (I forgot most of it... oops).
I don't think I will be able to write an editor as awesome as Willem's Toetag, but if I get even 1/4 of the way there, I will have learnt a lot :)
 One Tip
#426 posted by SleepwalkR [130.149.148.83] on 2010/10/04 14:10:44
Brushes are represented in map files as the intersection of a number of half space equations (each face is such an equation). You must convert this representation into a representation that you can use to draw stuff, i.e. polygons which you send more or less directly to OpenGL. There are several techniques to do this by calculating the extreme points of the solution of the original system of (inequality) equations. These techniques are computationally optimal, but very hard to implement correctly. I suggest you don't touch that stuff for your first version.
Another very simple way of converting a brush to polygons that you can draw is to start with a cuboid polyhedron (represented by its vertices and edges) with maximum dimensions and then simply split the brush along the planes which are defined by the faces. This is easy to do because you just have to intersect all edges of the polyhedron with the splitting plane. You'll end up with a polyhedron that has the same shape as the brush, but you can easily determine the face polygons from that data structure. This is computationally inefficient, but since you are not dealing with a lot of data, it's fast enough.
Another tip is to use the vertex / edge representation only for rendering, not for manipulation of the underlying brushes. The brushes should remain in their original representation and should be manipulated in that representation as well, This way you can use the very high precision that the half equation intersection representation offers (and don't have to convert float vertices back to int).
 Sleep: No No
#427 posted by bear [217.115.56.186] on 2010/10/04 17:50:49
implement rounding errors or some other form of sloppy calculations so it will feel just like using qoole!
 Thanks For The Tips
#428 posted by than [182.164.25.24] on 2010/10/04 18:33:43
I got my wad loader working ok and it renders textures from a 570 texture wad just fine. The code is a total mess and I still don't know what that weird 16 byte offset is, but I got Quake textures displaying on screen in sexy Quake colours, so I'm happy for today. Next step will be a map loader :)
I made a map compiler (not to bsp, but to a much simpler format that just uses polygons) before, so I still have some code that can create polys from brushes somewhere. I was going to use that. Pretty sure it uses the clipping method. The first solution sounds difficult :)
this?: http://en.wikipedia.org/wiki/Conve...
Math on wikipedia is always extremely hardcore sounding.
 Just Had A Look
#429 posted by than [182.164.25.24] on 2010/10/04 18:48:45
at my old brush compiler code and it appears to take a series of 3 planes to generate a vertex and then adds the generated vertex to a vertex list for the each face involved in its calculation. It iterates through this until it's done and looks very inefficient.
But if it works, I don't care :)
When editing a map, most of the brushes just sit there unselected. I would imagine the average user doesn't touch more than a handful at once, and even then most of the operations are probably moves.
 Probability Of Attack
#430 posted by necros [99.227.131.204] on 2010/10/05 20:07:17
anyone who's studied this could answer a question?
if i've got a monster who has an 80% chance to attack when his checkAttack function is called:
if (random() < 0.8)
attack!
does that really mean he has an 80% chance to attack at all times? or is it more like 10.7% chance to attack every second? (0.8 ^ 10 since animation rate is 10fps)
probability has always been pretty impenetrable for me. :S
as a result, i tend to rely much more on 'cooldown' (attack_finished) timers to regulate monster attack rates.
 Geometric Distribution
#431 posted by Preach [77.98.129.22] on 2010/10/05 21:53:02
The code you posted means he has an 80% chance to attack each frame. One of the best ways to understand what this means is to look at the probability that the monster will start attacking before the 2nd, 3rd, 4th frames etc.
The easiest way to do the calculation is to flip the question on it's head. What is the probability that we are still not attacking on the 2nd, 3rd, 4th frame? For that to be the case, we must have failed to attack in all the previous frames, and the chance of that is 0.2 each time. Since each trial is independent, we can multiply them together, as follows:
Frame 2: 0.2 * 0.2 = 0.04. 4% chance of not attacking after the second frame
Frame 3: 0.2 * 0.04 = 0.008. A 0.8% chance of not attacking on the third frame or earlier.
Frame 4: 0.2 * 0.008 = 0.0016. Scarcely a 0.1% chance that we are not attacking 4 frames of considering it.
So it's pretty certain we'll attack within the first 3 frames, our code is largely creating variety in behaviour over a range of just 0.3 seconds. Even if we changed the probability to 0.5 we'd still expect the monster to attack quite quickly: 1 / (2^10) is the probability that we wouldn't have attacked after 1 second - which works out at > 0.1%.
Using attack_finished as a cooldown is probably the best way to to long pauses out of your monsters, if you want them to choose between attacking with a missile or closing the distance with the player.
 Yeah
#432 posted by necros [99.227.131.204] on 2010/10/05 23:25:20
that makes a whole lot more sense. i confused myself over why the chance to attack would go down over time, which is just stupid because every time we check, we are giving a new chance to attack.
 Quick Question
#433 posted by than [180.147.88.240] on 2010/10/07 16:21:16
I have a feeling I could find this if I dug through the Quake source, but are the lengths of key/value pairs for map entities limited to 16 and 32 chars respectively? If not is there some other limit? Does anyone know.
On top of that, if anyone happens to know the max number of:
entities in a map
faces per brush (guess this is compiler limited)
key/value pairs per entity
I'd love to know them :)
 ^^
#434 posted by necros [99.227.131.204] on 2010/10/07 19:04:03
 Thanks
#435 posted by than [180.147.84.55] on 2010/10/08 00:20:21
1024 seems quite a lot. I'll just get rid of the limits I put on my map loader.
Texture names do appear to be limited to 16 chars though.
#436 posted by metlslime [159.153.4.50] on 2010/10/08 00:39:27
Texture names do appear to be limited to 16 chars though.
15 chars unfortunately, since you need a null terminator.
#437 posted by Spirit [80.171.82.183] on 2010/10/08 16:25:55
This is not QuakeC related. The answer is hopefully simple algebra but I am too stupid to figure it out for myself and Google is not helping.
Let's say I have these 2D coordinates:
1) 10;10
2) 54321;12345
and want to fit them into a smaller (let's say 800x800) window. How would I code that? Do I really need to make an affine transformation? If yes, no need to explain the how, I should know that.
 ";" Is Not A Name
#438 posted by jt_ [24.11.39.160] on 2010/10/08 22:57:46
frikqcc keeps complaining about ";" not being a name in the following function, but I can't any ;'s being used as names, or any stray ones that might being messing with things. This is just the BackpackTouch func from progs106
<code>
/* PLAYER BACKPACKS */
void() BackpackTouch =
{
local string s;
local float best, old, new;
local entity stemp;
local float acount;
if(other.classname != "player")
return;
if(other.health <= 0)
return;
acount = 0;
sprint(other, "You get ");
if(self.items)
if((other.items & self.items) == 0) {
acount = 1;
sprint(other, "the ");
sprint(other, self.netname);
}
// if the player was using his best weapon, change up to the new one if better
stemp = self;
self = other;
best = W_BestWeapon();
self = stemp;
// change weapons
other.ammo_shells = other.ammo_shells + self.ammo_shells;
other.ammo_nails = other.ammo_nails + self.ammo_nails;
other.ammo_rockets = other.ammo_rockets + self.ammo_rockets;
other.ammo_cells = other.ammo_cells + self.ammo_cells;
new = self.items;
if(!new)
new = other.weapon;
old = other.items;
other.items = other.items | new;
bound_other_ammo();
if(self.ammo_shells) {
if(acount)
sprint(other, ", ");
acount = 1;
s = ftos(self.ammo_shells);
sprint(other, s);
sprint(other, " shells");
}
if(self.ammo_nails) {
if(acount)
sprint(other, ", ");
acount = 1;
s = ftos(self.ammo_nails);
sprint(other, s);
sprint(other, " nails");
}
if(self.ammo_rockets) {
if(acount)
sprint(other, ", ");
acount = 1;
s = ftos(self.ammo_rockets);
sprint(other, s);
sprint(other, " rockets");
}
if(self.ammo_cells) {
if(acount)
sprint(other, ", ");
acount = 1;
s = ftos(self.ammo_cells);
sprint(other, s);
sprint(other, " cells");
sprint(other, "\n");
// backpack touch sound
sound(other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM);
stuffcmd(other, "bf\n");
// remove the backpack, change self to the player
remove(self);
self = other;
// change to the weapon
if(!deathmatch)
self.weapon = new;
else
Deathmatch_Weapon(old, new);
W_SetCurrentAmmo();
};</code>
 Deception
#439 posted by Preach [77.98.129.22] on 2010/10/09 00:14:24
The error message is true but unhelpful. It's trying to use a ; as a name, but what that means is that it's reading a line in a way you didn't intend. Often you get unexpected parsing errors when you leave a function earlier or later than expected. In this case you're missing the closing braces for the self.ammo_cells block, and so it's reading some further code incorrectly.
I know that func_ eats indentation(unless you're really patient with inserting nbsp character entities) so I can't say if missing indentation might have made this harder to spot. But I can recommend editing code in something that can highlight matching braces in some way - I pasted the code into notepad++ to check the matching. I will concede that knowing it could be a mismatched brace based on the error was really key, so it's not all software solutions.
 Hat Tip
#440 posted by jt_ [24.11.39.160] on 2010/10/09 00:32:49
That was it, thanks Preach. In my editor (acme) everything is indented fine, but as you said func_ at them. Wish func_ has support for <code></code> tags.
 Don't Forget To Spellcheck...
#441 posted by jt_ [24.11.39.160] on 2010/10/09 00:33:58
s/at/&e
#442 posted by necros [99.227.131.204] on 2010/10/09 02:23:15
a preformatted tag would be nice, but that would likely break the forum width?
mind you, the forum could probably get twice as wide without bothering anyone.
#443 posted by Spirit [80.171.27.31] on 2010/10/09 08:23:06
No, absolutely not. Apart from the colours and no-clutter, the 72-80 character line length ensures the great readability of func. Forums that spread to full-width (or anything remotely like that) are an abomination.
Use http://www.inside3d.com/pastebin.p... , that even gives you syntax highlighting. Also be aware that func has a preview button. ;)
 Yeah
#444 posted by RickyT23 [82.26.222.218] on 2010/10/09 14:59:28
No offence to Quakeone.com for example, but having like more than 12 words on a line is a bit of a no-no. Just hurts my eyes :)
 Good Point
#445 posted by ijed [190.22.73.244] on 2010/10/09 17:45:18
 Ok...
#446 posted by necros [99.227.131.204] on 2010/10/09 19:37:21
 Ropes...
#447 posted by necros [99.227.131.204] on 2010/12/07 02:45:52
so i was wondering... how hard would it be to create some kind of dynamic rope thing in QC?
i'm thinking, you would put down a func_rope and then specify the length of the rope. then target at an info_notnull.
for the code, i first thought it would be pretty easy, all you'd have to do is first spawn an entity chain and then set each of the nodes in the chain to obey quake gravity.
then, all you'd need to do is check every frame to make sure the next link hasn't moved out of range and if it has, move it back along a straight vector.
but then i started thinking that you can't just iterate forward through the chain because you would have two anchored positions with only the center moving freely and then my head exploded. :(
 Oh
#448 posted by necros [99.227.131.204] on 2010/12/07 02:48:37
and this is the best part: i started trying to code it because i was too lazy to make brushwork wires in my map. ¬_¬
#449 posted by metlslime [159.153.4.50] on 2010/12/07 03:50:25
ropes are easy enough, just set up a havok-like physics system and include inverse kinematics, then set up the constraints between your rope links, and mark the two ends as unmoveable. :)
 You Have To Burn The Rope From Both Ends At Once
#450 posted by Lardarse [62.31.162.25] on 2010/12/07 03:52:34
With two anchor points, you have the trail of gravity come from both at once. The only tricky situation is in the middle.
 Actually....
#451 posted by metlslime [159.153.4.50] on 2010/12/07 03:55:34
if all you want is a rope that doesn't collide with anything, including itself... it's doable.
To solve the rope position for a static rope, you just set it up at spawn: guess and then iteratively refine the locations of all entities until it's within a certain tolerance.
If you want it to be dynamic, you don't need iteration, but you give the rope some elasticity and then have it accelerate to the correct position every frame. With the right values, it will look pretty good. Otherwise it will be bouncy and rubbery and go crazy.
 Of Course...
#452 posted by metlslime [159.153.4.50] on 2010/12/07 03:58:08
You also need to render the rope. I guess you would use a model that represented a segment of rope X units long. Whenever the distance between segments is not X, you will see gaps or overlap. Or have a bunch of frames in the .mdl to represent a range of distances, and quakec can select the frame based on the actual distance between joints on that frame.
 Sounds Cool
#453 posted by ericw [206.75.128.68] on 2010/12/07 04:00:43
Here's one tutorial I found, not sure how easy it would be to implement in quakec though: http://freespace.virgin.net/hugo.e...
#454 posted by mwh [202.124.96.158] on 2010/12/07 05:12:42
If it's static, a rope hangs in a cosh curve ( http://en.wikipedia.org/wiki/Caten... ). You can just calculate it with a scientific calculator I guess :-)
 Ropey Mechanism
#455 posted by Preach [77.98.129.22] on 2010/12/07 21:10:04
The thing I'd want the most from creating a good rope system is just frustratingly out of reach of the QC - to be able to control endpoints of a polygon independently of each other. The use would be to attach some end of a polygon to one "entity" and the other end to another, so you could create a continuous mesh that you could deform individual segments of.
Without that, I'd say you're better off just creating a static mdl prop to represent a rope. One trick that I think could work well in a map is creating a reaction to "wind", and ropes would be a great prop for displaying it. The idea is to have a global wind variable which stores a value between say 0 and 40 representing the wind force in that frame.
You'd want a slow random walk which would move the wind through these values, it's possible that adding crandom()(sic) to it every 0.1 seconds and capping the value within the range would suffice. Scaling the adjustments by frametime(and then back up with a larger constant) would allow you to recalculate the wind strength every frame which might improve the animation. It might also benefit the model to make it more likely to move the wind towards the middle values than the extremes.
You then need props through your level like ropes, "ye olde inne" signs, torches, lanterns and flags which are specially designed to react to the wind. They might be give specially designed models which have frames from 0 to 40 corresponding to the strength of the wind. Then they would only need to have think functions which regularly update the entity's frame to match the wind in that frame, maybe with some jitter.
In some cases, like the sign at the inn, you might only be going for simple rotation back and forth. Then you would be better served using .angles rotation since some engines transmit it with higher precision, and you would avoid the floating vertices rotating a sign two degrees each frame is bound to produce. You would also not need to use granular values between 0 and 40, but just use the floating point wind value directly to calculate the angle.
Then just add some howling wind and creaking timber sounds and your windswept landscape is complete!
#456 posted by necros [99.227.131.204] on 2010/12/08 01:41:38
i was actually not going to bother continuing but those posts kind of encouraged me to at least try...
ended up with this:
http://necros.quaddicted.com/temp/...
each segment is 16-32 units long (they contain 16 frames in 1 unit long increments).
each frame, i iterate through the chain and add a velocity vector pointing towards both the next and previous points.
this works somewhat, but you have to tweak the tension (speed multiplier of the velocity vector) or it jitters a lot.
it also doesn't really work with longer ropes because you need super high tension to keep it from falling apart but those levels of velocity cause excessive jittering.
so yeah, with the expense of iterating the chain every frame and needed many segments with 1 entity each, it just doesn't seem worth it.
if i do try to continue with ropes generated at run time, looks like i'll have to look into static ropes, probably with a cosh function like mwh posted (thanks for that).
it's a shame though because i had hoped to get the ropes to react to rockets passing by and explosions. oh well. :S
 Just Noticed Another Problem
#457 posted by necros [99.227.131.204] on 2010/12/08 02:06:59
if your FPS drops, the required velocity to keep the segments together becomes increasingly larger. at some point around 30-45 fps, the velocity is too high and the chain breaks.
 HeLp ?
#458 posted by delore [151.66.161.90] on 2010/12/12 17:35:41
..my idea is to have in my map many different skinned monsters per class (4 or 5 soldiers with different colors, 4 or 5 knights, ..)
Easiest way to achieve this ?
-I'd happily skip the creation of every different skinned monster in qc code(since I want to change only clothes color for each!)
#459 posted by negke [88.70.92.65] on 2010/12/12 19:18:17
Store the new skins in the mdls; give the monsters a "skin" "#" field in the map (# being the index number of the respective skin).
 Ffs Don't Crosspost
#460 posted by negke [88.70.92.65] on 2010/12/12 19:19:36
#461 posted by necros [99.227.131.204] on 2010/12/12 19:20:31
there's 2 ways to create reskin monsters.
1. Create all new .qc files for each reskin, using the same frame macros but renaming the function names (so they don't conflict at compile time).
2. Integrate all the types of monsters into the same original monster code so that at key points in the code, it checks to see what type of reskin it is and then behave different accordingly.
method 1 is the easiest to understand and and read because everything is seperated into individual files. the monsters will adhere to the standard monster coding setups.
method 2 is the easiest to code because you don't have to rewrite anything and just add in little snippets for things like different attacks or a different ai_run routine.
also of note, a lot of the behaviour is controlled by the checkattack function, so simply having a seperate one of those can help distinguish between reskins.
if you're planning on doing this much coding, you should probably check out inside3d. they are more focused on coding while this board is more focused on mapping.
#462 posted by necros [99.227.131.204] on 2010/12/12 19:21:10
re 460: clairvoyant? o.0
 Oh Nm
#463 posted by necros [99.227.131.204] on 2010/12/12 19:27:15
i see what you meant now.
also: http://www.youtube.com/watch?v=21D...
not sure what those 'shivers' are... i can't only guess it is a symptom of irregular framerate. a momentary dip would cause it, i suppose, although i thought i fixed that. :P
also, i obviously need to find some way of adding in damping of some sort because those things will bounce around for ever.
 Lost Chapters Src
#464 posted by jt_ [24.11.39.160] on 2010/12/18 07:44:05
Does anyone have/have a link to the lost chapters src from qexpo? http://qexpo.quakedev.com/booths.p... doesn't go to the page. I've seen it linked somewhere here, just can't find it..
#465 posted by gb [89.27.197.65] on 2010/12/18 09:54:45
 Ty
#466 posted by jt_ [24.11.39.160] on 2010/12/18 14:15:49
#467 posted by necros [99.227.131.204] on 2010/12/19 01:45:59
http://necros.quaddicted.com/temp/...
another program with debatable usefulness. :P
#468 posted by necros [99.227.131.204] on 2011/01/15 04:46:36
is it possible to determine if the player is inside a triangular space with just the info of the 3 vertices and discounting vertical axis with QC?
making a monster that traps the player inside a triangular area but it would be awesome if the monster could tell if you were actually trapped (inside) or not.
#469 posted by necros [99.227.131.204] on 2011/01/15 04:49:54
god damn it...
as usual, after thinking about it for a few minutes, i figure it out a few seconds after posting.
i can just use the dot product of normalized vectors from the origin vertex.
we need a delete button. :P
 Rapid Fire Round
#470 posted by Preach [62.30.197.42] on 2011/01/16 13:25:57
Ok, I've got an idea to share with y'all about making QC events that happen at high frequencies cope with the framerate dropping below that frequency. But in order to make it a bit different, I'm going to set a bit of a puzzle about it first, which comes as a piece of code and three questions:
void() generic_think =
{
// do some kind of thing which needs to
// occur rapidly
self.nextthink = self.nextthink + 0.05;
self.think = generic_think;
if(self.nextthink <= time)
self.think();
}
• For partial credit, what is this code trying to do?
• For full credit, why will it fail?
• For extra credit, what can we do to fix it?
#471 posted by necros [99.227.131.204] on 2011/01/16 19:56:35
it looks like you're trying to get the think function to recursively take care of missed thinks.
i've never thought of doing it this way. usually, if i have a think function that needs to be accurate across inaccurate think times, i just use something like this:
void() thinkfunction =
{
fraction = (time - self.ltime) / 0.05;
self.ltime = time;
self.nextthink = time + 0.05;
self.think = thinkfunction;
}
and just scale whatever it is i'm doing with fraction.
doing it recursively would be easier to code, i guess, but i would think much more expensive on operations.
 Tail Recursion After This Message...
#472 posted by Preach [62.30.197.42] on 2011/01/16 22:59:49
That's exactly the point of the code, so here's what goes wrong with it:
The assumptions behind this bit of the code are that the QC global time stores the current server time, and self.nextthink stores the time we wanted the think to occur. One of these things is often not the case, and the other is always incorrect.
If you run a dprint on self.nextthink during a think function (but not an animated think function) you will find that nextthink is equal to 0! It's always reset by the engine, so that unless you explicitly call for a new nextthink, the engine can skip over thinking. Conversely, nothing happens to the think field when you run a think function, so if you're optimising QC code for a looping think function, you can remove the bit where you reset self.think in each call safely.
So that's the assumption that's always wrong, that the nextthink time you originally set is preserved in that variable when the think function eventually runs. But is the information lost forever? Not always! The QC variable time is set to either the current server time, or the actual nextthink time which WAS set - whichever is the largest. So as long as you don't ever set nextthink to be smaller than the server time + frametime (which calculates the server time for the NEXT frame), you get the information back.
Luckily never doing that is the point of this whole missed-think-avoidance code. All we need now is to find out the true server time during our think. This is easy though, as we just create a new global called servertime. We then use StartFrame to set servertime = time, and then refer to that in our calculation.
Our code then looks like:
void() generic_think =
{
//do stuff
self.nextthink = time + 0.05;
if (self.nextthink < servertime + frametime)
self.think();
}
As a closing remark, this function has tail recursion, so you don't actually have to fill up the callstack repeating it over and again. Just wrap it in a do...while loop for performance.
And there we go, a way to ensure that a given function executes 100 times in a second, regardless of the framerate of the server.
#473 posted by necros [99.227.131.204] on 2011/01/17 01:37:55
clever! :) i had no idea about time being set to nextthink.
also, you could just make a new .nextthink2 var:
self.nextthink = self.nextthink2 + 0.05;
this neatly avoids the engine resetting nextthink.
 Help With Those Damn .lmp Files !
#474 posted by delor3 [151.66.165.238] on 2011/01/30 14:01:23
Hi,
how can I modify those .lmp files included in id/paks ?
my goal is to have a new hud for my mod (at least change color and quake guy face !)
#475 posted by necros [99.227.131.204] on 2011/01/30 19:32:40
adquedit can import bmp and pcx (i think?) and convert them to lmp.
 Lmp2pcx
#476 posted by jt_ [24.11.39.160] on 2011/01/30 19:57:22
Or is it pcx2lump..i have one of them on my laptop, i can see if i can dig it up.
 QuakeC Source Code Licensing Status
#477 posted by metlslime [159.153.4.50] on 2011/03/01 20:12:04
So what's the licensing status of the quakec source code? I was planning on releasing the rubicon 2 source, but not sure what license i can put on it (e.g. GPL)
I know that the original source release was sort of an informal "you can make quake mods with it" type license, but not sure if there was a more recent GPL release of the same code. And whether it applies to hipnotic code as well (since i'm using the hipnotic rotating code.)
 Useless Qc Observation Of The Day
#478 posted by Preach [62.30.197.42] on 2011/03/18 11:03:45
It turns out that the builtins floor, ceil and rint are not as efficient as abusing the bitwise functions.
So if you're optimising a tight loop with a call to floor(x) you can instead substitute:(x | x) which does the same thing. Similarly ceil(x) can be replaced by ((x | x) + 1). rint(x) is a little more complicated to replace, it takes two statements:
x = x + 0.5;
(x | x);//is now the same as rint(x) before the first line
Note that you can't make a helper function like
float(float x) rint2 =
{
x = x + 0.5;
return x | x;
}
- because the efficiency saving arises from avoiding the function call overhead, and you waste that by making it a function instead. Also, reducing the number of instructions is only really worthwhile in a tight loop that might trip the runaway-loop counter. Hence this being the useless qc observation of the day...
 Except That
#479 posted by Lardarse [62.31.162.25] on 2011/03/19 04:40:22
((x | x) + 1) is incorrect for ceil() as if x==floor(x) then you get a number that's 1 higher.
 True Dat.
#480 posted by SleepwalkR [85.178.118.246] on 2011/03/19 07:25:11
 Man
#481 posted by necros [99.227.131.204] on 2011/03/19 07:31:39
i didn't understand any of that. :(
what is |? i only know it's the bitwise add operator. never heard of using it with normal numbers.
 | Is Bitwise OR
#482 posted by Lardarse [62.31.162.25] on 2011/03/19 08:01:46
However, since it can only work with integers, it floors each number before doing the OR.
 Doh
#483 posted by Preach [62.30.197.42] on 2011/03/19 10:15:06
Yeah, I completely fluffed that one. I guess you'd need to do something along the lines of
temp = (x|x)
(temp != x) + temp;//this ACTUALLY evaluates to ceil x
Some lovely abuse of the boolean to float conversion there. I'm not sure if that's still fewer instructions than the call to ceil though...
 More Negatives
#484 posted by Preach [62.30.197.42] on 2011/03/19 13:29:07
The "better" version of ceil still doesn't work for negative numbers, and you might not get the same results as you expect for negative numbers using the rint substitute either. So they're both limited in scope. The floor one works well though, since it does the least work...
 Is Qc Really That Slow
#485 posted by jt_ [24.11.39.160] on 2011/03/19 14:45:16
That crap like that matters? :s
 In This Case
#486 posted by necros [99.227.131.204] on 2011/03/19 19:02:41
it's not about speed at all. preach already explained that it's main usefulness comes from reducing the number of operations done, thereby increasing the amount of things you can do in a while loop before the engine complains about it.
 Well Maybe
#487 posted by jt_ [24.11.39.160] on 2011/03/19 22:10:51
Someone should make the engine not complain about how many instructions are being done. That seems.like a better fix than pretty much inlining every function call.
 Rationale
#488 posted by Preach [62.30.197.42] on 2011/03/19 23:24:16
That would mean that if you ever code an infinite loop in qc then the engine would hang rather than just drop the server with a runaway loop error message.
 Good Point
#489 posted by jt_ [24.11.39.160] on 2011/03/19 23:36:41
Maybe then how many instructions are needed for a runaway error to be trigger should be increased? It would seem so if the kind of optimizations that were listed earlier are needed to stop runaway. Or maybe I'm crazy.
 Been A While Since I Did QC
#490 posted by Kinn [86.153.224.11] on 2011/03/20 01:09:58
but how many loops would trip the runaway loop counter?
 100000
#491 posted by Preach [62.30.197.42] on 2011/03/20 01:20:21
100000 instructions between QC programs - a program being a succession of QC functions called without control returning to the engine. In general this is a sensible limit. An example where it might be a problem is if you need to run a looping on each of a set of entities, 20 entities would leave you only 5000 instructions for each one, disregarding overhead. Whatever you set the loop limit to, you could always push the boundaries, until the computation speed becomes more of a factor.
Of course by then you have a new excuse to optimise. The profile command suggests that once upon a time qc performance was an issue, and if quake were to be popularised in mobile or flash form, it might yet matter.
 Simple Idea
#492 posted by ijed [190.22.6.191] on 2011/03/20 02:24:06
Lower it for developer 1? Or have a runtime 'reader' to let you know exactly what's going on?
Like an ingame debugger.
 Maybe Not Lower It, But
#493 posted by Lardarse [62.31.162.25] on 2011/03/20 02:40:35
Have it print to console every 10k, noting the current stack and position. Or maybe even 5k...
 Yeah,
#494 posted by ijed [190.22.6.191] on 2011/03/20 03:33:57
Advanced logging.
 Qc Dev Tools
#495 posted by Preach [62.30.197.42] on 2011/03/20 12:07:57
Having extra qc developing tools in engines would be a blessing, but they wouldn't help with this problem because they won't ever be universally adopted - making a mod than only works in engines that have a raised instruction limit would not be wise. By that point you might as well customise the engine to do the intensive calculation for you and add it as a qc extension. It's not the kind of thing that can be set to "progressive enhancement" either - you can't change the logic of your code to suit the capacity of the engine that is running it.
At some point I want to write down my thoughts of "progressive enhancement" - usually a web design term - and how it relates to Quake. It can explain why features like fog and skyboxes were embraced, but things like qc extensions on the whole were not.
 That'd Be Interesting
#496 posted by ijed [190.22.48.69] on 2011/03/20 12:39:15
The question is, how can QSB be made.
 Honestly
#497 posted by necros [99.227.131.204] on 2011/03/20 23:24:53
i've never really had any problems with the 100k instruction limit and i've done all kinds of weirdo shit in while loops.
the point where you start to hit the limit, you're better off thinking about deferring operations to the next frame or something.
 Yeah
#498 posted by Preach [62.30.197.42] on 2011/03/20 23:37:15
While I've never had anything proper reach the loop limit yet, I know that it was a problem in Prydon Gate, so I guess it depends how different your mod is. The further you go from the original game, the harder you have to work in qc I guess...
 Interesting
#499 posted by Kinn [86.153.224.11] on 2011/03/22 21:05:52
If I started hitting the 100k limit in QuakeC I'd probably be at the point where I need to be doing an engine mod, not a QC mod.
#500 posted by necros [99.227.131.204] on 2011/03/23 04:06:27
speaking of instructions...
i've been trying to figure out a way to be able to have two huge groups of monsters fight each other without slowing down.
i'm talking like 2 or 3k monster teams here.
been experimenting with sort of deferring all ai functions to a 'group leader' but it's sort of hit and miss. you either have like a static group and when you have intermittent LOS, then some of the group can't hit their target, or you have a dynamic group and the code to figure out what group you're in eats up even more time than just running ai on all monsters like normal. :\
 Multithread It!
#501 posted by jt_ [24.11.39.160] on 2011/03/23 07:32:53
Oh wait...
 Show Of Hands
#502 posted by Preach [62.30.197.42] on 2011/03/30 01:31:00
Quick straw poll here:
If I was going to invest some time in a qc project, which would be most useful to mappers?
1) A medium-range navigation system for the AI. Where monsters now walk straight at the player over gaps they can't cross, this system would direct them around to the bridge. Basically navigation on the scale of rooms, not levels.
2) A system to create alternative shapes for triggers. For example, cylindrical, spherical, rotated rectangles, composition of multiple triggers into a single unit.
3) create an event-based system for with inputs and outputs on doors and other funcs. For instance, allowing you fire triggers on the events of a door beginning to open, reaching closed position, being blocked, etc.
On the input side rather than having just a trigger, you might be able to command a door to open (does nothing if it's already open), or shut (vice versa ), or toggle (the old behaviour). Having a system of 'filters' would allow conditional triggers like "pass this input on if door X is still moving".
 In Order (favorite Idea First):
#503 posted by RickyT23 [86.31.169.218] on 2011/03/30 01:54:58
AI first
I/O Triggers second
trigger b-box control third.
I guess all would have their uses, but better nav AI for monsters is the coolest idea IMO :)
 Ai
#504 posted by jt_ [24.11.39.160] on 2011/03/30 02:57:25
Would be neat to give monsters specific commands, ie attack this, run over there etc. :E
Being able to give things specific triggers would be great, for example, forcing a door to close only on a specific trigger, or making func_trains that can be made to reverse and go backwards through their track. :)
 3
#506 posted by Drew [132.205.103.141] on 2011/03/30 03:29:29
I would vote 3, but I don't really *release* maps, so...
 AI
#507 posted by Lardarse [62.31.162.25] on 2011/03/30 04:39:39
 3!
#508 posted by Spirit [82.113.106.203] on 2011/03/30 08:51:06
Quake is a simple pattern based arcade shooter that lives from its simplistic ai. 3 would allow people to create more atmosphere rich maps.
 1!
#509 posted by negke [88.70.236.23] on 2011/03/30 10:54:31
Actually, all three.
 1
#510 posted by onetruepurple [213.227.88.32] on 2011/03/30 11:36:14
 AI
#511 posted by ijed [190.22.113.202] on 2011/03/30 12:52:42
 1...3...2
#512 posted by generic [67.233.207.122] on 2011/03/30 13:43:54
I can't count :)
 Thoughts
#513 posted by Kinn [109.158.79.63] on 2011/03/30 21:15:35
1) sounds like it would have the most tangible impact on the gameplay, although purists might argue that it would feel wrong to have Quake monsters capable of relentlessly chasing the player from room to room.
That said, I did feel the need to mackle up a very basic system in my maps to allow the monsters to chase the player up and down some of the spiral staircases, that otherwise they would have had real problems with.
2) sounds like its use would be too limited. 90% of the time, axis-aligned boxes will do the job, although I might as well admit that in Marcher I hacked in a sort of line-segment trigger (that functioned like an arbitrarily oriented invisible tripwire) which I used in a couple of places.
3) Sounds very useful all round and is a philosophy I wish Quake's trigger system had adopted from the beginning.
 Clarification
#514 posted by Preach [62.30.197.42] on 2011/03/30 23:38:39
Without wanting to influence anyone's votes
to allow the monsters to chase the player up and down some of the spiral staircases
This is almost exactly the use case I had in mind - a system that would be capable of allowing this, or for creatures to know how to move from a balcony, down the stairs into the atrium where the player was. The trick is making a single system flexible enough to do that without being a nightmare to set up.
It wouldn't let monsters chase you from room to room, you'd have one trigger brush creating a region, and if the monster and the player were both touching the trigger then the monster would be told the direction which moves them topologically closer* to the player. Hopefully that doesn't compromise the fundamental behaviour of any monster, just allows them to deal with complex rooms as well as open spaces.
* As opposed to the direction bringing them physically closer - the current navigation method. In open spaces, the two are the same. So by reduction the gameplay is unchanged, and in a single bound I am free!
#515 posted by rj [86.0.166.158] on 2011/03/31 00:00:43
i seem to remember nehahra had some improved navigation systems for monsters, but you could always specify which one to use when placing the entity. i'd favour this approach
 AI 'smell' Trail
#516 posted by ijed [190.22.56.48] on 2011/03/31 02:23:22
Was something we were pondering. It got denounced when we mentioned it here of course.
Nehahra had various layers of AI and additional flags like INTREPID (ignore hazards when leaping off stuff) and the ability to teleport at will.
#517 posted by necros [99.227.131.204] on 2011/03/31 03:28:11
ai sounds like the most useful.
i've experimented with different methods but never really been satisfied.
currently, i'm using a sort of waypoint system that monsters will follow after they loose sight of your.
it has the benefit of making monsters look realistic when searching for you, but in some ways, it makes them less effective.
an interim system i have is to have the ability to flag path_corners to make monsters both not search for the player, not react to damage, and to use their run animation instead of walk when moving to them.
this is only useful for initial pathing, as once the monster is awake and not following path_corners, there's no benefit.
but yeah, a unified pathing system would be totally awesome, but i just can't see how to get it to work for all cases. :(
good luck though. i'd be really interested to see what you come up with!
A simple but useful bit of AI gameplay wise would be that melee monsters that are below the player or otherwise can't find a direct path to the player will instead seek to hide from the player's LoS (whether they could hide from grenades I dunno :P ).
This could help prevent a lot of cheesing combat, if the monster hides when the player is trying to pick it off from a safe position. It's often this which easily disarms the challenge in a lot of maps, and is why the only way to really create a challenging fight is just to suddenly drop the enemies directly on top of the player in a trap.
#519 posted by necros [99.227.131.204] on 2011/04/01 21:55:23
This could help prevent a lot of cheesing combat, if the monster hides when the player is trying to pick it off from a safe position. It's often this which easily disarms the challenge in a lot of maps, and is why the only way to really create a challenging fight is just to suddenly drop the enemies directly on top of the player in a trap.
i feel this is more a mapper's failing. you shouldn't really be letting melee monsters get into a position like that unless it's something you can't plan for (ie: fiend jumps off a ledge and can't reach you anymore).
but like, for example, you shouldn't really be able to 'pull' melee monsters from far away or don't provide an easy way to exploit them.
 Speaking As Primarily Not A Mapper...
#520 posted by Lardarse [62.31.162.25] on 2011/04/02 02:50:52
(...and no, this isn't an excuse to tell me to fuck off)
I'd contemplated re-writing the trigger system before, but I wasn't sure how best to do it. The way that makes most sense to me right now, is some sort of "message" system, that goes something like this:
"targetname" is what is being sent the message. The additional targetname entries (if present in the code) give it additional frequencies to be listening to.
"target" is what to send the message to. Again, the additional targets work like additional frequences to be broadcasting on.
And them there's an (as yet) unnamed 3rd field, which is the message to be sent. This could be something like a traditional use/activate signal, a kill signal, a change texture signal (so you can have brush entities that are doing something more useful than just being func_walls change their appearance), or maybe even something else.
This is made more interesting, of course, by being able to send a different signal to each target. Yes, this is still similar to how we have .target and .killtarget now (and yes, this would allow both to be done at the same time, as is the case in Quoth, RMQ, and other mods), but it would be more flexible than that.
The only part I'm not sure on, is if this signal should be a float or a string. And then, of course, you have to define what all of the signals mean for each object. Obviously, there would be a few common ones, but some would need additional things specified.
#521 posted by necros [99.227.131.204] on 2011/04/02 07:18:29
yeah, that's an ok system. it's good because it's unified and probably easy to understand from a programming point of view.
i imagine the 'message' could be a simple bit mask where you can select what you want to change to the triggered object. so a single trigger could change, for example, both the func_door texture AND open it or whatever.
but it's just easier to code up helper or script entities instead and more intuitive in an editor (not to mention there's no support for an 'entity message' in any editors).
#522 posted by necros [99.227.131.204] on 2011/04/02 07:21:07
mm, my mind apparently skipped a beat and i didn't actually explain myself on that third paragraph. o.0
i say it's easier, but it's also more flexible.
i have script entities that can change an entity's owner, change specific 'target' strings, toggle flags/spawnflags on entities.
toggling a func_walls texture is simple, but how would you put 'change targetname2 on this entity to xxxxx' in a simple one string/integer message. you'd end up with other helpers anyway.
 Collecting Sigils
#523 posted by Mike Woodham [86.185.201.19] on 2011/04/02 19:15:52
What is the code that lights the sigil's place marker on the player's GUI as each sigil is collected?
Are the lights just made in order or do the shapes relate to the actual sigil?
I do not get any lights when I use them (and collect them)in my levels - what gives?
 Sigils/rune
#524 posted by necros [99.227.131.204] on 2011/04/02 19:46:30
an engine coder could tell you for certain, but in the qc, picking up runes sets a global variable 'serverflags' with bits 1, 2, 4 and 8, corresponding to the appropriate episode.
the runes only work if the UI is set to the default one. hipnotic and rogue game modes turn on their respective UIs which don't check the runes. so if your mod uses one of those UIs or you're using quoth which uses hipnotic UI, then the runes don't show up.
 Mmmmm...
#525 posted by Mike Woodham [86.185.201.19] on 2011/04/02 20:57:11
Yes, if I use standard progs.dat (1.06) then the UI lights up. I am not using a mod but I am using an 'enhanced' progs.dat.
However, I have checked the sigil_touch section in items.qc and it exactly the same as the 1.06 version. I cannot find any other differences related to 'serverflags'.
Strange. Must be something, but I don't know what.
 WARNING: BAD CODE AHEAD
#526 posted by Preach [62.30.197.42] on 2011/04/02 23:44:24
THIS CODE IS PURE EVIL.
YOU MIGHT FIND IT USEFUL.
BUT DON'T GET FUNNY IDEAS.
So...I was working on the winner of the straw poll, which is the navigation entity stuff. Idea 3 did attract some attention so I might put up an article about naming in QC which would contain the "clever ideas" part of the event system I had in mind, so someone else with time on their hands would be free to do the footwork implementing it. Most of the effort would be in creating useful input and output on the various func_ entities, but at least it's a chance to exercise some creativity.
Anyway, I was trying to create some beautiful code involving callback functions (been reading too much ajax stuff recently) and managed to confuse the compiler enough for it to mistake a string field for a float. As you may or may not know string fields in QC are integer offsets into a big block of strings. So I came up with the following:
float INT_1 = 0.00000000000000000000000000000000...
.string tempstring;
void(.float stringfield) increment_string =
{
float increment, stringvalue;
increment = INT_1;
if(self.stringfield < 0)
increment = increment * -1;
stringvalue = self.stringfield;
do
{
stringvalue = stringvalue + increment;
increment = increment * 2;
}
while(stringvalue == self.stringfield);
self.stringfield = stringvalue;
}
void(void(.string floatfield) dispatch, .string fieldtype) strip_fieldtype =
{
dispatch(fieldtype);
}
//then put the following code somewhere
{
self.tempstring = self.model;
while(self.tempstring != "")
{
strip_fieldtype (increment_string, tempstring);
dprint(self.tempstring);
dprint("\n");
if(self.tempstring == ".bsp")
{
dprint("It's a bsp file!\n");
break;
}
}
}
It's so hacky I don't even want to talk about why it works.
 Shamefaced
#527 posted by Preach [62.30.197.42] on 2011/04/03 00:44:19
In time maybe I'll see this as undoing a great blessing but: a correction:
float INT_1 = 0.00000 0000000000000000000 00000000000000 00000014013; but without the spaces.
#528 posted by necros [99.227.131.204] on 2011/04/03 07:04:57
increment = INT_1 o.0
mike: i was referring to the command switch you use when launching quake, not the progs. the progs can't change the UI itself, unfortunately. if you put hipnotic progs in the id1 folder, you'll get hipnotic entities, but the UI will be default quake.
 Oh Btw
#529 posted by necros [99.227.131.204] on 2011/04/03 07:19:54
on pathfinding...
if you're gonna implement a brand new system... should just go whole hog and do a star and just let the mapper plop down some nodes. that'd be insanely badass...
i'd totally do it, but i'm too dumb. ^_^
 Necros
#530 posted by Mike Woodham [86.185.201.19] on 2011/04/03 10:07:54
I am not sure I understand. No, I am sure I don't understand.
I have two .bat files:
fitzquake085.exe -window -heapsize 40960 +gl_clear 1 -width 1024 -bpp 32
fitzquake085.exe -window -heapsize 40960 +gl_clear 1 -width 1024 -bpp 32 -game mynewprogs +skill 2 +map This_FMB_1_8c
The first runs the iD1 folder and this has no qconsole file and the same config file as the mynewprogs folder. The mynewprogs folder does not have a qconsole file either.
As far as I can see the only difference in the two folders is the progs.dat. Yet the first bat file game shows the runes lights as you pick them up but the other bat file game doesn't.
It does not actually affect the gameplay but as you need all four runes to create a certain effect, it would be useful for the player to able to see what he has already picked up instead of having to remember.
Any hints as to what could cause mynewprogs to apply different UI settings?
 Itemized
#531 posted by fakepreach [86.129.223.59] on 2011/04/03 17:40:53
So do you have .float items2 defined somewhere in your code? If it is defined anywhere then the serverflags info is not sent to the client - to save bandwidth that info is replaced by the items2 info instead.
 Fakepreach
#532 posted by Mike Woodham [86.185.201.19] on 2011/04/03 18:43:06
Funny you should say that. Yes, I do have .float items2 as I have the drole and vermis from Quoth.
//quoth -- items
.float items2; // bit flags for new items -- ran out of room on items...
I am not sure why they ran out room; perhaps I'll experiment with changing it to 'items' and see what breaks.
 Runes Can Exist In .items2
#533 posted by Lardarse [62.31.162.25] on 2011/04/03 21:15:52
As 32, 64, 128, and 256. If you're using .items2 for your own nefarious purposes, then leave those four bits for the runes.
 Note That
#534 posted by Lardarse [62.31.162.25] on 2011/04/03 21:22:25
sigil_touch() will need to be adjusted for this to work. The line
serverflags = serverflags | (self.spawnflags & 15);
should be followed by
other.items2 = other.items2 | ((self.spawnflags & 15) * 32);
This won't send the update to all players, but that's only relevant in coop. Also, impulse 13 won't update the hud properly, but a similar line will work in the function that handles it.
 Ohh
#535 posted by necros [99.227.131.204] on 2011/04/03 21:55:22
thanks fakepreach and lardarse for clearing that up! i had no idea .items2 had any bearing on serverflags!
mike, since you're only using the drole, you could do a quick find/replace and just change 'items2' to something like 'drolevar'.
that way you can just leave the old serverflags variable alone.
 Thanks Necros, Fakepreach, Lardarse
#536 posted by Mike Woodham [86.185.201.19] on 2011/04/03 23:25:42
As I am not using any Quoth items, I can do away with the code relating to items2 without any detriment. It's just a couple of If/Then statements which don't apply anyway.
Onwards and upwards...
#537 posted by necros [99.227.131.204] on 2011/04/04 00:12:42
don't forget to tone down the drole damage a lot. ^_^;
 Drole Damage...
#538 posted by Mike Woodham [87.127.250.2] on 2011/04/04 09:54:53
"Do you expect me to talk?"
"No, Mr Bond. I expect you to die!"
 Bbox Sizes
#539 posted by necros [99.227.131.204] on 2011/04/20 07:34:14
so, going in the opposite direction, it's possible to make monsters with smaller bbox sizes, however you need to offset them by whatever amount smaller than 6 that you used.
now that you're down frowning in perplexity at that terrible sentence, this is what i mean:
say you made a skinny monster, '-6 -6 -24' to '6 6 40'
you need to setorigin(self, '-10 -10 0') the monster or, if you place it close to walls, it will get stuck (even though, when you turn r_showbboxes on, the bbox clearly is not in any wall).
but other than that, it seems to work fine, really lets monsters bunch up. would have worked good for the voreling had i known about it back then.
 Heh
#540 posted by necros [99.227.131.204] on 2011/04/20 07:35:02
not only was that sentence awful, but there's a typo too.
by whatever amount smaller than 16 that you used.
#541 posted by metlslime [67.188.81.46] on 2011/04/20 07:55:21
I did this with Floyd, didn't do the setorigin trick but instead made the in-editor box full shambler size so there's no risk of placing it in a wall.
 Fools Echo
#542 posted by MadFox [84.26.69.4] on 2011/04/20 21:15:25
I'm still perplexed in making the Doomguy crouch.
Didn't want to start it over afraid of being a fool.
But the trick me was told to lower the bouncingbox so bullits would fly over it, and then raise the model again.
I don't understand. Then it would sink in the ground, but raising it would raise the bouncing box too?
#543 posted by necros [99.227.131.204] on 2011/04/23 21:38:08
i'm not sure if we've covered this but when do touch functions get run when monsters are involved via movetogoal/walkmove?
are they run at the moment movetogoal is called?
ie:
void() someTouchFunction =
{
other.happy = 0
}
void() ai_run =
{
self.happy = 1
movetogoal(1)
if (self.happy)
cheer
}
so in this retarded example, while the ai_run function will make the monster happy, it will run a touch function right after movetogoal so that the if statement will fail?
i've been observing some screwed up behaviour and i'm trying to track it down...
 Touchy Subject
#544 posted by Preach [86.129.223.59] on 2011/04/24 11:19:43
Yeah, the touch functions are respected there - only pitfall to bear in mind is that if the move fails because the monster is blocked, then they don't move at all and so the monster doesn't touch.
So that doesn't really help you track down your problem, and it sounds like you've got a pretty solid minimal test there. So I'm just gonna take a peek at somet-
OH MY!
Oh dear.
Very long standing bug in quake coming right up...
PF_walkmove, the non-navigational monster moving code, has the following code in it, which I remember noting down for possible use with that JS quake engine idea
// save program state, because SV_movestep may call other progs
oldf = pr_xfunction;
oldself = pr_global_struct->self;
G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true);
// restore program state
pr_xfunction = oldf;
pr_global_struct->self = oldself;
Firstly a quick note, "PF" in front of a function in the quake source means that it's a "progs function" - a light wrapper around some C code called as a QC builtin. The snippit I quoted then calls SV_movestep - the real heavyweight movement function which other parts of the engine also use.
Around that call we have some clearly documented code which stores the current QC execution state and restores it after the call. This firstly tells us that touch functions are a possibility they have programmed for, and secondly eliminates a possible source of the buggy behaviour - that after calling the touch control never returns to the QC.
Except your QC doesn't use walkmove, it uses movetogoal. I went looking for PF_movetogoal and was surprised to find it missing. It turns out that the QC builtin in this case directly calls SV_movetogoal instead, and I'm sure some of you are ahead of the puchline here - it doesn't save the QC state.
I haven't gone away to do follow up QC tests, but worst case I can imagine is that none of the remaining QC in your function ever gets run if a touch function is encountered, and best case is that it's just self that gets obliterated. In the former case you are basically stuck with using walkmove instead. In the latter you can put the traditional
local entity oself;
oself = self;
movetogoal(1)
self = oself;
But yeah, genuine nasty bug there I think. Let me know whether it is that worst case scenario situation or what, I'd like to know...
#545 posted by necros [99.227.131.204] on 2011/04/24 19:23:58
ohhh ok, yes! that explains some OTHER whacked out behaviour i've been seeing!
i've changed movetogoal into a wrapper that (among other things) does a movetogoal(1) and then calls x number of walkmove(directionItMoved, 1) to complete the amount of movement requested.
this allows a monster to continue to move forward, even if it's full movement would have failed allowing it to get through thin or cluttered areas better.
i was seeing some weird stuff where a monster would touch a trigger and then not move (it was moving by 1, but not completing the walkmoves).
i guess it never mattered before because movetogoal was solely called at the very end of a qc function.
anyway, i've added in your suggested change and will test it today. :)
 Binary Chop
#546 posted by Preach [86.129.223.59] on 2011/04/24 21:09:23
If you like, there's a trick you can apply there to only call ~log(x) steps in the worst case rather than x for moving x distance.
The trick is try to walkmove(x) first - being optimistic!
Check the return value for failure, and we can finish if we succeeded.
If we failed, replace x by with x * 0.5. We then try to walkmove(x) with the new x.
We keep repeating the above step(in italics) until x is smaller than 1.
Note that after the first iteration we don't need to check for success.
Worked example:
Suppose we want to walk forward 32 units but only have space to move 21 units.
First time through walkmove fails
x=32, moved=0
Second time through we succeed
x=16, moved=16
Third time round we fail as we are 16 forward and another 8 would take us past 21
x=8, moved=16
Fourth round we succeed
x=4, moved=20
Fifth fails
x=2, moved=20
Sixth succeeds
x=1, moved=21
Since x has reached 1 we terminate.
I'm making the numbers quite friendly by choosing x as a power of 2. It also lets me gloss over the edge cases of when to reject x and stuff. Nonetheless the method is sound and a fairly simple loop. Probably the hardest idea would be proving that it always works, I've always been fond of Proof by Single Worked Example though...
 Yeah
#547 posted by necros [99.227.131.204] on 2011/04/24 21:23:21
i've been meaning to do that. :)
i wasn't sure if there was a point where doing it linear was faster or if it was always slower and i just haven't been in the mood to try to figure it out. ^_^;
it looks like you're saying it's always faster?
also, while we're talking about walkmove and such...
is it ok to call a walkmove/movetogoal larger than bbox size?
ie: walkmove(yaw, 256) as opposed to looping walkmove (16) 16 times.
not sure if this messes up collision if the bbox is displaced by huge amounts...
 Empirical Answers
#548 posted by Preach [86.129.223.59] on 2011/04/24 22:44:06
Yeah, the binary chop should always reduce or equal the number of calls to walkmove in the linear algorithm. The nice thing about it is getting twice as high precision only costs you 1 more call to walkmove, although I doubt anyone needs monsters THAT much more finely tuned than 1 unit.
I tried to pick through the engine code to decide if you could get away with long distance walkmoves. But it's the densest part of the engine code and although my suspicion was that collisions would get skipped I wouldn't be able to answer with confidence.
So I bashed away through the latter half of Lewis and made a test mod. The emperical conclusion: no.
You can make a monster walkmove 256 units and completely skip a 64 unit trigger. So yes, limit yourself to width of a bbox per iteration. You can be assured that you won't ever skip through walls of the game world. BSP entities don't count though...
#549 posted by necros [99.227.131.204] on 2011/04/24 23:48:57
You can be assured that you won't ever skip through walls of the game world.
this is more what i was asking. mainly the use for super long walkmoves would be to check for charging/leaping abilities like fiends instead of just checking if they can see the player.
a walkmove check of even just 16 or so units will stop a fiend from doing that psycho leap 'bug' around doorways.
otoh, it will also cause it to fail more often as a jump that would hit a wall yet allow it to bounce towards the player (and hit him) would fail unlike with just plain visibility checks.
you could possibly make a 'robust' walkmove that, when failing to move, would tweak angles by a small amount and try again.
hmmm...
 Rollback
#550 posted by Preach [86.129.223.59] on 2011/04/25 01:15:14
I was about to warn you about using walkmove checks(where you want to reserve the right to rollback the movement and do something else) too freely. What if you walkmove into a rocket during a 'check'. Luckily you are largely protected from this kind of thing. This is because the only thing that monsters will collide with during a walkmove or movetogoal is SOLID_TRIGGER entities.
So really all you need is to watch your triggers which respond to monsters when you're doing that kind of thing. In standard quake that's only really telefrag and trigger_hurt - which you could guard against with a quick change of takedamage.
If you really get into that kind of thing you can go down the route of having a 'proxy' entity instead. I was trying to think about what you'd need to do to get the proxy perfect. The think you'd need to get right is .owner, which would take 3 transformations:
1)Set the owner of monster's owner to the proxy.
2)Set the owner of proxy to the the monster.
3)Set the owner of everything else in the world currently owned by monster to proxy.
You also need to store enough information to reverse all these changes.
This is of course absurd levels of effort. Just set the proxy's owner to the monster, and accept that occasionally it'll be blocked by an entity that owner settings would have let the monster walk through. Never realistically a problem.
#551 posted by necros [99.227.131.204] on 2011/04/25 01:20:10
thanks, i hadn't thought about the 'premature triggering'. yeah, a proxy looks like the best bet.
when i needed a walkmove check to move through monsters, i just set all monsters to non-solid. :P
#552 posted by necros [99.227.131.204] on 2011/04/25 01:20:43
or a flag on the entity you toggle on temporarily that you code into all triggers 'don't activate when this flag is on'.
 Addendum
#553 posted by Preach [86.129.223.59] on 2011/04/25 01:25:44
The takehome message of the last post is:
During a walkmove or movetogoal the monster will only collide with SOLID_TRIGGER entities.
I want to add that only the trigger's touch function is activated, the engine does not call the monster's touch function with the trigger as other.
 In Error
#554 posted by Preach [62.30.197.42] on 2011/05/01 22:59:30
So I adapted the code which I wrote last week answer that question about movetogoal. It turns out that even through touch functions, it restores the QC state correctly and doesn't overwrite self. This is admittedly run from a think function - it's possible that you could create a more complex scenario like calling movetogoal from a touch function and then causing another touch function.
But I thought I'd share it with you because it made me practice a few diagnostic tools for QC, some of which you may not be aware of.
The first is just chucking in a load of dprint statements, I'm sure most people have done that before. Don't forget that you can use ftos() and vtos() to put more information into the statements. If you need to confirm the value of an entity variable (as I wanted to with self) you can use the eprint() function instead.
The second tool is a bit of a hack. It allows you to get a stack trace. This was particularly valuable in this case because of the concern that the touch function might be trashing the stack. But it would be of use in any case where you have an interaction between think and touch functions causing a bug, and you want to know what is calling what.
There's no command or QC builtin for a stack trace, but you get one if you manage to perform an invalid operation in QC. The infinite loop is one way to do this, but personally I prefer the null function call, it's a bit quicker. Just throw something like
world.think1();
and unless the map is particularly weird you'll get your error. Of course, this does shut down the entire server, so it's not for general use.
The last trick is pretty powerful but also has the potential to spam the entire console out. The QC builtins traceon() and traceoff() allow you to enable console output of every Qasm instruction that the server reads - including the register values that were employed.
I believe Qasm is a neologism, and if so I demand it be pronounced as "chasm". It refers to the bytecode that a QC compiler creates - effectively Quake Assembly code. You can use one of the options in FTEQCC to output the assembly to a file, which is a good way to learn what the trace output means.
(As an aside FTEQCC also lets you enter segments entirely in Qasm. This is in the same way as c compilers accept asm segments - and indeed the original quake engine did just that in the renderer to get tight loops fast enough for the Pentium 75s to run the game. I believe most modern ports strip the assembly out. Even so, in quake Qasm is a great way to do naughty things that the compiler won't let you...)
 Are You Sure?
#555 posted by necros [99.227.131.204] on 2011/05/02 00:29:27
because i distinctly remember after putting in the self restoration bit that it fixed a problem with walkmoves not being called after movetogoal.
 Yeah
#556 posted by Preach [62.30.197.42] on 2011/05/02 01:06:07
We might have to share some code here to determine why we're getting different results. If you whack an eprint(self) in before you restore self do you get the trigger entity that you touched come up?
 Heh
#557 posted by necros [99.227.131.204] on 2011/05/02 02:37:38
yeah, i guess i could have at least tried it out to see.
you're right, self isn't being lost when touching triggers. trying to figure out now would be pointless though, because the code has change by a huge amount since last time i posted about it.
 SV_TouchLinks: Next != L->next
#558 posted by necros [99.227.131.204] on 2011/05/08 20:20:10
is it bad to call setorigin(self) in a touch function? does that cause the touchlinks warning in fitz?
i googled the forums, but i don't think it was ever really explained what this error is, only that it used to crash in e2m2.
 Potentially
#559 posted by Preach [62.30.197.42] on 2011/05/09 00:29:39
I'm not brilliant at following how the engine handles this touch stuff, but here goes:
The following functions could cause problems with sv_touchlinks:
setorigin
setsize
droptofloor
movetogoal
walkmove
All these functions can cause the entity to be relinked, which potentially causes the issue. There are some other conditions that need to be met before the error is encountered: firstly the entity has to be part of the same areanode as the touch is coming from*. My understanding is that there are 32 areanodes in a map to reduce the number of entities considered in the collision code by approx. that factor.
The other important thing is that the entity has to actually break the chain of linked entities at exactly the point that sv_touchlinks is operating. I believe that this translates to applying any of the dangerous functions to other, but again not too sure how all the code fits together.
A final small point is that even if do apply one of the dangerous functions to other (and since we're in a touch function with other it's safe to assume by now that they share an areanode) we might still get away with it. This case would occur when other is the first entity in the areanode - relinking it will reinsert it in the same place as before. There's no safe way to exploit this though, as it's determined entirely engine-side.
The last thing to remember is that sv_touchlinks only looks through the list of triggers, so anything that's not SOLID_TRIGGER* ought to be safe. Best of luck!
*Technically that should read "that wasn't SOLID_TRIGGER last time the entity got linked into the world" but that's a bit of a mouthful. All that means is that you can't quickly set the entity to SOLID_NOT and get away with anything, it's about whether the entity is truly a trigger according to the engine at that time...
 Footnote
#560 posted by Preach [62.30.197.42] on 2011/05/09 00:31:10
That first asterisk should have been removed, the footnote is about the SOLID_TRIGGER statement - though most of you spotted that anyway I expect...
#561 posted by necros [99.227.131.204] on 2011/05/09 00:54:07
yeah, this is happening with some of my code. unfortunately, i have the report from a third party and haven't been able to reproduce it myself. apparently, occasionally, it spams the console with sv_touchlinks errors in fitz085.
i noticed this tiny comment in defs.qc for setmodel:
void(entity e, string m) setmodel = #3; // set movetype and solid first
and i checked that entity and found that i was setting self.model before movetype. so i fixed that anyway, but maybe that might also have been the problem? the entity wasn't properly linked in the first place?
geez, i dunno. :P
it's supposed to be a visible solid_trigger entity that changes origin when you touch it.
i was wondering, maybe if i used to bad method of just setting self.origin in the touch function, that way, it's not breaking whatever links?
 Danger
#562 posted by Preach [62.30.197.42] on 2011/05/09 09:11:32
From what I've gleaned, that is the most dangerous thing you could do in a touch function, although I didn't post it correctly. I was talking about the danger of moving other, but I should have been talking about self. I had the two muddled up and I'm sorry about that.
The standard way to cope with this is to set up a quick think function from the touch function, and have that think reset the origin. You can put a guard into the touch function to prevent multiple touches before it moves - set up a flag on the entity and toggle it in the touch and think.
 Hm Ok
#563 posted by necros [99.227.131.204] on 2011/05/09 20:57:07
so what i can do is just set a flag after the touch function to disallow further touches until the think function has been run. that should work i guess. a little roundabout but last thing i want to do is start causing crashes. :P
 Speed-up
#564 posted by Preach [62.30.197.42] on 2011/05/09 22:19:31
You can also use a little trick to make sure the think function runs as soon as possible (either this frame or the next). Just set self.nextthink = 0.05; - note that we are deliberately omitting time from the assignment. Since this will be in the past for every frame the engine runs (frame 1 runs at time = 0.1) it will execute the think as soon as the entity is checked by the engine.
The flag works a bit like the way that
if (self.nextthink > time) line works for a trigger_multiple, so if you don't want to use another field that approach is an option. My feeling is that the flag is simpler because you don't need the flexibility of a custom delay but it's basically a preference thing.
 Spot The Deliberate Mistake?
#565 posted by Lardarse [62.31.162.25] on 2011/05/10 19:59:18
(frame 1 runs at time = 0.1)
Time starts at 1, not 0. This is one of the quirks of the system, that makes very little difference, except for when it trips you up (and when it does, it hurts). For example (from the id1 code):
self.nextthink = self.nextthink + random()*0.5;
This line appears in all of the $foomonster_start functions. The original intention was for monsters to not all do their setup on the same frame, to reduce computer load. However, what actually happens, is that on a roughly 1 in 32000 chance (or, according to LordHavoc, 1 in 2 billion on Linux), self.nextthink is set to 0, which means that it never thinks again (as .nextthink is set to 0 before the think function is called), and the rest of the time, it happens on the next frame.
 Thinking About The Other One
#566 posted by Lardarse [62.31.162.25] on 2011/05/10 20:00:41
When a think function is being called, what is other set to? Does it get set to something predictable, or is it just left as whatever it was last time?
#567 posted by necros [99.227.131.204] on 2011/05/10 20:30:38
so that bit of code doesn't actually do anything then?
might be a good idea to change to it 1 + random() * 0.5 then? because that must mean thinks are being all processed on the same frame.
also, on other, a few think functions steal other for their own uses so even if it is being reset every frame, you might get a semi-random entity if one of those thinks happened during the frame?
 I'd Say
#568 posted by SleepwalkR [85.178.188.171] on 2011/05/10 22:05:58
(1 + random()) * 0.5
#569 posted by necros [99.227.131.204] on 2011/05/10 22:09:03
won't that still generate nextthinks < 1?
 Depends
#570 posted by SleepwalkR [85.178.188.171] on 2011/05/10 22:11:59
Is nextthink a float or an int, and how do conversions work in QC? If it is like in C, then yeah. I just thought it's closer to the original, but heh we don't really want that.
 <1
#571 posted by Preach [62.30.197.42] on 2011/05/10 22:42:23
It's ok to generate thinks which are less than 1 in this case, because as long as it's non-zero the think function will run, and you'll still spread the monsters out over many frames because not all of them are going to get the same random number.
And if they did, they'd all be set off on the same frame no matter what function you did...
Good catch on the server starting at 1 there though, I must remember that. Either way, as long as you don't set nextthink to 0, any value less than 1 will give you the soonest think function possible.
 However
#572 posted by Lardarse [62.31.162.25] on 2011/05/11 00:41:39
any value less than time will give you the soonest think function possible
As Spike has pointed out a few times, when a think function is called, for that dive into the code only, time is set to what .nextthink was before being reset. So the most reliable way for a "do this next frame" is probably self.nextthink = time;
 Well
#573 posted by Preach [62.30.197.42] on 2011/05/11 00:52:53
Only if your code depends on the value of time in some way. If all you need is for it to run in the next server frame then you can take the shortcut. If time cannot every be less than 1 then a value less than 1 is always less than time...
#574 posted by necros [99.227.131.204] on 2011/05/11 01:27:20
i think it's more about successive thinks.
the less aligned monster thinks are, the less impact the ai routine has when it's run, i would think.
if you just set nextthink to 1, every monster will be thinking at the same time.
 Shadow Casting Bmodels
#575 posted by necros [99.227.131.204] on 2011/05/14 23:03:56
could someone modify MH's Aguirre's light utility so that it casts shadows from bmodels?
and maybe a new key '_noshadows' to disable shadows on that particular bmodel?
or is it not possible? like bmodels can't be used for some reason?
 Reversal Of Fortune
#576 posted by Preach [62.30.197.42] on 2011/05/15 02:00:54
Not commenting on how it can or can't be done, but I'd recommend making it opt-in rather than opt-out. Having an entity key _castshadows which you set to 1 to enable the new function would keep the current behaviour on existing maps, which is always desirable when possible. I also think there's a good chance that the cases you don't want it on (entities which move or have little to no impact on shadow casting) outnumber the entities which would benefit. It would make it reactive - used only when the lighting looks wrong - but I don't think that's a bad thing.
#577 posted by necros [99.227.131.204] on 2011/05/15 02:05:40
maybe. either way, it'd still be great if someone could haxor that in. :)
#578 posted by necros [99.227.131.204] on 2011/05/21 01:45:14
we were talking about how bsp models use their bboxes to figure out collision a little while ago.
i mentioned about setting bbox in qc affecting that. i tested it out, and yes, if you manually set bbox size, you can don't need to put visible brushes at the min/max extents.
also, if there are visible parts of the bsp model but the bbox is smaller, any bits outside the bbox are nonsolid.
 Award
#579 posted by Preach [62.30.197.42] on 2011/05/21 10:02:36
also, if there are visible parts of the bsp model but the bbox is smaller, any bits outside the bbox are nonsolid.
Hack of the week right there, folks. Awesome!
 Backup Past 0
#580 posted by necros [99.227.131.204] on 2011/05/30 00:15:26
what the heck is this anyway? :P
 Tracing Error
#581 posted by Preach [62.30.197.42] on 2011/05/30 11:42:39
It's a glitch in the trace code. It arises in the part of the code that tries to determine the exact impact point of a trace on a solid surface. The loop starts with a point at "0" which it knows is in the open, and a midpoint which it knows is in solid BSP. It then backs up from the solid point in small increments until it gets into the open, and so fixes the endpoint of the trace.
The glitch occurs because the increments don't always exactly hit the 0 point as they build towards it. It is possible for the trace to be in solid for all of the test points before 0, and for the increment to never hit exactly 0. The loop would then return the first point past 0 as the nearest point-in-open to the impact point(i.e. backup past 0). However, we already know that 0 is closer to the surface and in-open so that point is returned instead.
Phew, so basically I think this is most likely to occur on short traces where an endpoint is near a surface, as these are the traces where floating point inaccuracy is most prevalent, and I believe that makes a difference. However, there is a binary chop portion of the trace algorithm, so the circumstances that cause it to happen may just occur at random on any given surface.
Possibly also complicated or intricate BSP architecture could make this more likely, since you would have to dive down to a smaller scale to check exactly which bit of fine detail a trace collides with, but I've got no example to back that up with.
 Thanks
#582 posted by necros [99.227.131.204] on 2011/05/30 19:38:35
that helps a lot to know at least what is causing it.
 Sounds...
#583 posted by Mike Woodham [109.156.194.239] on 2011/05/30 20:36:49
Sounds don't start until one second after worldspawn. Is this 'fixable' from qc?
My FMB_BDG map finishes with an earthquake and it would be nice, from a continuity point of view, to start the next section with the tail-end of the earthquake.
 Time Starts At 1, Not 0
#584 posted by Lardarse [62.31.162.25] on 2011/05/30 21:46:32
So it's probably fixable from the map(s).
 Sound Out
#585 posted by Preach [62.30.197.42] on 2011/05/30 22:05:41
Completely untested, but if you spawned a static sound which didn't loop, does that cause an error? Otherwise it might just get around the problem...
 Oh Yeah
#586 posted by Preach [62.30.197.42] on 2011/05/30 22:07:21
that helps a lot to know at least what is causing it.
Probably the most important thing to know about it is that it doesn't matter at all - there's nothing going wrong in your code and the engine has already coped with the potential inadequacy. So I'm not even sure the message is worth preserving in the engine...
#587 posted by necros [99.227.131.204] on 2011/05/30 22:25:06
well, if there's no purpose to it... i just get spammed by it sometimes and it clears out the console buffer of useful debug text, which is annoying. good to know it isn't causing problems.
also, trying to spawn a static (ambient) sound that isn't looped results in that 'sound isn't looped' error we used to get with the broken ambient_thunder entity.
#588 posted by necros [99.227.131.204] on 2011/06/12 21:24:22
i have a bad feeling about this but....
is there ANY way to detect if the player has either opened the menu or closed it?
 Unlikely
#589 posted by Preach [62.30.197.42] on 2011/06/13 00:04:18
I can't see how you could, no QC runs while you're in the menu (assuming you're thinking of single player - multiplayer is different but no more helpful). You'd end up trying to spot differences from frame to frame in the same way that QC detects loading from a saved game. The problem is that you could quite easily go into the menu, do nothing and then leave after a while. I don't see how this would leave anything for the QC to find...
#590 posted by necros [99.227.131.204] on 2011/06/13 01:39:19
yeah, i was afraid of that. :(
oh well, thanks anyway.
 Monster_decoy
#591 posted by jt_ [24.11.39.160] on 2011/06/17 04:30:21
What was this used for in SoA?
#592 posted by necros [99.227.131.204] on 2011/06/17 04:56:00
cutscenes. it's a player model that will run to path_corners and wait for a few seconds, if 'wait' or 'delay' or something is set.
iirc.
 Ah, Thanks.
#593 posted by jt_ [24.11.39.160] on 2011/06/17 05:05:43
 Func_areaportals
#594 posted by jt_ [68.42.82.10] on 2011/06/21 02:10:01
Has anyone ever tried porting func_areaportal from quake 2 to a quake 1 engine and related tools? Any idea on how hard it would/wouldn't be?
#595 posted by jt_ [68.42.82.10] on 2011/06/21 21:45:30
So I've been thinking about a way to add a lot of new weapons to a mod, but I'm not sure about some things. The first issue would be how the hud handles the weapons, since there's only enough room for 9 weapons, I would need to figure out a way to make sure that weapons don't take each others spots. I was thinking of adding a precache variable to each weapon and then checking to make sure that none of them conflict with each other when the map starts. I think that would eliminate the problem of weapons taking other weapons spots. I have no idea how and for what reason they're placed on the hud the way they are, anyone have any insight on this?
 HUD Placement
#596 posted by Preach [62.30.197.42] on 2011/06/21 23:50:38
There's actually quite a lot to be said about the HUD but I'll try and get the basics into one short post.
The engine uses the QC field called items to determine which weapons to display on the HUD. If you look in defs.qc you will find the following:
// items
float IT_AXE = 4096;
float IT_SHOTGUN = 1;
float IT_SUPER_SHOTGUN = 2;
float IT_NAILGUN = 4;
float IT_SUPER_NAILGUN = 8;
float IT_GRENADE_LAUNCHER = 16;
float IT_ROCKET_LAUNCHER = 32;
float IT_LIGHTNING = 64;
To display some collection of these items on the screen simply sum the values of the icons you want displayed, and then set self.items to equal the total.
The numbers are all carefully chosen as powers of 2. This is useful because it makes any sum a unique combination of these numbers. It also means that you can use the bitwise OR function to safely add an item without checking if it is already there:
self.items = self.items | IT_SHOTGUN;
If we started with 0 items, and then blindly added IT_SHOTGUN to self.items we might get into trouble - if that line of code ran twice then self.items would equal 2 and we'd have IT_SUPER_SHOTGUN instead.
We can also use the bitwise AND function to test if an item is present:
if(self.items & IT_NAILGUN)
{
//do something just for players with a nailgun
}
If the bitwise functions are new to you I'd advise searching for a c tutorial on them, it's probably been explained before in a clearer way than I'd invent today.
Other things to know about icons:
• Setting self.weapon to one of the IT_ values highlights that icon as the selected weapon.
• The mission packs added extra weapons which complicate the icons code somewhat, so I'll avoid that until another day.
• The engine automatically makes new items blink on the HUD when they are added to self.items.
 Path_corners
#597 posted by jt_ [68.42.82.10] on 2011/06/29 17:13:57
I made some path_corners in a map that are different z distances (hight/lower than another path_corner) apart, thinking that my flying monster would follow them. To my surprise, it didn't. After removing the z distance from my path_corners, the monster follows the path just fine.
It seems that with flying monsters, if path_corners are different z distances apart, they 'ignore' the path_corners. From a brief look at the quakec, everything looks ok. Maybe this is a bug in PF_walkmove? Looking at the function, it looks like it ignores the z axis all together (line 28), so maybe it's not a bug, Carmack just ignored it :p.
Note: This doesn't affect walkmonsters (as much) as flying monsters. With walkmonsters, they seem to be able be able to find their next path_corner as long as the z distance between them isn't too much (I haven't found out exactly what it is).
Looking a monsters.qc at the walkmonster_start_go and flymonster_start_go functions, and more specifically at if statement testing if there's a target, walkmonsters set their .ideal_yaw to vectoyaw(self.goalentity.origin - self.origin), flymonsters don't do this. I have no idea if that statement is related to the problem, correlation does not imply causation. :)
I need to fresh up on my trig..
#598 posted by necros [99.227.131.204] on 2011/06/29 20:52:34
it is a bug with movetogoal.
movetogoal does not check z unless both .goalentity AND .enemy is set.
if .enemy is set to a path_corner, it actually will track vertically. unfortunately, this necessitates a qc change.
ALSO, if you can believe it, THIS SAME FUCKING BUG IS PRESENT IN DOOM 3. CARMACK. COME ON MAN.
you will not believe the incredulity i felt when i set up a nice path for my cacodemons only to have them not able to fly up or down to reach them, even with AAS computed.
 Oh Also
#599 posted by necros [99.227.131.204] on 2011/06/29 20:54:43
walkmove explicitly does ignore z but it is only a simple 'step in this direction' function.
the function you want to look at is the movetogoal one, which does the random monster bumping around stuffs and probably contains the vertical adjustments for flyers when chasing enemies.
 Necros
#600 posted by jt_ [68.42.82.10] on 2011/06/30 01:23:14
Upon reading your posts, I naively added ``self.enemy = self.goalentity'' right after path_corner check in *monster_start_go functions in monsters.qc, thinking that it would fix all of my problems. Now the monsters are facing towards the path_corner and are in their walking animation, bit they're not moving at all. D: I suspect this has to do with a change I made, as I've been adding a lot of new things and haven't been testing any of them :p. oops.
Now 'all' I have to do if find what the problem is..
#601 posted by necros [99.227.131.204] on 2011/06/30 02:27:22
you will need to haxor it in.
basically, in ai_walk, change:
movetogoal();
to
self.enemy = self.goalentity;
movetogoal();
self.enemy = world;
or alternatively, create a movetogoal wrapper only for walking:
void(float step) walktogoal =
{
self.enemy = self.goalentity;
movetogoal();
self.enemy = world;
}
and just replace movetogoal calls with walktogoal in ai_walk.
the wrapper method is probably cleaner and if you make new walking functions, you don't need to repeat the hack.
 Remember That
#602 posted by Lardarse [62.31.162.25] on 2011/07/02 17:52:49
movetogoal() also needs the distance.
 Ragged
#603 posted by MadFox [94.215.210.233] on 2011/07/15 03:23:58
I'm trying the shield code for the EldenOgre, but I'm not lucky.
I found some arg in the qc I couldn't place, or I should look at the pentagram code but I couldn't trace it.
OLDONE.QC - line 271 => self.takedamage = DAMAGE_YES;
OLDONE.QC - line 138 => pl.takedamage = DAMAGE_NO;
So I thought to be smart by adding it to the shielding frames like:
void() xogre_shield4 =[ $shield4, xogre_shield5 ] { self.takedamage = DAMAGE_NO;};
This works, but now I can't stop it shielding!
Another thing is the line in Defs.QC
DEFS.QC - line 443 => .void() th_defense; // gb, need to defend against enemy fire
The oldone.qc is the only one with the th_defense in it.
Can I use it on an entity?
Maybe a weird question for someone who knows how the code DOES work, butI thought making an entity shield is something like giving it a Pentagram?
It's not Quake related, but anyone up for helping me with a SAT based collision detection issue?
 Madfox
#605 posted by gb [46.142.11.117] on 2011/07/17 21:53:27
th_defense is an RMQ specific AI extension.
Look in ai.qc under ai_defensecheck(). If a monster's self.enemy just fired a weapon, the monster does whatever is defined in its th_defense function.
In the case of the shield ogre, it does xogre_defense() which is calling the shield animation etc.
It also sets self.shielded to 1 (in the shield frames), which is checked in turn in weapons.qc. That is where the actual projectile reflection stuff is done.
xogre is the only monster with a defined defense behaviour atm; however, you can use self.th_defense on *any* monster in RMQ. Just need to make a mymonster_defense function that contains the defensive action. If the monster's defense requires some extra jazz, like projectile reflection, add that to weapons.qc.
All of this requires RMQ.
If you want to do this in your own progs.dat, just port the ai_defensecheck(), self.shielded and all related stuff to your codebase.
I'm pretty sure Supa wrote the actual projectile reflection code in weapons.qc, so ask her (on the trac) if you need help with that.
All of this is really pretty RMQ specific stuff. Monsters actually defending against attacks is something that's still work in progress.
 Thanx Gb, For Your Explaination
#606 posted by MadFox [84.26.185.158] on 2011/07/17 22:41:47
I've been looking at the RMQ code, but as the normal QC.108 is already over my hat I thought to look at the normal monstercode.
The th.defense I found in Defs.qc and as I couldn't find it elsewhere I wondered where it could relay to.
I tried earlier to calculate the code in but when I used te RMQ code it started stuttering on other args.
 Ambush
#607 posted by jt_ [68.42.82.10] on 2011/09/06 02:48:47
Does the engine cause monsters not to make noise when their ambush flag is set? I can't find anything about it in progs.
#608 posted by Lardarse [62.31.162.25] on 2011/09/06 04:39:07
Look in the code for an if statement looking at .spawnflags & 3 - there's a comment next to it about zombies having ambush on a different flag.
#609 posted by necros [99.227.131.204] on 2011/09/07 01:23:21
no, monsters still make noise when they have ambush set.
i had to add that specifically into my progs to get silent monster wakeups.
 Then What Does The Ambush Spawn Flag Do?
#610 posted by jt_ [68.42.82.10] on 2011/09/07 04:32:22
Erm, makes them not wake up when they hear something...
#612 posted by metlslime [159.153.4.50] on 2011/09/07 20:49:40
makes them not wake up until they see you -- normally they will also wake up if a nearby monster sees you.
Kind of general programming question about Quake. How does Quake handle broad-phase collision between entities. Does it store them in each BSP leaf and only collide those in the same leaf?
 Linked In
#614 posted by Preach [94.171.245.254] on 2011/10/04 23:28:42
There is a bit of a problem with that approach, which is key to understanding how the engine actually does it. The problem is that an entity might occupy more than one bsp leaf at the same time. So you would actually need a list for each entity of all the leaves that it spans.
Since this might get unwieldy the engine does something a bit simpler, and if you're interested in finding all the code behind it then the thing to search the source for is sv_areanodes*. Areanodes actually just split the space occupied by the world into a fixed depth binary tree by repeated partition along the longest axis of the previous areanode.
That last sentence is concise and accurate, so I instantly fear it's not very approachable. It's like making a new binary space partition which almost entirely ignores the geometry of the world and only uses the minimum and maximum points. It then recursive splits the space into two equally sized parts. It does this by splitting the longest side of the remaining space to try and keep the lengths of all the sides as even as possible.
An areanode is one of the dividing lines between two of these areas, or one of the areas themselves in the case of the leaves on the bottom level of the tree. If an entity straddles a division then it is stored within the list attached to that node. If it straddles many divisions then it is stored in the list belonging to the division furthest up the tree. If the entity is wholly contained in a single area, then unsurprisingly it is stored in the list belonging to that "leaf areanode".
It's then fairly simple to pare down the list of entities to collide against. Find the areanode that your collision trace belongs to (imagine the area swept out by the movement as being a big entity), then you need only test all entities in the subtree with that areanode as the root. If you go through the exact centre of the map then you will certainly be testing against all the entities in the map, but it's not often a problem in practice.
It's worth noting that quake actually has a fixed depth for this tree of just 4. This translates to 16 leaf areanodes representing volumes in the map, along with 15 splits separating them. To illustrate the size of that, if your map is 4 times wider in both x and y than it is tall, which it's easy to imagine something like Castle of the Damned might be, then there would be no areanode splits in the z axis at all, instead creating a 4x4 grid over the map.
Presumably custom engine coders who push both the extents of the bsp format and the number of entities in a map do increase these limits somewhat, either dynamically or just to a higher static cap.
*Warning: If you look too carefully in this region of the source you might find this pair of macros which make the Rune of Black Magic look tame...
#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area)
#define STRUCT_FROM_LINK(l,t,m)
((t *)((byte *)l - (int)&(((t *)0)->m)))
ic... or at least, I think I do :P So this set up is kind of like a grid containing sub-grids kind of set up (can't remember what they're called)?
How does it manage the list of objects as it changes? ie how does it move an object from one node to another?
/slaps self for awful grammar.
 Sorting
#617 posted by Preach [94.171.245.254] on 2011/10/05 02:43:16
The term used within the engine for maintaining these lists is called linking*. You might have heard the term used in QC in discussions about SetOrigin. Typically guides would mention that if you set the origin key directly then the entity will not be correctly linked in the engine. This actually means the entity you moved will still be listed in the collision list for its old position.
In the same way that we have to be responsible and call SetOrigin every time that we move things in QC, the engine code has to call the relinking function every time that it updates the origin of an entity. The nitty-gritty of the relinking is not too complicated, just run through the areanodes until you find one that intersects your entity (or reach a leaf that contains you). Then remove yourself from the old list and append yourself to the new one.
*The term linking does have quite a nice visual metaphor of tying the entities to their areas, but I think it really only arose because the entities are stored in a traditional "linked list" structure.
#618 posted by necros [99.227.131.204] on 2011/10/05 03:44:20
It's worth noting that quake actually has a fixed depth for this tree of just 4. This translates to 16 leaf areanodes representing volumes in the map, along with 15 splits separating them.
this is also the thing that causes large bmodels to flicker or disappear, i believe. especially with rotaters because qbsp errs on the side of (paranoid) caution and makes the bboxes massively oversized. it also doesn't take into account the actual rotations that you will be subjecting the rotater to (this is more understandable though), so if you had like a 1024 long brush that had just a 4x4 cross section, the bbox would still be roughly 1024x1024x1024, even if it only rotated along the long axis.
this makes it get connected to tons of those leafs and and overloads the engine. i guess it just either discards the first links it made, or stops linking when it hits the limit.
 It's Weird...
#619 posted by metlslime [159.153.4.50] on 2011/10/05 04:03:59
you'd think it would simply store the node of the first plane that the bbox is on both sides of. Then when testing two bboxes against each other, find out if their node pointers are related (identical nodes, or ancestor-descendant), then test bboxes directly. Maybe they did that and it wasn't fast enough on the target machines.
One thing to point out -- I attempted making a mapper-oriented console warning in fitzquake that said "entity XYZ touches too many leafs, exceeds MAX_ENT_LEAFS", but what i found was that even tiny id maps had these errors. So even e1m1 breaks the limit on a couple of entities, but you never see a symptom of it unless it goes over so much that none of the 16 leafs are in the current PVS, causing the entity to vanish.
Interesting... time to go off and read some more about data structures :)
What bits of source code should I look up to take a peek at the game loop? Interested in how it sorts through objects and knows what to do with each object :E
 Explorer
#621 posted by Preach [94.171.245.254] on 2011/10/05 10:26:30
I'd say SV_Physics() in sv_phys.c is the main loop to start with. It runs once per frame, looping through all the entities, running physics and QC functions on them. It doesn't give you everything the server does in a frame, stuff like creating the updates to be sent out to the network and parsing the input from the client is elsewhere. But it is the heart of the matter.
One thing that I've found extremely helpful in tackling the quake source code is loading it into a proper IDE like Visual Studio. Being able to right click a function name or typedef and select "go to definition" allows you to focus on figuring out what the functions do, rather than having to switch your train of thought to hunting down the function manually. The history buttons are likewise important so that you can return to the original function just as easily.
Finally "Find All References" allows you to step outwards, for instance to go from Sv_Physics back out to the rest of the server code, and see where it fits in.
 So On The Subject
#622 posted by Preach [94.171.245.254] on 2011/10/12 01:37:21
of looking far too closely at the engine physics source, is this a mistake?
trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
{
trace_t trace;
vec3_t offset;
vec3_t start_l, end_l;
hull_t *hull;
// fill in a default trace
memset (&trace, 0, sizeof(trace_t));
trace.fraction = 1;
trace.allsolid = true;
VectorCopy (end, trace.endpos);
// get the clipping hull
hull = SV_HullForEntity (ent, mins, maxs, offset);
VectorSubtract (start, offset, start_l);
VectorSubtract (end, offset, end_l);
// trace a line through the apropriate clipping hull
SV_RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace);
// fix trace up by the offset
if (trace.fraction != 1)
VectorAdd (trace.endpos, offset, trace.endpos);
// did we clip the move?
if (trace.fraction < 1 || trace.startsolid )
trace.ent = ent;
return trace;
}
Quoting from fitzquake source but I think it's unaltered. I've bolded the references to offset, because as far as I can see it never gets initialised. It doesn't actually matter in the call to SV_HullForEntity because that function never reads the offset parameter, just uses it as a local variable for internal calculations!
Still, since it's not passed by reference there presumably the same junk sitting there from the uninitialised starting state. Is it just by fortunate placement on the stack that it always gets zero initialised or something? It looks like it should at least mess up trace_endpos if it really contained garbage, or worse the whole SV_RecursiveHullCheck call...
 It's Actually Passed By Reference!
#623 posted by ericw [161.184.107.5] on 2011/10/12 05:04:30
It's only evident when you check the definition of vec3_t in q_stdinc.h:
typedef float vec_t;
typedef vec_t vec3_t[3];
vec3_t is an array type, so it is always passed by reference in a function call. In this case, it looks like SV_HullForEntity always writes to the offset variable, so there's no problem.
That is really confusing, though - at first I though vec3_t was a type that is passed by value, like struct { float x; float y; float z}.
 Ah...
#624 posted by metlslime [159.153.4.50] on 2011/10/12 05:24:09
that's what i suspected too, looking at that code.
 Preach
#625 posted by inertia [68.175.2.88] on 2011/10/12 06:18:32
Do you write about code elsewhere?
 Other Writing
#626 posted by Preach [94.171.245.254] on 2011/10/12 09:57:34
inertia: No, this is basically my one coding outlet! I've got a little article coming up on names in qc which lead me to look up this function and get confused.
ercw: Thanks! Been away from c coding too long I missed that vector thing. And I really should have seen it given the way that the VectorAdd function works, clearly taking a pass by reference in order to mutate the third parameter. Ah well...
 In C++
#627 posted by RickyT23 [94.13.59.170] on 2011/10/13 20:22:16
how does a for loop initialise an integer automatically?
int a;
for (a=1; a<150; a++){cout << a;}
Works, buuuut:
int a; cout << a;
throws a compiler error with
"Run-Time Check Failure #3 - The variable 'a' is being used without being initialized."
Now obviously i could start with:
int a=0;
But what gives the for loop the right to initialise the variable before it begins testing it?
#628 posted by metlslime [159.153.4.50] on 2011/10/13 21:02:56
for (a=1; ...
The a=1 is the part that initializes the variable
#629 posted by RickyT23 [94.13.59.170] on 2011/10/13 22:08:50
Hmmmmm. I kinda suspected that. I guess I'm mis-understanding the for part. I can't help but think of it as an if.
The excercise which has gotten me confused is the following:
int i, j;
bool isprime;
for(i=1; i<100; i++){
isprime = true;
// see if the number is evenly divisible
for(j=2; j<= i/2; j++)
// if it is then it is not prime
if ((i%j) == 0) isprime = false;
if (isprime) cout << i << " is prime. \n";
It's the modulus test part, and my understanding of prime numbers which has gotten me confused.
I inserted this after the second last 'if':
cout << "i=" << i << ", j=" << j << " ";
This way it shows me what the two variables are. I don't understand why the test works.
#630 posted by necros [99.227.131.204] on 2011/10/13 22:48:09
cout << "i=" << i << ", j=" << j << " ";
looks suspiciously like some of the stuff i've been learning about linux and input redirection...
#631 posted by Spirit [80.171.98.14] on 2011/10/13 23:01:58
What exactly don't you understand?
The if (isprime)? If you test a variable without explictly writing eg isprime == 42 it will test if the variable is true (or not false or something like that).
#632 posted by RickyT23 [94.13.59.170] on 2011/10/13 23:16:39
%
% is what i dont understand lol.
Why is 2%3 equal to 2 ?
Why is 45%89 equal to 45 ?
#633 posted by Spirit [80.171.98.14] on 2011/10/13 23:20:01
 Yeah - I Just Read That, Incidentally.
#634 posted by RickyT23 [94.13.59.170] on 2011/10/13 23:36:20
The most useful bit of information I could find on that page was:
^ ISO/IEC 14882:2003 : Programming languages -- C++. 5.6.4: ISO, IEC. 2003. "the binary % operator yields the remainder from the division of the first expression by the second. .... If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined".
The word "nonnegative" scares me a bit TBH.
#635 posted by necros [99.227.131.204] on 2011/10/13 23:36:43
modulus is basically the remainder. like you do long division and stop before going into decimals.
so 2%3 is 2 because 2 can't fit into 3 at all, so you have 2 as the remained.
same with 45%89.
otoh, if you had 3%2, it's 1, because 2 fits into 3 once, and you have 1 left over.
 Necros
#636 posted by SleepwalkR [130.149.148.83] on 2011/10/14 09:44:00
I'm sure you meant to say: 3 can't fit into 2 at all, that's why 2 % 3 = 2. Look at this, Ricky:
0 % 3 = 0
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
etc.
Just try a few examples and you'll get a feel for what the modulo operator does.
 Thanks Necros & SleepwalkR
#637 posted by RickyT23 [217.44.35.184] on 2011/10/14 10:36:27
I also figured he meant a three, but I was pretty cross-eyed already ;)
I think I'm starting to understand it. C++ is weird though, because sometimes you get a negative short. Which is weird. BUT I'm beginning to get my head round it. Which means I'm learning :D
 Sleep
#638 posted by necros [99.227.131.204] on 2011/10/14 22:22:36
haha, apparently math doesn't fit at all into my head. :P
 Ricky
#639 posted by SleepwalkR [85.179.157.100] on 2011/10/15 08:33:22
You're probably messing up the types (using signed short instead of unsigned or something).
 Shoutouts To Awesome Lines Of QC Code #1
#640 posted by Preach [77.98.165.95] on 2011/12/13 16:31:43
A real gem from the walkmonster_start_go code I've never noticed before today:
self.ideal_yaw = self.angles * '0 1 0';
Why not just use self.angles_y? Because that wouldn't be vectorised and involve lots of awesome multiplication!
 Why Not Just Use Self.angles_y?
#641 posted by mh [109.79.197.104] on 2011/12/15 02:33:01
...because then self.ideal_yaw_x and self.ideal_yaw_z may not be set to 0.
#642 posted by necros [99.227.131.204] on 2011/12/15 02:40:51
ideal_yaw is a float though
 Yeah
#643 posted by Preach [77.98.165.95] on 2011/12/15 09:57:08
It's the dot product, so it returns the sum of all three components once you do the componentwise multiplication. Very handy some of the time, but a bit of a waste here.
 Rocket Trails
#644 posted by Mike Woodham [86.174.74.100] on 2011/12/25 09:40:59
What is it that gives rocket (and grenades) their flight trails?
 Model Flags
#645 posted by Preach [86.129.214.167] on 2011/12/25 20:06:18
There are flags you can set on models which select which (if any) effect that applies. If you open one with QMe it gives you a nice tickbox interface if you bring up the model properties box.
 Flags
#646 posted by Mike Woodham [86.174.74.100] on 2011/12/25 20:22:34
Thanks Preach
 Just To Clarify
#647 posted by Preach [86.129.214.167] on 2011/12/25 20:51:30
This is distinct from the QC .flags field - you can't change the particle effects from QC (other than having multiple models and switching between them).
 Flags
#648 posted by Mike Woodham [86.174.74.100] on 2011/12/25 22:23:56
Understood: I found the flag on the model immediately and was able to change them as required.
Is there any effect on performance if I suddenly add lots of these 'particle' effects?
#649 posted by necros [71.99.108.193] on 2011/12/26 02:18:04
i don't think you'll feel it on an engine with the original particles (or even fitz' circular ones) but DP and other engines with 'fancy' particles can start to chug. i know some players using DP found the lava splash effects in ne_ruins would just kill their framerate, for example but i wouldn't even notice it in quakespasm.
#650 posted by Spirit [80.171.98.65] on 2011/12/26 11:35:57
I think older engines have a maximum number of particles they will show.
#651 posted by necros [99.227.131.204] on 2011/12/27 00:13:01
even glquake had -particles though and i always used to use -particles 20000. these days, i use -200000, but i think modern engines don't even use that anymore? i just leave it because it's in a batch file. :P
 Five Particles
#652 posted by Preach [86.129.214.167] on 2011/12/27 00:24:25
A hell knight fires 5 projectiles with trails in a single attack. Launching 1 projectile with five trail should be within scope.
 I Forget...
#653 posted by necros [99.227.132.108] on 2012/01/01 19:14:37
is:
val = random();
val = val + random();
val = val + random();
val = val + random();
the same as:
val = random() + random() + random() + random();
?
 Should Be
#654 posted by ericw [142.179.129.40] on 2012/01/01 21:03:09
as long as assigning the result of random() to val doesn't lose any information.
e.g. for the first case, if the language was C, val was an int variable, and random() returned a float between 0.0 and 1.0, the right hand side of each statement would be truncated to an integer before being stored in val, so in most cases you'd end up with val as 0, unless one of the random()'s returned exactly 1.0. In the second case the floats would be added up before being truncated to an int.
I think in QuakeC they are equivalent because random returns a float and the only numeric type is float.
#655 posted by necros [99.227.132.108] on 2012/01/01 21:08:25
this is purely for quakec.
my question was along the lines of ftos() vs random() and if random() has the same problem ftos does.
my head tells me it shouldn't, since floats are primitives, but my gut tells me qc may do things in a weird way.
#656 posted by ericw [142.179.129.40] on 2012/01/01 21:26:14
Ah. here's the implementation of random: (pr_cmds.c)
static void PF_random (void)
{
float num;
num = (rand() & 0x7fff) / ((float)0x7fff);
G_FLOAT(OFS_RETURN) = num;
}
it looks fine.. I think the "G_FLOAT(OFS_RETURN) = num;" just stores the result in the vm global for return values. so it's not using any shared buffer like ftos.
... but I'm a qc noob so maybe someone more experienced should chime in :-)
#657 posted by necros [99.227.132.108] on 2012/01/01 22:09:49
i'd totally test this, except it's random. XD
 Return Value
#658 posted by Preach [77.98.165.95] on 2012/01/02 00:42:01
Yeah, it's basically about return values in C. A return value of a float is passed by value - i.e. copied to the return value and therefore to the QC. So subsequent calls to rand get different values.
The problem with ftos is that strings in C aren't primitives. Instead what gets returned is a pointer, and passing a pointer by value is effectively passing the string by reference. In the case of ftos there is only a single buffer used, so the pointer which is returned always has the same value: the address for the start of the buffer.
#659 posted by necros [99.227.132.108] on 2012/01/02 00:58:24
in a way, qc is a lot like java...
thanks, preach.
 Interesting Micro-optimisation
#660 posted by Preach [77.98.165.95] on 2012/01/02 11:07:07
The idea that the pointer doesn't change means you can save valuable QC operations by not assigning the return value of subsequent calls to ftos or vtos!
local string a;
a = ftos(self.health); //store pr_string_temp in a
dprint (a);
ftos(self.max_health); //a already stores the pointer to pr_string_temp
dprint(a); //so no need for an assignment
vtos(self.origin);
dprint(a); //vtos uses the same string buffer
Remember: saving 3 QC commands in your debugging routines should be your top priority when coding.
To make it even more efficient, why not just use a global!
string pr_string_temp;
void() worldspawn
{
...
pr_string_temp = ftos(0);
...
}
Never assign to the variable again, and just use the following pattern in all ftos code:
ftos(self.health);
dprint(pr_string_temp);
Although it was born from a daft optimisation idea, there is one nice thing about this coding structure. It makes explicit the gotcha with using ftos - that internally it uses a single temporary buffer. Consequently you'd be much less likely to use it incorrectly by calling ftos twice without writing the buffer to screen or console first.
 Multiple Replies
#661 posted by Pineapple [62.31.161.177] on 2012/01/02 11:29:12
#653:
In theory, yes.
In practice, the original QCC has a bug where only the last of the return values is actually used if they appear in a single statement, so it becomes random()*4. Spike claims to have fixed this in FTEQCC, but I remain slightly skeptical.
#660:
Note that some engines will likely break this behaviour...
#662 posted by necros [99.227.132.108] on 2012/01/02 18:39:18
In practice, the original QCC has a bug where only the last of the return values is actually used if they appear in a single statement, so it becomes random()*4. Spike claims to have fixed this in FTEQCC, but I remain slightly skeptical.
So we're back to it not working then? :(
 Depends
#663 posted by Preach [77.98.165.95] on 2012/01/02 21:21:42
Some compilers will do this wrong thing:
• call random once - store the return value
• call random again - overwrite the return
• call random yet again - overwrite the return
• call random finally - overwrite the return
• add the final return value to itself four time
The difference between this and the ftos problem is that ftos overwrites the result on the engine side, but this bug occurs on the QC side so better compilers can fix it. I'm pretty confident that FTEQCC compiles this correctly but you could test it with the following code:
float counter;
float() increment_counter =
{
counter = counter + 1;
return counter;
}
void() test_compiler =
{
local float total;
total = increment_counter() + increment_counter() + increment_counter() + increment_counter();
dprint( ftos (total));
}
Compilers with the fix should output 1 + 2 + 3 + 4 = 10, the original qcc compiler will output 16. There's no difference here between a builtin returning a float and a compiled QC function which does the same.
#664 posted by necros [99.227.132.108] on 2012/01/02 22:05:16
excellent! everything is peachy, preachy!
 Wow
#665 posted by SleepwalkR [130.149.216.250] on 2012/01/03 10:06:40
you are a poet!
|