Func_Msgboard: Coding Help

News | Forum | People | FAQ | Links | Register | Log in

|  | Coding Help

Posted by metlslime [71.202.113.29] on 2007/08/08 04:57:56
This is a counterpart to the "Mapping Help" thread. If you need help with QuakeC coding, or questions about how to do some engine modification, this is the place for you! We've got a few coders here on the forum and hopefully someone knows the answer.

First 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
http://celephais.net/board/view_th... see post #2
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
http://qexpo2005.quakedev.com/boot...
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
#505 posted by ZealousQuakeFan [188.220.248.12] on 2011/03/30 03:26:56
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!
#518 posted by ZealousQuakeFan [188.220.248.12] on 2011/03/31 12:09:00
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?
#604 posted by ZealousQuakeFan [188.220.248.12] on 2011/07/17 02:47:36
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
#611 posted by ZealousQuakeFan [82.16.80.21] on 2011/09/07 12:01:34
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.
#613 posted by ZealousQuakeFan [82.17.114.249] on 2011/10/04 21:52:12
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)))
#615 posted by ZealousQuakeFan [82.16.80.21] on 2011/10/05 01:27:26
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?
#616 posted by ZealousQuakeFan [82.16.80.21] on 2011/10/05 01:27:52
/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.
#620 posted by ZealousQuakeFan [82.16.80.21] on 2011/10/05 05:32:59
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
http://en.wikipedia.org/wiki/Modul...
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!
#666 posted by Willem [174.109.109.171] on 2012/02/14 14:52:29
OK, some basic QuakeC questions here...
1. I know if I set the velocity on an entity, it will start moving in that direction. I read that avelocity is a force acting on that. Does that degrade over time or something? Like, is it a temp force or is it always there?
2. Velocity moves an entity regardless of it's angles, correct?
3. Is there a quick and dirty way to tell an entity to face the direction that it's moving in? Like, say I fire a rocket from a point in space -- I want it to face the direction that it's moving in. This is escaping me for whatever reason.
Thanks! :) Hopefully these aren't too rudimentary.
#667 posted by Willem [174.109.109.171] on 2012/02/14 14:54:41
To expand on the rocket things, say the rocket fires from EntityA towards EntityB. So the direction is:
normalize( EntityB.origin - EntityA.origin ); (or vice versa, I can never remember that)
That gives me a unit vector I can use to calc a velocity for flying but how do I make it point at EntityB while it's doing that?
#668 posted by necros [99.227.132.108] on 2012/02/14 20:17:24
1. avelocity is angular velocity and has no bearing on velocity. it does not degrade either. the engine just increments .angles by this much every second.
2. velocity moves an entity IF it's .flags does NOT contain the ON_GROUND flag.
yes, it does move it regardless of angles. to move in the direction of angles, you need to do makevectors(self.angles)
self.velocity = v_forward * someSpeed
3. yes: self.angles = vectoangles(self.velocity) turns the velocity into an angle.
#669 posted by Willem [174.109.109.171] on 2012/02/15 02:25:04
Excellent, thank you! Yes, what you said in #3 fixed my rockets as well. Woot!
Contentious Quake Design Nitpicking
#670 posted by Preach [77.98.165.95] on 2012/02/18 21:51:04
I contend that the th_* style ai functions are over-engineered in the original qc code.
There is not, to my recollection, a single instance where quake monsters share a th_* function like th_melee, th_pain etc. Therefore it would be best to just have a single function
.float th_handle(float eventnum)
for each monster. The eventnum would then have EVENT_MELEE, EVENT_RUN and so on, and which handler to run would be chosen from a switch statement. There's no reason to suppose that the handling code would all go inline, most likely each statement in the switch would just call the relevant function.
The two big wins of this change would be:
Firstly to get rid of all that cruft for all the other entities in the quake universe.
Secondly to allow many more 'events' to be handled. Whack in default handling which does nothing and you can add event handling for just selected monsters. Things currently dealt with by hacky classname searching like monster waking sounds or attack decision making could be refactored into events.
Having laid out a grand vision, I would grant mercy to the special case of th_pain. Mercy is extended on the grounds that pain functions are parametrised on attacker and damage, and hacking around that would probably be worse than just letting it stay.
#671 posted by necros [99.227.132.108] on 2012/02/18 22:09:25
wouldn't it be better to expand the .functions available to monsters?
for example, replacing all the sight sound checks with wake functions so each monster could have it's own method of playing sounds (some monsters might want different attenuations, other might play random sounds).
one thing i liked about doom3 monsters was that their animations and thinking were separated into two threads. that'd be interesting to do in quake, have your monster model with frame handling and some invisible thinking entity that actually animated based on events and such.
Hierarchy
#672 posted by Preach [77.98.165.95] on 2012/02/18 23:01:31
Expanding the available functions is exactly the sort of thing that was motivating me. The feeling I have is that there's a tension between adding all the fields a monster could ever need and not burdening all the other entities in the game with stuff they don't use. This leads us to the current compromise where the most vital events get handlers and the rest get hacks.
Adding new fields is possible, but if you wanted to support 50 or 60 events it would get pretty crazy. In a world where lots of monsters shared th_melee or th_stand functions, or even where such things might change dynamically for a given entity (like an injured monster switching to a limping run) the current architecture would have more weight. But as it is, you're born with the think functions of one class, and you die by it, so why not bundle the whole package into one function for the monster to carry about?
#673 posted by necros [99.227.132.108] on 2012/02/18 23:27:32
i know what you mean about the th functions...
i run into it when designing a boss monster that doesn't fit into the standard mold of 1 melee, 1 ranged.
i usually end up overriding the checkattack function for those monsters with a function that directly calls whatever attacks are needed.
still, a function field is stored as, what, an integer? or float? 32 bits is not really a big deal, even with thousands of entities.
seems like it would be more hard to deal with code where there is a single entry point for all monsters and hundreds of checks for each one.
that would be very annoying, i'd think.
Sketching
#674 posted by Preach [77.98.165.95] on 2012/02/18 23:33:18
You could have a list of the events starting with the mandatory ones
//====MANDATORY====
float EVENT_STAND = 1;
float EVENT_RUN = 2;
float EVENT_MELEE = 3;
...
float EVENT_WAKE
float EVENT_DIE = 8;
//=====OPTIONAL====
float EVENT_OPTIONAL_START = EVENT_DIE + 1;
float EVENT_STRUCK_BY_LIGHTNING = EVENT_OPTIONAL_START;
float EVENT_SHOT_MIDAIR = EVENT_STRUCK_BY_LIGHTNING +1;
...
Then the pattern for event handling goes:
void monster_shamber_handler(float eventnum)
{
if(eventnum < EVENT_OPTIONAL_START)
{
//code to handle events all monsters are expected to deal with
//even if grunt response to EVENT_MELEE is no-op etc.
return;
}
else
{
//handle any other selection of events with set of if..else lines
}
}
In theory you could do some sort of jump table with the mandatory events using the event number as an index. Of course, since qc doesn't really handle integer calculations all that well you'd probably find the most efficient solution was to redefine all the event numbers to already be the desired offset into the table stored as an integer bytewise. Assuming you need 4 qc instructions per handler to call a function and return you'd be looking at
float EVENT_STAND = 0.00000000000000000000000000000000...
And so on...wasn't there a compiler that let you define integer constants like %4.
HUD Text Shizzles
#675 posted by Kinn [109.150.216.190] on 2012/02/21 19:35:29
I've never ever looked into mucking around with the HUD before - but would like to do one thing if possible...
You know when you press tab to see your monster and secret counts? Is it possible with QC to change the text "secrets" to say something else? As well as on the end level tally screen?
Kinn:
#676 posted by metlslime [159.153.4.50] on 2012/02/21 19:44:34
the score bar (when you press "tab") is hard-coded in the engine.
The intermission screen is an image, so you could edit that image to say anything.
Ah Ok
#677 posted by Kinn [109.150.216.190] on 2012/02/22 00:30:35
hmmm, so i can change one but not the other :/
#678 posted by necros [99.227.132.108] on 2012/02/22 00:40:13
you could always make your own menu/screen.
even with stock quake exe you can make a functional menu that pauses the game.
#679 posted by Kinn [109.150.216.190] on 2012/03/04 00:26:36
you could always make your own menu/screen.
Nah that would be huge overkill for what I'd be doing. Besides, I realised what I originally had in mind is probably just a silly gimmick anyway, I'm gonna shove it to one side for now.
Brushmodel Texture Swapping
#680 posted by Kinn [109.150.216.190] on 2012/03/06 16:36:20
So let's say I have a brushmodel and i want the texture it wears to change according to events. From what I understand, all I can do is this:
- have an (optionally) animated looping texture with frames following the +0blah, +1blah etc. convention, then change to a non-animated image with the +ablah naming convention, by setting frame = 1 on the bmodel.
What I'd like to do is have 4 different non-animated textures, and i can just swap between any one of these images.
I assume it's not possible without doing crap like having 4 seperate bmodels and hiding 3 of them (or I guess just using 2 if i abuse +0blah and +ablah).
Or is it?
Also I don't wanna use a .mdl
#681 posted by necros [174.113.119.133] on 2012/03/06 19:40:49
yeah, you'd need multiple bmodels.
the frame stuff is hard coded, unfortunately and treats +0 ... +9 as a frame group, hence frame = 0 and 1 only.
Ok Cheers
#682 posted by Kinn [109.150.216.190] on 2012/03/06 20:06:44
I'll move to plan B which is to do the effect with sprites. Lighting won't be ideal, but i think i can get away with it.
Lightning Bolts
#683 posted by Kinn [109.150.216.190] on 2012/03/07 17:52:54
so, spawning a lightning bolt:
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_LIGHTNING2);
WriteEntity (MSG_BROADCAST, self);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
WriteCoord (MSG_BROADCAST, self.enemy_pos_x);
WriteCoord (MSG_BROADCAST, self.enemy_pos_y);
WriteCoord (MSG_BROADCAST, self.enemy_pos_z);
no matter what i set the vector org to, it only spawns the bolt starting from the entity origin - basically the calls for setting the starting point are ignored - is that how quake works?
Only If The Given Entity Is A Player, Otherwise It Works As Expected
#684 posted by czg [213.112.239.56] on 2012/03/07 18:40:50
Ah I See
#685 posted by Kinn [109.150.216.190] on 2012/03/07 18:44:17
sneaky, sneeaky quake :}
#686 posted by necros [174.113.119.133] on 2012/03/07 21:10:16
note also that you can only have one lightning bolt per entity.
these days i build my lightning bolts manually out of edicts because every engine besides fq/qs tries to be cute and put in effects. :(
Stealing Weapons
#687 posted by Mike Woodham [109.156.118.88] on 2012/03/10 15:29:42
What do I need to consider when trying to implement something like this:-
void() steal_weapons_use
{
other = activator;
if (self.spawnflags & SPAWNFLAG_SHOTGUN)
{
other.items = self.items - (self.items & IT_SHOTGUN);
other.ammo_shells = 0;
other.weapon = IT_AXE;
}
};
Or am I running in treacle here?
I can see it taking the weapon and the shells, but it breaks with an error in the weapon_cycle command.
Activator's Other Self
#688 posted by Preach [77.98.165.95] on 2012/03/10 16:03:07
There seem to be three entities in this story, and I'm having to guess who is who:
self: presumed to be an entity added to the map as a trigger perhaps?
activator: if the above is correct then activator is whoever set off the trigger called self
other: known to be the same entity as activator
The first thing seems to be that other is unnecessary, which makes the function confusing. If we replace other with activator we get:
void() steal_weapons_use
{
if (self.spawnflags & SPAWNFLAG_SHOTGUN)
{
activator.items = self.items - (self.items & IT_SHOTGUN);
activator.ammo_shells = 0;
activator.weapon = IT_AXE;
}
};
We can then see more easily what might be dangerous. We only act if self has the spawnflag for removing shotguns. On that condition, we then set the activator's items to be equal to self's items with item shotgun removed. I'm pretty sure that this line should be referring to activator all the way through.
Thanks Preach
#689 posted by Mike Woodham [109.156.118.88] on 2012/03/10 16:20:21
I didn't see the wood for the trees.
#690 posted by necros [174.113.119.133] on 2012/03/10 18:36:38
don't know if you trimmed things out for clarity, but you should probably add a quick check:
if (!activator.flags & FL_CLIENT)
return;
just to avoid any possible problems in the case of monsters some how activating it.
Spawn()
#691 posted by necros [174.113.119.133] on 2012/03/10 19:10:01
is spawn() completely reliable? is there any time where it can fail and not create anything? or it creates an entity but the entity somehow gets lost? maybe if you're spawning many entities in the same think?
Invalidation
#692 posted by Preach [77.98.165.95] on 2012/03/10 19:24:36
In the standard implementation spawn only fails if there are no free edicts, which produces a game-ending error. As long as it doesn't error out, it returns some entity and as far as I can see there's no way this entity could fail to be a clean slot. But that entity might be more familiar than it seems.
Edict slots aren't allowed to be reused with 0.5 seconds of an entity being removed from a slot*. However, if a reference to an removed entity persisted longer than that it could end up creating a reference invalidly relating to a newly spawned entity. Could a stale reference be somehow responsible for your issue?
*This is not true for the first two seconds of the map, in order to not waste lots of slots if entities are loaded from the map but removed by the qc straight away.
#693 posted by necros [174.113.119.133] on 2012/03/10 19:39:50
right, i totally forgot about that! i still had that .doNotRemove trick from before and i found that the entity is indeed being removed by something else. thanks, i'm off to track that down. :P
And
#694 posted by necros [174.113.119.133] on 2012/03/10 19:55:18
it's done. thanks! turns out i had some sloppy linked list clean up. i was clearing out the list but forgot to clean up the head link. :P
Not Qc...
#695 posted by necros [174.113.119.133] on 2012/03/17 06:23:27
this is java, but it's more of a conceptual thing...
finally started working on the inheritance aspect of fgd files for my fgd<>def converter thingy...
essentially, i have multiple lists of objects which represent key/val pairs. these lists can have duplicate entries from base classes that an entity inherits from.
eg: you have 'monster_army' which inherits target/targetname fields from the base 'monster' class.
what i'd need to do is, on demand, collapse the multiple lists into a single one.
ie: when i 'get' a list element, i should iterate through a single apparent list:
monster_army has the fields: (inherits from 'monster')
0: B
1: C
2: D
monster has the fields:
0: E
1: C
2: A
should look like:
0: B
1: C
2: D
3: E
4: A
(duplicate C in 'monster' is ignored because 'monster_army' already had that field)
i can work through the logic of building a new list myself, but the problem is that there are many times where i need to iterate through the (final) list, so every time i .get(i), i'll have to rebuild this list which feels wrong.
is there maybe some more complex data structure that would work better for this? i'm thinking maybe trees due to their non-linear nature?
unfortunately, i've never really learnt data structures more complex than a simple linked list. :S
or maybe there's some absurdly easy solution and i'm just not seeing it.
#696 posted by ericw [23.17.185.198] on 2012/03/17 07:13:59
you should be able to get away without building the final list. I'd store the key/value pairs in a hash table instead of a list of key/value pair objects, and have an object for each "quakec class". then set up a recursive get() function which first checks the object-being-called's key/value pair mapping (e.g. Grunt), and if the requested key wasn't found, call the get() method on the superclass (e.g. Monster) (or return null if there is no superclass - so for a key requested that isn't in either Grunt or Monster).
something like this:
class EntityClass {
EntityClass superclass;
HashMap<String, Object> fields;
Object get(String key){
if (fields.containsKey(key)) {
// the requested key is stored directly in our class
return fields.get(key);
} else {
// the requested key is not in our class, try searching
// our superclass.
if (superclass != null) {
return superclass.get(key);
} else {
return null;
}
}
}
}
hope that helps..
btw here's the javadoc for HashMap.
http://docs.oracle.com/javase/1.5....
#697 posted by czg [83.253.15.82] on 2012/03/17 08:55:39
If you're only storing the keys and no value with them, I'd recommend using a Set instead of a Map. You should read up on the Java collections framework, as in most cases it already solves a problem for you.
To Lob Or Not To Lob, That Is The Question
#698 posted by Mike Woodham [86.174.73.229] on 2012/03/17 10:30:47
I have a Lavaman in my map (FMB_BDG) and he does a good job except I notice that there is a point where I can stand and the missiles go over my head. If I move closer, they hit me and if I move further away they hit me up to a point where they drop in front of me.
I can see that MOVETYPE_BOUNCE seems to affect the 'lobiness', and velocity_z seems to affect the height of the lob. What is affecting the gap between the hit range and the no-hit range? I am not too worried about the far distance as the missile falling short is not an issue because I can contain the player, but the over-me-'ead-john lob is not good.
If I change to MOVETYPE_MISSILE and ignore velocity_z I get a straight line missile a la roquette, but this is not good where the player is higher or lower than the Lavaman as dodging becomes too easy.
Are there any good web-sites that explain weapon behaviour (in short sentences with no long words, and mathematics that any four year old can understand)?
Ericw++
#699 posted by SleepwalkR [85.178.55.133] on 2012/03/17 12:58:34
This is also how I would do it. Note though that the keys will be unordered. If you need them to be sorted in some way, consider a SortedHashMap, e.g. TreeMap.
Hash Maps
#700 posted by Preach [77.98.165.95] on 2012/03/17 13:43:35
I think everyone is right on Necros' problem that using sets/maps is the way to go, but I think that the implimentations so far have been backwards: So far we've had how to check if a specific element is a member of one or more sets, but what necros is looking for is the ability to list all the unique elements in a collection of sets, omitting duplicates.
The key to doing this is set union. Have a set of properties for the monster, a set of properties for the grunt. When it comes time to output the combined list, use the addAll method to create combined lists which omit duplicates.
Lobbed Projectiles
#701 posted by Preach [77.98.165.95] on 2012/03/17 14:15:16
The most important mathematical idea for understanding projectiles is being able to imagine the movement in just one direction, to ignore all the other motion.
In particular imagining just how the height changes is important, but it's also the most difficult. It's also quite hard to deal with the idea of projectiles that can go north-south and east-west.
So lets just fix our boss facing north, and think about the horizontal movement of the projectile first. It turns out that the northwards movement of the projectile is exactly the same for MOVETYPE_MISSILE and MOVETYPE_BOUNCE. We fire it with some speed to the north, and it keeps going at a constant speed. Most importantly it takes the same amount of time to come in line with the player.
Now we can think about how the height changes. The MOVETYPE_BOUNCE has exactly the same behaviour as a falling player. Measure the amount of time the MOVETYPE_MISSILE takes to hit the player. If a player could fall from the height the projectile is thrown at down to the height of the target player in that time then the MOVETYPE_BOUNCE will score a hit, otherwise the shot will sail over his head.
So what can we do if it does sail over his head? Well, one trick is to start the projectile with a negative velocity_z, so it is already moving downwards. This will make it fall further during the flight and hopefully hit the player. Alternatively we can reduce the speed it travels north at. This will give a longer flight time and so longer for it to fall. We could combine these two ideas if needed.
Calculating exactly how much adjustment is needed is where you have an unavoidable level of fairly grizzly maths. I'm sure I wrote something about this particular problems but I can't remember if I finished it and ever posted it. You can probably get a good enough function by just measuring the threshold at which projectiles start going overhead, and applying one of the two above fixes when the player is that close(using trial and improvment to get values that work well).
Thanks Preach
#702 posted by Mike Woodham [86.174.73.229] on 2012/03/17 16:12:46
So, with a zero velocity_z, does the projectile fall from the horizontal at a predetermined and constant rate i.e. no acceleration? And if so, do we (you) know what that rate is?
I'm just thinking that a series of If Thens could adjust the velocity_z and velocity to get to the target in the given time. (Not sure what the player would think of that?)
Basically
#703 posted by JPL [82.234.167.238] on 2012/03/17 16:13:52
z is a parabolic function, depending of initial velocity, the initial direction/angle and the weight of the projectile, so to say gravity...
x and y can be linear it eases calculation, unless you want to add some wind effects, etc..
Wikipedia gives some mathematics clue for this: http://en.wikipedia.org/wiki/Traje...
Experiment !
Gravity
#704 posted by Preach [77.98.165.95] on 2012/03/17 16:51:42
JPL is right that it's not a constant rate, it's an acceleration, and the easiest way to understand it is to experience it in-game. Because gravity affects players in the same way as bouncing missile, you can see exactly how it changes just by falling. In particular if you go on ziggurat vertigo then you can see the change in speed happen in slow motion, which makes it easier to gauge. The next paragraph is optional mathematical detail:
Standard quake gravity is 800 units per second per second. What that means is that after 1 second of falling (from rest) a projectile will be travelling down at a velocity of 800. After 2 seconds that speed has reached 1600 and so on until it reaches the maximum speed the engine allows (which defaults to 2000 units per second I believe). The speed increases smoothly, after 0.5 seconds the projectile has velocity 400 and so on, but this constantly increasing speed means that position changes irregularly.
Despite that, we can use approximations here. The trick is to restrict ourselves to a controlled case. Work out the distance at which the shots are missing the player, and only apply this approximation within that range.
Since we know that the shots we are correcting are close range, we can assume the flight time will be 1 second or less. We can then approximate the falling motion of the projectile as a constant speed of 400 units per second*. Based on that we can do some fairly simple calculations based on the relative height of the player and the projectile's start location. If these differ by more than 400 units then add the difference to the velocity.
The important thing is to get that typical flight time right for the shots we want to correct, detect when we're in that case and then issue appropriate corrections. If my guess at the timescale is wrong please let me know and I'll recrunch the numbers.
*This certainly looks like I just averaged the starting speed of 0 with the final speed (at 1 second) of 800. I will be belligerent and claim I actually performed an integral of the accelerated motion to discover the exact distance a projectile covers in 1 seconds, the answer being 400.
Experimenting...
#705 posted by Mike Woodham [86.174.73.229] on 2012/03/17 17:48:40
Well, the first thing I did was to manipulate velocity_z, with so-so results. However, by just thinking a little less, and velocitising a bit more, I found a figure for velocity that covers the distance perfectly, whilst still providing an obvious 'lob' effect. The only downside is that if you get too close, you do not really have time to get out of the way.
Still, given that the player starts off x distance from the Lavaman, which IS a dodgeable distance, and given that he does not HAVE to get closer if he does not want to, I may just reduce velocity a bit according to the distance to target, and settle with what I have.
It's not that I don't want to do the maths, it's just that this is just one monster in one scenario for my final Quake map.
Thanks Preach and JPL for the guidance.
#706 posted by necros [174.113.119.133] on 2012/03/17 19:00:10
wow thanks to everyone for pointing me towards those other data structures.
looking around, it seems like LinkedHashSet is the way to go as it is supposed to be only slightly slower than a HashSet (but not as slow as a TreeSet) and preserves the order based on when they were added.
I don't think the Map interface is what i need, since i'm storing more than just a relation between a key.
FGD has many extra bits for key/vals including data type (Target/TargetName/Integer/String/... along with (if a choice) the individual choices as well as a default value (if any), so all that has to be in there, but i'm only interested in the key's name when building the list (as that's how i want to determine if it's a duplicate or not)
anyway, i'll throw some more code at the problem and see what happens!
Reaction Time
#707 posted by Preach [77.98.165.95] on 2012/03/17 19:20:43
One of the interesting things about projectiles is that for a fixed launch speed there are two angles you can fire them to land at a given range*.
http://en.wikipedia.org/wiki/File:...
The above illustrates how that works, but the reason I raise it is because the high lobbed shot, which I think of as the less obvious of the two paths, has a longer flight time. The difference is greatest on the shortest ranges.
This would be a great solution to giving the player enough reaction time. It also would be good at lobbing over any cover between the player and monster, which might make the combat more interesting.
There is however a price to pay, and that price is the need for greater computation. Essentially when you fire on a low trajectory you can land a lot of hits even without getting the range very accurate, because the horizontal line of the attack will usually intersect with the player somewhere. When you go for the tall lob shot your attacking line is much more vertical, and so you need to get the horizontal spot on.
How that would go would be:
• Calculate the vertical range to target h.
• Compute the time to drop to height h:
http://chart.googleapis.com/chart?...
(here 800 is gravity and 600 is our intended upwards velocity)
• Calculate the horizontal range to our target x
• Divide x by t to get the horizontal velocity v
We now need to make a vector with length v moving towards our target in the horizontal plane:
d = self.origin - self.enemy.origin;
d_z = 0;
d = normalize(d)*v;
Finally we set d_z = 600 and off we go!
*except for maximum range where the two solutions converge on 45°
Necros
#708 posted by SleepwalkR [85.178.55.133] on 2012/03/17 19:26:18
I seriously doubt that you need to worry about performance in your app. Or am I wrong? Also, if you have more data, you can store an object that represents that data in the hashmap with the name as the key. That's the usual approach anyway.
#709 posted by necros [174.113.119.133] on 2012/03/17 19:31:55
re 707: in doom3, you can see that happen sometimes with hellknights. they usually use the more horizontal trajectory when throwing their fire balls, but occasionally, they'll toss one way up into the air.
re 708: yeah, you're probably right about performance. anyway, managed to get it working in a quick hacked up way. i store keyval lists as arraylists on the entity definitions, but when i call the get method for a keyval index, i build the linkedhashset each time out of all those arrays.
works well for now even though it feels like this is wrong... like i should be storing all the keyval lists as linkedhashsets and using .addall.
Target Match
#710 posted by JPL [82.234.167.238] on 2012/03/17 20:18:54
I guess the main problem is not to obtain the parabolic curve, that can be even coded very simply like an exponential code (i.e x = x/2 or x = 2x). no I guess the challenge is rather to be able to obtain the projectile to perform direct hit to the player (if not moving). in order to achieve this, a reverse calculation is required: you know player position, monster position, projectile weight, hence it is possible to determine velocity and angle to apply... that could be very interesting as more realistic ;)
#711 posted by necros [174.113.119.133] on 2012/03/17 20:27:21
did this a while ago.. it was supposed to be for a leaping monsters and would trace the parabolic course in increments via a while look and tracelines so check if the leap parabola was clear...
traceFraction = 0;
while(traceProjection <= 1)
{
traceProjection = traceFraction + 0.25; //project the trace. will make for 4 traces.
local vector trace_pos0, trace_pos1;
trace_pos0 = self.origin + ( (dir * (hdist * traceFraction)) + ( '0 0 1' * ( ((vtime * 400)*(vtime * traceFraction)) + (0.5 * (vtime * traceFraction) * (vtime * traceFraction) * -800) )));
trace_pos1 = self.origin + ( (dir * (hdist * traceProjection)) + ( '0 0 1' * ( ((vtime * 400)*(vtime * traceProjection)) + (0.5 * (vtime * traceProjection) * (vtime * traceProjection) * -800) )));
traceline(trace_pos0, trace_pos1, 0, self);
bprint (vtos(trace_endpos));
bprint (" -> ");
if (trace_fraction < 1)
traceProjection = 1000000; //cause a break
traceFraction = traceProjection;
}
dunno if that code is 100% correct... or even helpful. :P
Necros
#712 posted by JPL [82.234.167.238] on 2012/03/18 08:47:14
I guess what is interesting is not that much the result, but the logical reasoning you made to obtain it ;)
#713 posted by necros [174.113.119.133] on 2012/03/18 17:56:41
oh sorry yeah, should have explained it a bit. :P
you'll notice there's two vars, traceFraction and traceProjection.
these should be only values of 0 to 1.
next is trace_pos0 and trace_pos1.
we determine these points by using that physics formula that describes position relative to time with an initial velocity and taking into account gravity. (i subbed 400 in to any 'g' variables, which, sorry to say preach, i figured out totally by guessing... ^_^;)
this is where traceFraction and traceProjection come in. if you look at the first line of the loop, traceProjection is always 25% ahead of traceFraction.
trace_pos0, then, is the origin of the traceline and trace_pos1 is 25% ahead through the trajectory.
incidentally, you could increase the 'resolution' of the trace by decreasing 0.25 to 0.1 or something to get 10 iterations instead of 4.
if you can imagine a typical parabolic trajectory, what you're doing in that code is tracing a line, first, from the starting point to 25% into that trajectory.
then, from there, tracing another 25% forward to the middle of the trajectory, etc etc.
if at any point the traceline fails (trace_fraction < 1, ie: something blocked the trace)
then we break out of the loop and notify the rest of the code what happened.
basically, i turned quake into a really crappy 3d graphing calculator. :P
#714 posted by necros [174.113.119.133] on 2012/03/18 18:05:00
oops, there was additional information i forgot about (i wrote this years ago so i forgot how it works! :D)
this is the start of the code (before that loop):
epos = self.enemy.origin;
//epos = self.enemy.origin + dir;
zsep = epos_z - self.origin_z;
dist = vlen(epos - self.origin);
dir = epos - self.origin;
dir_z = 0;
dir = normalize(dir);
if (zsep > 64) //player is too high, forget it.
return;
temp1 = epos;
temp1_z = 0;
temp2 = self.origin;
temp2_z = 0;
hdist = vlen(temp2 - temp1);
if (hdist > 768) //player is too far, stop
return;
if (hdist < 440)
{
hspeed = hdist; //do a 1 second jump.
vtime = 1;
}
else
{
hspeed = 440; //max out at 440 horizontal speed and jump higher to compensate.
vtime = hdist / hspeed;
}
i guess the important things is noting that dir is a flattened normalized vector towards the target and that vtime is the amount of time we want to spend in the air.
Necros
#715 posted by JPL [82.234.167.238] on 2012/03/18 21:06:32
Oh, I see, you made it like a move forward, rotate, move forward, rotate, etc... like polar coordinates or (better) vectorized move: interesting, I was not thinking it was possible doing it this way actually ;)
And thanks for the explanations :)
PHP Is Kicking My Ass At The Moment
#716 posted by RickyT23 [2.124.172.60] on 2012/03/23 18:08:51
The sooner I can get my assignment finished, the sooner I can get back to mapping :)
This bloody search_array thing is kicking my ass. Trying to use php to search an array of info. Its a multi-dimensional array. I swear I have looked all over for a solution and have had no luck.
http://www.php-forum.com/phpforum/...
No Php Guru
#717 posted by bear [217.115.56.186] on 2012/03/23 18:26:19
But the search_array func probably isn't built for multi dimensional arrays. Seems to be versions of that in the comments of the search array in the php docs:
http://php.net/manual/en/function....
Yeah I've Been Sifting Through That
#718 posted by RickyT23 [2.124.172.60] on 2012/03/23 19:25:54
I dunno, it seems I'm just not getting something.
I think it's to do with the way I have my array. Which is awkward because It's just the way it comes from the XML file. Blerg.
Oh God...
#719 posted by jt_ [174.252.232.14] on 2012/03/23 21:13:11
Xml...*shudders*
Yeah I Know
#720 posted by RickyT23 [2.124.172.60] on 2012/03/24 08:07:10
Any sensible person would design a DB in MySQL, but we are not allowed to use MySQL (go figure?!) - which sucks because any real-world application would use MySQL or SQL or even .net + access or whatever, but 'noooooo', not my retarded course. php + XML is the recipe of the day. Fuck it, I guess I'll learn something at the end of the day ...
Dude
#721 posted by SleepwalkR [85.178.188.49] on 2012/03/24 08:24:38
ditch array_search and iterate through the arrays manually. It's pretty simple.
Could You Perhaps Show Me An Example Of A Loop?
#722 posted by RickyT23 [2.124.172.60] on 2012/03/24 08:35:24
I can handle a stupified approach, I just wanna get a fucking A. I don't care if I have to re-design my data structure completely, I have been thinking about this for far to long now and my brain hurts...
#723 posted by Spirit [82.113.119.51] on 2012/03/24 09:41:02
Hey, XML can be fun and it is quite simple if you use a good parser. seriously!
#724 posted by Willem [174.109.109.171] on 2012/03/24 10:53:16
I enjoy the transition from, "Stupid class, can't use a database, have to use XML, *grumble*" to "can you show me an example of a loop?"
Walk before you run, Ricky. :)
Ricky
#725 posted by SleepwalkR [85.178.188.49] on 2012/03/24 17:25:06
If you don't know what a loop looks like, how can you expect to get an A in a programming class?
Here is an explanation of the most important loop constructs in PHP:
http://nefariousdesigns.co.uk/loop...
Lol - I Do Know What A Control Loop Looks Like
#726 posted by RickyT23 [2.124.172.60] on 2012/03/24 20:33:18
However that is a rather good reference!
Thanks :)
I don't really know that many types of loop, just while loops and for loops.
I think I'm getting somewhere with it now, I used the following bit of code to remove the extra level of array which was preventing me from being able to use the array_search function:
$b=$a['Rrestaurant'];
Dumb, huh? I thought I was going crazy!
Also - I WILL get an 'A'. I could have done the lame thing and just planned a static website. The level of the course is not so high that I have to do a PHP website, but I really want to learn some programming, so I set myself the challenge.
August last year, I had never coded anything really, since messing around with BASIC like 15 years ago.
Now I have coded a text-based version of minesweeper (which is 200KB, fuck)(the second half of the assignment will be probably a discussion of how I can use functions to simplify my code).
And this flippin XML/PHP project. You've got to start somewhere!
But seriously, thanks again.
Success!
#727 posted by RickyT23 [2.124.172.60] on 2012/03/24 21:51:54
OK, so now that I've figured out how to access my arrays within the array that was within an array, and started iterating through it manually with my loops, I'm having success :)
$search = 'the';
$resultsCounter = 0;
function object2array($object){return@json_...
$xml = simplexml_load_file("places.x...
$a = object2array($xml);
$b=$a['Rrestaurant'];
$count = count($b);
for ($x=0;$x<=($count-1);$x++){
$c = $b[$x];
$d = $c['Rinfo'];
if (strpos($d,$search)) { echo $c['Rname'] . '<br /><br />' . $c['Rinfo'] . '<br /><br />' . $c['Rindex'] . '<br /><br />'; $results[$resultsCounter] = $c['Rindex']; $resultsCounter++; }
}
$count = count($results);
for ($x=0;$x<=($count-1);$x++){
echo $results[$x] . ' ';
}
I'm sure that's probably pretty hideous, but its giving me nice search results :) :) :)
Doh!
#728 posted by RickyT23 [2.124.172.60] on 2012/03/24 21:53:13
$search = 'the';
$resultsCounter = 0;
function object2array($object) {return@json_decode(@json_encode ($object),1);}
$xml = simplexml_load_file ("places.xml");
$a = object2array($xml);
$b=$a['Rrestaurant'];
$count = count($b);
for ($x=0;$x<=($count-1);$x++){
$c = $b[$x];
$d = $c['Rinfo'];
if (strpos($d,$search)) { echo $c['Rname'] . '<br /><br />' . $c['Rinfo'] . '<br /><br />' . $c['Rindex'] . '<br /><br />'; $results[$resultsCounter] = $c['Rindex']; $resultsCounter++; }
}
$count = count($results);
for ($x=0;$x<=($count-1);$x++){
echo $results[$x] . ' ';
}
Now Try:
#729 posted by bear [217.115.56.186] on 2012/03/24 23:57:30
some better variable names and have a look at foreach
Yeah
#730 posted by SleepwalkR [85.178.120.41] on 2012/03/25 00:04:51
Don't call variables 'a' or 'b' - give them a name that reflects their meaning in the program.
Ah - Good Idea.
#731 posted by RickyT23 [2.124.172.60] on 2012/03/25 00:21:37
$wholeArray
$wholeCumbrianRD
$wholeRrestaurant
$wholeKey
Craving Your Indulgences Again
#732 posted by Mike Woodham [86.174.73.229] on 2012/03/25 16:32:25
I don't do much qc but whenever I do I find myself running into brick (BRICKL_0 or similar) walls.
I have a procedure that I want to run x times as selected from the editor (self.something = x). I want to say along the lines of:-
void () Proc1
do some stuff
while (self.something >0)
do this
wait random y to z seconds
self.something = selfsomething - 1
ewhile
do some other stuff
endproc
(I know I am mixing languages, I am just trying make it look obvious what I am doing)
When looking at 'think' and 'nextthink' examples, they all seem to act on an external procedure/function before returning to the calling procedure/function. I also see use of recursion i.e. a procedure/function calling itself.
Is it necessary to use 'think' and 'nextthink' for timing purposes in my ditty above or is there some other construct available. If I need to use 'think' and 'nextthink' is it therefore advisable to separate the 'while/ewhile' (or whatever loop is used) into its own proc/func?
#733 posted by necros [174.113.119.133] on 2012/03/25 16:43:40
you are thinking about it the wrong way.
best to think about what gets done in a frame and what doesn't.
nextthink and think is what you need to delay over multiple frames.
doing a while loop will do everything in the same frame.
in this case, you don't want a while loop at all.
void() proc1
{
//some stuff here
self.think = proc1; //run this again
self.nextthink = time + (some random amount here);
}
the way nextthink works sort of is that the engine looks at what nextthinks are > current time and when they become < current time, the engine sets nextthink the 0 and runs the .think function with self as the 'thinking' entity.
Necros
#734 posted by Mike Woodham [86.174.73.229] on 2012/03/25 16:58:26
So the engine is not waiting for my procedure but it will have my 'thinks' queued up? Also, do I not think of this a true recursion?
By the way, I just ovelayed the draflam2.spr with the bigexp.spr (just via an entity choice in the editor and using two entities) and it looks pretty smart. I am not sure what it will look like to a player seeing it for the first time in a game - you know how fussy Quake people are :) But it looks good enough to blow the head of a Vermis!
#735 posted by necros [174.113.119.133] on 2012/03/25 17:12:48
no, it is not recursion.
recursion is when a function calls itself.
you can accomplish this in qc with the usual method:
void() proc1
{
//stuff
proc1();
}
this also does everything on the same frame, just like for and while loops.
when you use thinks, it is the engine that is calling the function when nextthink ticks by.
for the explosion, if you're feeling adventurous, you could try throwing a bunch of MOVETYPE_TOSS invisible entities out of the explosion point that spawn explosion sprites themselves. ;)
looking forward to seeing it.
Queuing
#736 posted by Preach [77.98.165.95] on 2012/03/25 19:39:36
To add, there is no queue with think functions, you only get one. So if you have code like
self.nextthink = time + 0.5;
self.think = CleanTheDishes;
self.nextthink = time + 2;
self.think = CookDinner;
the second think function overwrites the first one and so you end up eating off dirty plates. One way to fix that is to move the code which sets up CookDinner into the CleanTheDishes function, chaining the functions one after another.
This worked because our two functions were dependent. If you need your entity to think about two independent things one solution is
to have a master think function. This should think regularly, and check timers on your entity manually. The way that playerprethink and playerpostthink handle powerups running out is an example of this pattern.
If that's too complicated, the other way is to have two separate entities, as each one has its own think function and nextthink timer.
Preach
#737 posted by Mike Woodham [86.174.73.229] on 2012/03/25 22:07:25
Nice analogy; because we don't want to be eating off dirty plates now, do we?
It is falling into place, so thanks.
QCC
#738 posted by sock [24.82.147.76] on 2012/04/04 16:52:51
I have been compiling my own progs.dat file using the tools from the ID FTP. Is there a good base of .QC files? Has the community got an agreed on base? Any recommendations? Something to start from?
All Can Be Found Here:
#739 posted by rj [86.29.94.69] on 2012/04/04 19:11:37
http://www.inside3d.com/gettingsta...
Door Key Use Sound Troubadour Verily
#740 posted by Kinn [86.176.167.133] on 2012/04/04 19:41:45
Ok, this is fairly what-the-christ.
So, I'm running around my mod opening doors with keys. Use a key, door opens - it just plays the door open sound. I'm thinking hang on...didn't it used to play a "use key" sound before when it opened?
I then check vanilla quake. But no, even playing vanilla quake - no mods - key doors are only making the door open sound when they open.
I look in the QC - vanilla QC - the door is *trying* to make a "use key" sound, but it's immediately getting overridden by the "door open" sound (same channel). Looks like that's how it behaves all the time. So, another id bug to fix then. But why do I have a very clear memory of hearing the use key sound in vanilla Quake??
And more creepily, why did Scampie hear it on some compiles of his map, but not on others??
What is this door devilry??
#741 posted by necros [174.113.119.133] on 2012/04/04 20:37:56
sock: and definitely use a new compiler.
you can use more c conventions like for loops and increment and decrement operations.
also removes arguably useless stuff like needing semicolons after function braces.
kinn: not sure, but maybe because czg's progs from his last map pack had a fixed variation where the key sound plays and the door opens after a short delay?
More Door Bollox
#742 posted by Kinn [86.176.167.133] on 2012/04/04 20:38:09
ok, so i've fixed the key use sound thing, and also for visual effect added a small delay between the key being used and the door actually opening. This introduced me to something that I never knew anything about before. Namely this:
in door_touch, if I replace the call to door_use(), with:
self.nextthink = time + 1;
self.think = door_use;
The door actually opens after about 10 seconds, not 1 second. (whu?)
if I write:
self.nextthink = self.ltime + 1;
self.think = door_use;
The door opens after 1 second, as expected.
do doors play by totally different rules when it comes to time and nextthink shizzle?
Hah
#743 posted by Kinn [86.176.167.133] on 2012/04/04 20:40:05
wrote that before seeing necros' post :}, didn't know czg did this but maybe that's where i'm remembering it from...
#744 posted by necros [174.113.119.133] on 2012/04/04 20:42:59
preach made a fantastic post about this explaining how nextthinks work on MOVETYPE_PUSH entities:
http://celephais.net/board/view_th...
Append /\
#745 posted by necros [174.113.119.133] on 2012/04/04 20:45:43
also of note, setting .velocity on a MOVETYPE_PUSH entity without setting a .nextthink will do nothing.
.nextthink must be set higher than .ltime in order for the engine to update position based on .velocity
Compilers
#746 posted by sock [24.82.147.76] on 2012/04/04 21:32:26
@rj, thanks, I am still learning where everything is hiding! :P
@necros, I got the qcc compiler from ID and it would not work under 64win so I got hold of FTEQCC instead. The original ID files produced a ton of warnings and crap messages. I don't suppose any of that has been cleaned up and a new qc pack of files is anywhere? Or should I not worry and just add my own stuff to the pile?
Are there any QC libraries? Luckily czg released his source so I can see how he did the trigger spawn routines. Is there other things that everyone uses?
Necros/preach
#747 posted by Kinn [86.176.167.133] on 2012/04/04 21:33:41
thanks, that was useful. Preach - never stop quakeing, you are an asset.
So, from what I gather, because time was not advancing from the door's point of view, when I set self.nextthink = time + 1; it still had a self.ltime of like 0.1 or something, so the "ten second" delay i mentioned earlier was basically due to however long the game had been running up until that point + 1 second. lol.
Funny that I'm still learning new things about quake, but then again I've never dicked around with push ents before, which is why i've never come across this :}
Sock It
#748 posted by Preach [77.98.165.95] on 2012/04/04 22:17:44
Sock: http://www.btinternet.com/~chapter... ought to be handy.
It's my cleaned out copy of the qc source, only for the purpose of defeating all those warnings. I might have made some other minor tweaks but I'm pretty sure it's faithful to the basic game. The only real compromise I added to it is a pointless, vestigial function at the bottom of DummyFunction which defeats the warnings about .wad and .light_lev not being used in the progs, even though you must define the fields.
Broken Link
#749 posted by sock [24.82.147.76] on 2012/04/04 23:12:09
@Preach, that is awesome what you have done, but I can't get the link to work?
Arrgh
#750 posted by Preach [77.98.165.95] on 2012/04/04 23:55:24
Forgot to test the link, turned out the ftp failed because the server dislikes filenames containing spaces. Renamed to
http://www.btinternet.com/~chapter...
Awesome
#751 posted by sock [24.82.147.76] on 2012/04/05 00:09:00
Thanks Preach that has saved me a ton of effort trying to work out what is my mistake and what is broken already.
Not Sure If This Has Been Covered Before...
#752 posted by necros [174.113.119.133] on 2012/04/06 05:08:59
if i have two entities, one with .nextthink = time + 0.01 and another with .nextthink = time + 0.02 will the order the thinks occur be based on nextthink or just the order the entities are stored in the list in the engine?
my gut tells me it's the latter, and that makes me sad. :(
#753 posted by necros [174.113.119.133] on 2012/04/06 05:10:48
didn't say, but it should be obvious, i'm assuming the next frame will be longer than 0.02 seconds after. say 0.1 seconds after to be clear.
time = 0
->a.nextthink = time + 0.01;
->b.nextthink = time + 0.02;
time = 0.1
a fires first, guaranteed? or it might be a or b based on it's position in the list.
i should think before i post. -_-
You Are Correct
#754 posted by Preach [77.98.165.95] on 2012/04/06 10:47:13
If the frame takes longer than 0.02 then it is strictly in the order that the entities appear in the entity list.
Interesting fact though, say that instead we start at time 17
a.nextthink = 17.01
b.nextthink = 17.02
Now at 10fps the next frame starts runs at server time 17.1. However, during the think function for "a" time will be set to 17.01, and during the think function for "b" time will be set to 17.02. This change literally applies only to the QC variable, not to anything else in the server.
Back to the original problem, a lot of the time with the think intervals that you have chosen players today will see a think before b because the cap on fps is 72, which is high enough for a frame between 0.01 and 0.02. Beware of the effect in the previous paragraph though, if you are setting the nextthink values from within individual think functions for "a" and "b" it may not be the case that
a) b.nextthink - a.nextthink = 0.01 (even accounting for floating point error!)
OR
b) that b.nextthink won't occur in the next frame!
That second one needs a bit more explanation and in particular would benefit from concrete figures. We will say that the server issues a frame every 0.014 seconds.
Frame i:
server time = 17
b.nextthink = 17.003
Frame ii:
server time = 17.014
b's think at 17.003 executes and contains the qc:
b.nextthink = time + 0.02;
Since the engine sets time = 17.003 during the think, we now have
b.nextthink = 17.023
Frame iii:
server time = 17.028
So b executes a think in consecutive frames!
If you really need execution in order you might have to concoct a "priority queue" style system, with a new fakenextthink field. That would mean a linked list of entities so that the following pattern holds:
self.fakenextthink <= self.nextent.fakenextthink
You insert into the queue by traversing the list until your fakenextthink is <= the next fakenextthink. Finally you'd need a function that runs from startframe and traverses the queue, running fakethink on all the entities which are due. Since the queue is in order of fakenextthink, they run in the desired order and as soon as you reach one which is not yet due to run, you know you can stop traversing.
Two tips: firstly don't forget to purge the entities from the queue before you fire their events! It's very likely that entities will want to reinsert themselves into the queue from their own fakethink calls, so you need to be aware of that. Secondly, I'd keep up the practice of setting time = fakethinktime for the duration of the fakethink call (then you MUST reset it to evaluate the next entity on the queue).
These two tips combine for an interesting effect: you can have multiple fakethinks occur in a single frame, since they get reinserted into the queue with a fakenextthink that might still be lower than the server time. I suppose to be sure of correctness we need to make sure the loop of execution is less traversing the linked list, so much as always looking at the head of the list and popping it if execution time has arrived.
I have another use for this priority queue in mind which is less technically involved and more generally applicable, so I might go away and write it up with some nice complete code over the weekend...until then!
#755 posted by necros [174.113.119.133] on 2012/04/06 18:30:14
thanks for confirming that for me. i was looking at the engine source, and that was what it looked like, but i'm not familiar with it enough to be sure.
i do remember the bit about time being set to nextthink time on think calls. i've used your 'servertime' workaround often because of it. :)
as for making a fakethink sorted list, i thought of it, but i only have one bit of code where it would be important, and it felt like it was better to just solve the problem by working around it rather than making the whole fakethink system.
thanks!
Sounds
#756 posted by Mike Woodham [86.174.73.229] on 2012/04/06 19:33:51
Is it possible for one entity to affect (stop) the sound of another entity e.g by forcing a null.wav to play on that other entity's channel?
I have just noticed on an old map of mine that when the player dies the music keeps playing, which seems wrong to me. I thought I might just be able to drop a 'sound' line into Player DeathSound to say kill the music, then carry on and play whichever death sound you want. But I do not know how to use the sound(self,...) parameter to point to a specific entity.
Alternatively, could I stop all sounds just before playing the Player DeathSound?
Mike:
#757 posted by metlslime [159.153.4.50] on 2012/04/06 19:48:19
instead of "self" in that sound call, use the name of another entity.
#758 posted by metlslime [159.153.4.50] on 2012/04/06 19:48:42
(i.e. the entity that you want to silence)
#759 posted by necros [174.113.119.133] on 2012/04/06 20:10:50
also of note: make sure you play that sound with 0 attenuation to make sure it is 'heard'.
if you are outside audible range, the engine won't even bother registering the sound and so won't interrupt a previous one playing on that channel.
Sounds, Part The Second
#760 posted by Mike Woodham [86.174.73.229] on 2012/04/06 21:13:10
metlslime: thanks, once I realised that I needed to add the entity name to ALL parameters, we wuz cookin' on gas.
necros: it's in the form of:
sound (self, CHAN_VOICE, self.noise9, self.volume, self.distance);
where 'noise9' happens to be the null.wav, and everything else is already set from within the original entity's entity definitions.
It works just fine now: music stops, player does his death sound and dies gracefully, monks start chanting a dirge - I keep allowing myself to be killed because I find it ever so slightly amusing :)
(One day, I'll get the hang of this .qc stuff)
#761 posted by necros [174.113.119.133] on 2012/04/07 05:07:34
concerning c++
i've been messing with that fake GI hack with MH/Aguirre's light and the main block right now is integer precision...
specifically, in order to get better results, i need to add more suns. the more suns i add, the brighter everything gets.
i'd need to use a light level of maybe 0.5 or less.
unfortunately, everything's in ints.
how hard would it be to convert everything to floats and would that markedly slow down light calculations?
is it even worth doing? :P
Heh...
#762 posted by necros [174.113.119.133] on 2012/04/07 05:08:46
so... i posted that and then i just thought of a better solution (albeit, one that's way more hacky).
i was thinking i could assign all sunlight a different light style and then give it a light style of z to n instead of m...
#763 posted by necros [174.113.119.133] on 2012/04/07 05:49:28
yeah, that didn't work that well. :P
Holy Lmaps Batman!
#764 posted by Kinn [86.128.113.123] on 2012/04/07 14:11:21
Out of interest - what are the performance implications of using styled lights on that sort of scale?
Basically None
#765 posted by Preach [86.136.150.139] on 2012/04/07 14:42:30
All lights in quake are styled, you can see the code which sets the styles in the worldspawn function with. Performance costs are only incurred when the style changes, either due to an animated style (multiple letters) or calls to lightstyle to alter the style.
Incidently there's a trick to making easy lightning effects relating to this. Lighting tools put all the sky lighting on style 0, but we can see that it's possible to alter the levels of style 0 in the same way as other styles. If you put all of your static, sourced lights on a different style, you can reserve style 0 exclusively for light from the sky. Then you can quickly switch the style to intensity "z" to create a flash of lightning while keeping indoor lighting constant.
Well Damn
#766 posted by Kinn [86.180.65.35] on 2012/04/07 14:57:37
I know what I'm gonna be doing now!
Cheers.
#767 posted by necros [174.113.119.133] on 2012/04/07 18:46:36
Incidently there's a trick to making easy lightning effects relating to this./q>
that works ok, but tends to look a little boring because the shadows don't change. it's also annoying because you have to remember to set all your normal lights to a style other than 0. although, i guess you could code up your qc to default to another style.
i'm currently trying to haxor in mutliple 'lightning' suns so i can get fake varying lightning strike positions. i'd leave the lightning suns 'off' and then flicker them on when needed. note that you could only have max 3 of these because 4 is the max number of styles allowed. :(
sadly, i know nothing about c++, my only experience being with java which does pointers automatically and seems, overall, a lot simpler, so progress is slow for now.
#768 posted by necros [174.113.119.133] on 2012/04/07 18:46:42
awwww
#769 posted by metlslime [67.188.81.46] on 2012/04/07 20:59:21
it's also annoying because you have to remember to set all your normal lights to a style other than 0. although, i guess you could code up your qc to default to another style.
I don't think qc will help you; styles have to be set up in the map file (either "style" or "targetname" on each light entity) so that light.exe knows about them.
#770 posted by necros [174.113.119.133] on 2012/04/07 21:14:32
oh that's right. ouch.
Control
#771 posted by Preach [86.136.150.139] on 2012/04/08 00:41:35
I'd edit your fgd/def file to make other lights default to a different style. I'll admit I've not tried it, so maybe the effect isn't all that dramatic. It sounds like it's one of those things where you could get a good effect in the bsp format, but that maybe the tools for it aren't quite there yet...
Touchy Triggers
#772 posted by necros [99.227.156.158] on 2012/04/20 22:42:52
is there some kind of bug where triggers touching each other can cause a touch function to run on the other trigger?
ex: trigger A and trigger B are touching.
player touched trigger A, trigger A and trigger B both run touch functions.
#773 posted by negke [31.18.185.232] on 2012/04/20 22:59:08
Sounds like you forgot to set the trigger_dont_link flag.
#774 posted by necros [99.227.156.158] on 2012/04/20 23:08:30
you're thinking about door_dont_link.
this happens with just normal triggers.
Joke; Didn't Work
#775 posted by negke [31.18.185.232] on 2012/04/20 23:14:12
Shape
#776 posted by Preach [77.98.165.95] on 2012/04/20 23:14:59
Any chance that it can be because the trigger brushes are oddly shaped, and the resulting bbox shaped triggers end up coinciding? Never heard of an effect like this before, might have to take a look at the map to figure it out.
Another tool that I've only recently begun to use is the pair of builtins traceon and traceoff. Calling the former means that all the QCasm instructions get printed to the console until you call the latter. It's about as close as you can get to a debugger for QC, and a lot of the time it's much quicker than adding a bunch of dprint statements to nail down a bug. Putting them round the touch functions might be helpful.
#777 posted by necros [99.227.156.158] on 2012/04/20 23:22:01
this actually happened in ne_ruins.
it's in that little corridor between the two maps, in the start map, there are a couple of triggers that get fired depending on flags (ie: just entered the map or returning).
except they were both firing until i spaced them 1 unit apart.
i'll have to try out those builtins...
Re Traceon/off
#778 posted by necros [99.227.156.158] on 2012/04/21 00:25:46
yikes... that's a LOT of output.
it'll take some time to get used to reading this.
i suspect it'll be best to turn it on and off sparingly.
Deluge
#779 posted by Preach [77.98.165.95] on 2012/04/21 00:30:29
Yeah, it's not something that you want on functions running many times a second,it might be necessary to turn it on right after the basic touch rejection code. The thing I found it best for was when I have a function with lots of if branches, and needed to work out which ones it was taking or rejecting.
Unsolicited Advice
#780 posted by Preach [77.98.165.95] on 2012/04/22 15:47:03
Suppose that you are writing a mod which other mappers will use, and you want mappers to be able to control a parameter. In this post I will use corpse-duration in a corpse removal system as an example. Traditionally if you wanted a global property then you would add a field to worldspawn to set it.
Apart from it being a bit of a waste to add a field to every entity which is only ever read from worldspawn, I think there is a better pattern, which is a bit more powerful. The key use-case is a property which a mapper might want to vary as well as set initially. Corpse lifetime arguably fits this criteria, for example: during a large horde combat you might want to remove corpses faster than the rest of the map.
My suggestion is to create a new entity function as follows:
float corpse_duration;
void() set_corpse_duration =
{
corpse_duration = self.count;
if(!self.targetname)
remove(self);
}
Then the way to set the corpse_duration initially is adding a point entity with classname set_corpse_duration and count equal to the desired duration.
Can any of you keen entity hackers see how we change it dynamically? Well, we set up an entity with the desired count, but with "classname" "info_notnull" and "use" "set_corpse_duration". If we set a targetname then every time we trigger this entity it sets the corpse duration. If you want to codify it as less of a hack you could invent a new classname for this kind of entity like set_dynamic_property, but it doesn't need any supporting code.
The remove(self) bit of code is a bit ugly, but it makes sure that initially setting the property doesn't cost you an entity, yet the info_notnulls can be used multiple times.
Mismatched Flags
#781 posted by sock [24.82.147.76] on 2012/04/23 01:59:24
I am trying to query a monster to see if the current enemy has an artifact enabled. I am using (self.enemy) and I checked to see if it a player. I query the flags (self.enemy.flags) and it does not show the player flags properly. I tested this by using an artifact routine (void() powerup_touch) to print to the console what it was doing and it produced a different flags value.
I double checked the classname field and other player variables and it is certainly looking at the same entity, except the monster query will not show the content of the flags correctly.
Is there something strange about queries with the player flags?
#782 posted by necros [99.227.156.158] on 2012/04/23 03:02:29
what value are you seeing? how are you querying for flags? flags must be checked via bitwise operation (self.enemy.flags & FL_CLIENT) will return 1 (true) for the player.
are you saying you do the above operation and it returns false when the monster is angry at the player?
also note that players gain and loose flags a lot during play, godmode, notarget, onground, partialground...
Fixed
#783 posted by sock [24.82.147.76] on 2012/04/23 05:35:19
I was mixing up .item and .flag queries. I was too tired to see the difference and could not understand why. doh!
Ideas/tutorials For Useful Entities...
#784 posted by wakey [178.191.195.208] on 2012/04/25 00:26:40
to add to progs.dat.
I'm quite the noob when it comes to coding.
Included misc_model entity from a old tutorial, but that was just copy/paste and compile.
For example i would love to have such swinging door's/ pushable func_rotate things like in ne_ruins.
Googled, found no tutorial for that yet, and dont know what other stuff could come handy.
Suggestions?
Inside3D Is The Place For Everything QC
#785 posted by negke [31.18.185.232] on 2012/04/25 10:48:57
They have a forum and a set of tutorials on the site.
necros code is a different matter. I think it's part QC and part evil arcane magic, or something.
QC Beginnings
#786 posted by Kinn [86.164.173.251] on 2012/04/25 12:39:37
For example i would love to have such swinging door's/ pushable func_rotate things like in ne_ruins.
If this is your first foray into QC I would forget all that stuff and just start small. Most of the QC tutorials you can find online are the "make a grunt shoot rockets"-type stuff but they will help you learn the basics.
The sort of stuff necros does (pseudo-physics movers and whatnot) is not going to be achieved with copy and paste.
#787 posted by necros [99.227.156.158] on 2012/04/25 19:01:18
this is from last year, but i haven't really done much to the pushable rotaters since then anyway.
http://necros.slipgateconstruct.co...
i doubt it will work by plugging in, and it should be obvious it requires the hipnotic rotation code, but you can at last see what's going on. it also needs a lot of work still-- there is no implementation for other axis of rotation because those must factor in gravity.
overall, the concept is pretty simple: the movewalls have a touch function now that takes the speed that the player touches them with and dumps it into the rotators which then decays that speed over time. there's more to it of course, like translating velocity into angular momentum and such, but i'm not going to explain the whole thing. i think i've posted about it before, but i forget where.
for coding qc:
knowing about unit vectors, dot product, normalizing vectors, reflection, etc... (but you can always google this stuff up and learn it on the spot if you are not averse to that)
if you know some physics, that can help too, but you don't need to be a rocket scientist. (wow, when will that saying get old? maybe it already has...)
Thx Necros & Negke
#788 posted by wakey [80.121.104.224] on 2012/04/25 20:36:21
for the code & link.
Looks very complicated for me, but the concept sounds logical, hopefuly i will learn enough about qc to understand the code eventualy.
Let's see what i will find on inside3d :D
#789 posted by necros [99.227.156.158] on 2012/05/04 01:56:14
um.. what is trace_plane_dist?
the name implies it's the distance to the plane it hit, but after testing, i was seeing very strange values being returned, some in the thousands from a short 80 unit trace.
#790 posted by necros [99.227.156.158] on 2012/05/04 05:48:51
a bit more testing... seems to be the distance from the origin based on the axis of the plane, so a trace on a face on the y/z plane returns distance from origin along the x axis.
non-axial planes returns strange values, some kind of combination of the different axis based on the slope of the plane?
It Is Probably The Plane Distance Using The Plane Normal
#791 posted by czg [212.16.188.76] on 2012/05/04 08:36:56
http://mathworld.wolfram.com/Hessi...
You can define a plane in 3D space using its normal vector and distance from origin (in the direction of the normal)
#792 posted by necros [99.227.156.158] on 2012/05/04 18:59:43
thanks for clearing that up. :)
#793 posted by necros [99.227.156.158] on 2012/05/11 20:01:03
is there any way to 'reset' frame aliases back to 0?
or is making a new qc file the only way?
Compiler Extensions
#794 posted by Preach [77.98.165.95] on 2012/05/11 21:04:56
QTEQCC supports resetting the frame aliases using
$framevalue 0
(literally, the keyword is framevalue)
The need to make an extension suggests it can't be done normally.
#795 posted by necros [99.227.156.158] on 2012/05/11 21:27:57
you mean fteqcc?
anyway, thanks. :)
Centerprints
#796 posted by necros [99.227.156.158] on 2012/07/16 19:49:42
What is the maximum centerprint length?
Does it matter if you use the extra centerprint that allows multiple arguments?
Fitzquake has a bug that allows longer centerprint strings, but Quakespasm 'fixed' this, so that strings that display fine in Fitzquake spam the console with errors and truncate the string in Quakespasm.
:(
Centerprint Article
#797 posted by sock [24.84.102.77] on 2012/07/16 20:15:39
http://www.inside3d.com/qip/q1/qce...
From the page :
* The maximum number of characters in one string or multiple strings printed at once is around 2000.
* The maximum number of characters per screen line is 40, independent from the current resolution.
* The maximum number of lines per screen is 13, as more will cause weird problems in 320x200.
The links says max 2000 chars but 13 lines of 40 characters is 520 total. I assume Quakespasm is not liking those above limits?
#798 posted by necros [99.227.156.158] on 2012/07/16 20:22:00
I brought this up before, and iirc, the 'limit' was actually higher than it should be. Apparently, the centerprint had some kind of unchecked thing which allowed strings that were too long to go through and it was only through luck that data wasn't being corrupted somehow. (and probably why it was never noticed?).
Bizarre Problem...
#799 posted by necros [99.227.156.158] on 2012/07/25 01:05:08
I've got a bmodel with MOVETYPE_PUSH and SOLID_TRIGGER that moves via velocity.
Normally, I can be inside this entity when it moves.
For some reason, if I touch a rotate_object (which has no touch function, is MOVETYPE_NONE and is SOLID_NOT), the moving trigger bmodel becomes blocked! (it runs it's .blocked function too).
It's the weirdest thing ever...
Reproduction
#800 posted by Preach [77.98.165.95] on 2012/07/25 01:39:30
This sounds like it might be quite a specific problem and so hard to reproduce, can you e-mail me a map/progs that displays the behaviour?
Solution
#801 posted by Preach [77.98.165.95] on 2012/07/26 02:04:42
This might be a little opaque for anyone outside of me and necros.
The engine code underlying this problem is quake's blocking detection system. It's crude to the point of being technically incorrect, but infrequently enough that nobody has cared - before now. When a door moves, each entity is tested against the door:
Is the entity within the bbox for the door?(i.e between its mins and maxs, not exact intersection) If not move on.
Is the entity currently stuck? If not move on.
At this point the code attempts to move the entity the same distance as the door moved, in the hope this pushes it clear.
To see if it worked, we test again if the entity is stuck.
If it is stuck, we declare the door blocked and revert all the movement for this frame.
The flaw in this system is that we only test if the entity is stuck, not if it is stuck in the door. You can test this with a map with a func door which encloses the player without touching them in any way, combined with a solid brush which impales the player inside. The door will crush you without ever making contact.
Here endeth the general lesson. Specifically for necros: Your post missed something important out, the func_pressureBolt is MOVETYPE_FLY, which apparently causes the player to count as stuck and hence block the trigger. You set MOVETYPE_NOCLIP at line 73, but change to MOVETYPE_FLY at line 95. If you delete line 95 your trigger will work again.
If you are worried this class of bug might recur in other circumstances, I can outline an alternate design which uses a static rather than moving trigger, just ask!
#802 posted by necros [99.227.156.158] on 2012/07/26 03:02:23
If you hadn't found the solution, I would have done the trigger movement via setorigin (since there's no collisions to check (beyond .touch of course).
You set MOVETYPE_NOCLIP at line 73, but change to MOVETYPE_FLY at line 95
*sigh* man, one day I need to rewrite my code... that's just sad. :P
But yeah, you are a genius man. I never would have imagined the rotation controller entity was behind the problem. Both it and the accompanying rotate_objects are SOLID_NOT. Why would the engine even check collision if it's not solid? Maybe because movetype_fly was intended for use with projectiles?
Very strange!
Thanks for yet another awesome bit of quake wisdom. :)
Motionless
#803 posted by Preach [77.98.165.95] on 2012/07/26 21:43:20
My suggestion was going to be have a trigger which starts off at full height. Then just add
if(other.mins_z < self.owner.maxs_z)
return;
to the touch function for that trigger. Here self.owner is the visible moving entity which marks the upper end of the trigger. If there was no visible part you could calculate the height mathematically based on the time since triggering.
I'm still not sure why MOVETYPE_FLY matters, it might also be tied up in using a bsp model for the func_pressureBolt...the physics code is pretty dense and circuitous.
#804 posted by necros [99.227.156.158] on 2012/07/26 21:51:51
Is this also related to that bug where a lift moving up/down will get the player 'stuck'? I had it happen more on some lifts than others, for example, that big lift in ne_ruins with the rotaters did it so much, there is actually code that sets the blocking entity's origin 1 unit up to stop the player being crushed.
Necros
#805 posted by Baker [69.47.162.203] on 2012/07/27 08:40:09
Can that physics issue happen in DirectQ or the RMQ Engine? Both those engines have very intricate physics fixes that essentially make sure that the physics always run at Quake normal speed.
I ask because it is an engine physics issue, that isn't something you should worry about too much because MH has the cure that needs to get into other engines.
Sheesh Missing Word ...
#806 posted by Baker [69.47.162.203] on 2012/07/27 08:42:34
"I ask because IF it is an engine physics issue" ... [basically can you reproduce the problem in the RMQ engine or DirectQ with that lift? If not, maybe mentally mark that problem off the proverbial list ...]
#807 posted by necros [99.227.156.158] on 2012/07/27 08:44:13
you can test to see if it's happening by going loading ne_ruins, turning on dev mode to 1 and riding the lift up.
It's very random and may or may not happen, but you will see "corrected bmodel collision bug." if the qc detects a block that shouldn't happen.
it doesn't happen in DP, so if the collision fixes are as extensive as it is in DP, then it probably doesn't. The collision in DP is really sweet.
#808 posted by Baker [69.47.162.203] on 2012/07/27 11:03:14
That's enough info then. Yeah, DarkPlaces is fixed up in that way as well (and has been for ages and ages).
I wouldn't spend any time worrying about this one.
I intend to extract the fixes from RMQ/DirectQ and more or less document how to implement them.
[I just haven't done it yet because of the things on my list, well, it's kinda boring and tedious ... ]
Bsp Format Question
#809 posted by Preach [77.98.165.95] on 2012/07/29 12:49:32
I'd like to check something with people who are familiar with the BSP file format. Suppose we have a very simple map with one room containing a func_wall. The room has 20 faces and the func_wall has 6. Is it correct that there is just one list of 26 faces in the bsp:
0
...
19
20
...
25
then the model headers go
"world: I own 20 faces starting at face 0"
"wall: I own 6 faces starting at face 20"
The same then goes for vertices, nodes etc, that the bsp file collects all alike data types into a single list and each model in the file operates on a subset of that same list? (with exceptions like textures which are instead shared).
Why? I was thinking about the external bsp models trick - initially I was just trying to write a tool to pack them back into a single bsp for release. However, if the above is correct, it might also mean that the external file gets a new lease of life - as a limited way to help maps which exceed bsp limits
#810 posted by necros [99.227.156.158] on 2012/08/03 02:42:52
interesting...
rezTarget.think =
rezTarget.nextthink = time + 0.1;
compiles fine with fteqcc, but flat out crashes quake. :P
Is It Correct That There Is Just One List Of 26 Faces
#811 posted by mh [78.152.240.205] on 2012/08/04 17:59:59
Yes, that's the way everything is stored, but there are subtleties.
"Faces" as you understand them in a map have little relationship to surfaces in the generated BSP file. QBSP will remove faces (facing out, never visible, overlapped, etc) and generate more from splits, so you can't really use the map file as any kind of guideline to what the BSP will contain.
Regarding use of external BSP models, you can use them to go through BSP limits, but lighting will be wrong for them. Lighting for an external BSP model doesn't take account of any geometry in the world that might occlude those lights, and they fail to get dynamic lights. That might be OK with you, but it might not be OK with someone playing your map.
It's My Lighting Aand I'll Cry If I Want
#812 posted by Preach [77.98.165.95] on 2012/08/04 18:28:33
That's good to know.
On lighting: Although losing dynamic lights is a shame, I don't think that the static lighting is much of a obstacle.
Although I've been talking external bsps in general, the best use-case for external models is for objects that rotate or move. Static lighting of course can't get these right, so there's less worry they might be lit "wrong". Along the axis of rotation it makes more sense for the lighting to be uniform, instead of baking in a shadow which will be wrong as soon as the object moves.
For statics moved outside the BSP for limit reasons only, I suppose it's not impossible to write a version of light that applies occlusion from a base map onto an external bsp (using the code-path from the light executable which occludes lights on models 1-n based on the geometry from model 0, except loading model 0 from a different file to model 1). Think I'll stick to the bsp packer for now though...
#813 posted by mh [78.152.240.205] on 2012/08/04 19:27:50
Dynamic lights can be fixed in-engine; there's a (slight) performance cost but the visual consistency is more than worth it. I have it in my current (unreleased) codebase, but I don't believe any other engine has implemented it yet (aside from those with fully real-time solutions).
Along the axis of rotation it makes more sense for the lighting to be uniform, instead of baking in a shadow which will be wrong as soon as the object moves
Definitely agree with that.
I suppose it's not impossible to write a version of light that applies occlusion from a base map onto an external bsp
It's an interesting idea. It would break if more than one entity used the same BSP model (an example would be a generic door modelled as a BSP) but for limited special cases it should work.
#814 posted by necros [99.227.156.158] on 2012/08/04 20:15:52
would it be possible to make an 'fake ambient occlusion' mode in light.exe such that, when enabling it, it creates an array of suns like it does with sunlight2, except that these suns do not require sky textured brushes?
that way we could make some external brushwork and get some nice shadows that look good from all rotation angles.
heh, maybe I should do that... I'll see if I can puzzle it out.
#815 posted by necros [99.227.156.158] on 2012/08/04 21:58:09
http://necros.slipgateconstruct.co...
hmm, seems like it was easier than I thought it would be. o.0
If Anyone's Interested:
#816 posted by necros [99.227.156.158] on 2012/08/04 22:36:20
http://necros.slipgateconstruct.co...
This is based off of mh's coloured light/multicore tweak of aguirre's light utility, so it has all the good stuff.
Two new command switches are added:
-fakeGISun2
Makes sunlight2 cast additive light in a full sphere (as opposed to a dome).
-fakeGIMode
Makes sunlight2 not require skybrushes (ie: the light comes from the void)
NOTE: REQUIRES -fakeGISun2
Also note you need to set the worldspawn keys 'sunlight' to '1' otherwise it's not activated at all and the 'sunlight2' settings should be low, 2 or 3. 4 is pretty bright.
I basically just turn off the check that requires sunlight to pass through a skybrush first, so with -fakeGIMode, the sunlight comes from the void.
Source included. Some small changes to LoadEntities() and TestSky() and maybe one or two other tiny things I've forgotten.
Good Stuff
#817 posted by Preach [77.98.165.95] on 2012/08/04 22:50:09
The spherical illumination might also be handy for coagula style maps.
#818 posted by necros [99.227.156.158] on 2012/08/05 03:11:40
not bad results on space maps with only the fake gi light enabled: http://necros.slipgateconstruct.co...
Might be cool to do another space map one day with this lighting. The only thing is that exposed faces tend to look flat. The above screenshot is only interesting because of those high walls.
#819 posted by mh [109.79.236.164] on 2012/08/05 04:13:36
That actually looks pretty cool; kinda radiosity-like.
#820 posted by necros [99.227.156.158] on 2012/08/05 20:43:04
Dunno if anyone has mentioned this before (Preach maybe?) but I've been using a lot of multi-entity setups these days and cleaning up after them has become rather annoying so I used this trick to simplify things:
void(entity e) remove_builtin = #15;
.void() removeFunction;
void(entity e) remove =
{
local void() _removeFunction;
entity tempSelf;
if (e.removeFunction)
{
_removeFunction = e.removeFunction;
e.removeFunction = SUB_Null;
tempSelf = self;
self = e;
_removeFunction();
self = tempSelf;
}
remove_builtin(e);
};
Create a wrapper for the remove builtin that first checks if a .removeFunction is set, and if so, run it.
Then, I just create a unique function for each multi-entity that takes care of any child entities.
Note how we first save a reference to the .removeFunction() before clearing it. This is to prevent some recursive removeFunction running where (if you are not careful) you remove a child entity that then removes the parent, that then removes the child... etc...
This makes sure it only runs once.
ps, sorry about formatting.
Destructive
#821 posted by Preach [77.98.165.95] on 2012/08/05 23:18:25
Works like a destructor in C++ (*), I like the recursion guard you put in there, I don't remember seeing that before.
Another place where this can be helpful is if you have some kind of linked-list structure, where removing an entity would break the chain. You can set a removeFunction which repairs the list before and after the entity which is getting removed.
* How long can it be before we get a QC++ compiler bringing the full power of object orientation and inheritance to our favourite game?
Loading MDL Files
#822 posted by necros [99.227.156.158] on 2012/08/07 06:28:21
Trying to load .mdl files with java following this: http://tfc.duke.free.fr/coding/mdl...
For the most part, I got the data in fine, but for some reason, there seems to be a few extra bytes AFTER the frames that I don't know what to do with.
The number of bytes isn't consistent either, sometimes it's 10 bytes, other times it's 9 or 8.
Obviously, this screws up the next frame from loading properly because all the bytes are offset by however many were left over.
I tried just doing the dumb thing and skipping over these extra bytes after loading a frame, but the number of extra bytes can change within the same model.
Is this a real thing (extra bytes between frames) or am I just messing something up?
#823 posted by mh [78.152.239.241] on 2012/08/07 07:44:36
Extra bytes between frames isn't normal. Possible that your struct sizes are being padded out to some alignment? I don't know Java so I'm not certain if it even does that, but it's the only off-the-top-o-me-head explanation I can think of.
#824 posted by necros [99.227.156.158] on 2012/08/07 07:59:19
Thanks, I was just looking for confirmation that there are no extra bytes. :)
Java doesn't have structs or any of that, so I'm just reading everything in byte and byte and converting things to big endian ints and such.
I'm sure I'm just reading something in wrong or I missed something.
Gotchas
#825 posted by Preach [77.98.165.95] on 2012/08/07 12:19:36
One of the things to watch out for is where there are mdl_vertex_t being loaded to define the bounding boxes of the frames and the like. Make sure you're reading the vertex normal in all of those places, even though it's not used.
#826 posted by necros [99.227.156.158] on 2012/08/07 20:42:16
For the first frame, the data seems to be fine up to (at least that I can easily check) the frame name.
I parse each byte as a char until I get to the first byte that is 0, then I start loading vertex objects.
The problem seems to be somewhere during the vertex loading.
I'm using a 5 frame model with 5 verts.
Looking at the file itself, I see 48 bytes between one frame name and the next, so it looks like each frame is 48 bytes long. (the frame data actually starts 12 bytes before the name, there's one int (4 bytes) and 2 mdl_vertex_t (2 * 4bytes))
The mdl_vertex_t struct has an array of 3 unsigned chars and then a single uchar, so that means each vertex should be 4 bytes long.
So with 5 verts, that should mean that all the vertex info should be 5 * 4 = 20 bytes.
Take those 12 bytes mentioned above and we have 32 bytes. The name is "POLY1" so that should be 6 bytes for the name (including \0).
So in total that's 38 bytes and then 10 bytes left over.
#827 posted by necros [99.227.156.158] on 2012/08/07 20:47:00
hm.. ok, seems like typing it out helped me see the problem...
Looks like the String for the frame name is ALWAYS 16 bytes, not until the next 0 value. It loads in some junk characters but that explains the 10 byte offset (16 - 6).
That's really bizarre.
#828 posted by necros [99.227.156.158] on 2012/08/07 20:58:41
actually, I guess it sort of makes sense.
c just stops reading strings when it reaches the first \0, but I guess they didn't want to bother with a dynamically allocated char array so those junk bytes are actually whatever was on the guy's memory when he saved the file...
Yup
#829 posted by Preach [77.98.165.95] on 2012/08/07 21:12:49
You have permission to zero all of that out nicely when you export though! Also, don't forget to test your code with grouped frames and grouped skins before you're through - it always seems to be going so well until that point...
#830 posted by necros [99.227.156.158] on 2012/08/07 22:19:38
I wasn't really going to bother with that for now.
Mostly, I just wanted to redo that "qcStarter" program I made so you could load a model and generate some basic monster code complete with a spawn function.
But now I'm thinking it'd be pretty cool to make a modern mdl editing program.
#831 posted by mh [89.19.71.35] on 2012/08/08 01:00:04
c just stops reading strings when it reaches the first \0, but I guess they didn't want to bother with a dynamically allocated char array so those junk bytes are actually whatever was on the guy's memory when he saved the file...
Yup, that's it. The struct in the C code is defined as follows:
typedef struct {
trivertx_t bboxmin; // lightnormal isn't used
trivertx_t bboxmax; // lightnormal isn't used
char name[16]; // frame name from grabbing
} daliasframe_t;
Reason why is when loading an MDL you can just load the entire file into a single buffer, then walk through the data setting and incrementing pointers as needed. If every daliasframe_t is the same size the job becomes much easier.
Same basic principle applies to other model data that contains a string name; e.g. textures have a fixed 16 char array for their names.
#832 posted by necros [99.227.156.158] on 2012/08/08 01:24:32
Makes sense, they would design the format to make it easy on themselves, not some guy digging into it 15 years later. :P
Now to see if I can draw it to screen!
I think understanding exactly what the heck is going on with the so called 'compressed coordinates' is going to be the really hard part. o.0
Hahaha
#833 posted by necros [99.227.156.158] on 2012/08/08 01:37:30
http://necros.slipgateconstruct.co...
well, it was supposed to be a fiend...
This Is Interesting
#834 posted by RickyT23 [94.13.59.186] on 2012/08/08 01:40:40
I always wondered what the algorithm was for drawing point to point in 3D.
Also I'm stunned that I am following this. It actually makes sense to me(!) now that I can program a little.
#835 posted by mh [89.19.71.35] on 2012/08/08 01:58:44
they would design the format to make it easy on themselves
...and they even failed at that!!! http://www.team5150.com/~andrew/ca... (section beginning "anatomy of a mis-feature").
well, it was supposed to be a fiend...
It's real close though, and a whole ton better than my first attempt at doing this with C# 8 or so years back.
#836 posted by necros [99.227.156.158] on 2012/08/08 02:19:08
http://necros.slipgateconstruct.co...
Silly mistake... tried reading in verts on the frames directly. :P
Oh Thanks For That Link
#837 posted by necros [99.227.156.158] on 2012/08/08 02:20:12
It's nice that that .plan stuff is archived. I never had internet back then (plus I was too young) but that seems very interesting now. :)
#838 posted by mh [89.19.71.35] on 2012/08/08 02:36:41
Good stuff with the fiend. You gonna take this all the way and do texturing too?
The .plan archive is essential reading, it's a great window into the thinking behind why much of Quake is the way it is, and can be very informative for decisions of the "should I add/remove/change this feature?" kind.
Skinzz
#839 posted by necros [99.227.156.158] on 2012/08/08 08:03:07
http://necros.slipgateconstruct.co...
Good Job, Necros...
#840 posted by generic [67.235.210.16] on 2012/08/08 13:24:43
I can see its eye in that shot :)
Heresy!
#841 posted by jt_ [174.252.254.224] on 2012/08/08 14:13:18
Fiends don't have eyes.
Yes They Do!
#842 posted by negke [31.18.178.240] on 2012/08/08 18:29:46
You guys don't do a whole lot of looking at things, do you?
http://www.lunaran.com/pics/fiends...
And now, shambler will argue that those aren't actually eyes:
Herp Derp
#843 posted by Shambler [31.18.178.240] on 2012/08/08 18:32:40
http://www.lunaran.com/pics/fiends...
ZOMG! I've been wrong all this time!!!
#844 posted by Spirit [80.171.143.135] on 2012/08/08 18:41:26
I thought those were nipples?
Coding-related Questions
#845 posted by Tronyn [24.79.202.92] on 2012/08/09 00:15:04
Now that the RMQ project has split up, what's happening with the BSP2 format? Will it still be updated? Do any engines other than the RMQ Engine support it yet?
Also, is anyone willing to do some coding for new boss monsters? PM is currently cleaning up and debugging Drake, but doesn't want to spend time adding entirely new monsters/features/etc. I totally understand this POV but it'd be nice if Drake had a unique final boss of some sort.
Looks like eyes to me. Now the Shambler, on the other hand...
Can Someone Make Me A Demon Skin
#846 posted by Drew [199.180.99.21] on 2012/08/09 03:16:37
With no eyes? This is horrible.
Hm
#847 posted by MadFox [94.215.208.34] on 2012/08/09 08:54:41
Glases are fine, horns another question.
http://members.home.nl/gimli/gdemo...
#848 posted by necros [99.227.156.158] on 2012/08/09 09:57:54
maybe I'm just tired, but that was hilarious. :D
@Tronyn
#849 posted by gb [46.142.19.93] on 2012/08/09 12:35:33
Since I was linked here from IRC, I might as well answer to Tronyn:
FTE and Darkplaces (and Hmap2) support BSP2, but DP's version is slightly different.
I would guess that DirectQ also supports it, and Baker and Rook's engines might as well.
I have switched to Warsow's FBSP for my project, so BSP2 will get no updates from me. The Schism team would probably be the ones to maintain it now. Perhaps ask at their forum.
You should talk to MH, Spike and Lordhavoc to get the BSP2 support in those engines harmonised.
Quakespasm did not want to support BSP2 when I asked.
I don't normally read this forum anymore, but I'm willing to help where I can. Poke me per mail or IRC if someone wants to talk to me.
OBJs + PCX!
#850 posted by necros [99.227.156.158] on 2012/08/10 01:11:24
http://necros.slipgateconstruct.co...
Thanks to preach for his pcx loading code in the md3tomdl converter. The runlength encoding stuff was confusing the crap out of me.
Also, I had to flip the pcx data on the x axis AND reverse the pixel array order after loading it... is that normal? If it's not then I must have messed up the UVs on the OBJ...
Heh
#851 posted by necros [99.227.156.158] on 2012/08/10 01:18:40
actually, flipping x axis and reversing the order is basically just flipping y axis right? :P
Triple Post
#852 posted by necros [99.227.156.158] on 2012/08/10 01:38:41
looks like it's a thing with obj... just flipped v component when loading the obj instead of goofing with the pcx.
Y Axis
#853 posted by Preach [77.98.165.95] on 2012/08/10 10:15:43
There is an issue to be aware of with "intuitive" coordinates and storage of data. The natural order of storing skin/image data (at least the one taken in the mdl format) comes about I think from their usual representation as a string (in the purest array of numbers sense). Since we are used to text flowing from left-to-right then step down a line and repeat (for english-speakers) we extend that idea to the order pixels should be stored in the skin.
The place where this begins to cause a conflict is when we start thinking about cartesian co-ordinates on the skin. The standard mathematical axes on a graph has the y value increase as you move upwards. So if you have something that wants to put the origin of the coordinates in the bottom-left of the skin, you need to reflect the skin in y to compensate.
Having said that, if you're actually going to render the 2d skin somewhere, you really ought to apply the reflection to the skin vertices rather than the image file itself. Otherwise you will cause a lot of confusion for the user who has an expectation on what their skin looked like.
#854 posted by necros [99.227.156.158] on 2012/08/10 19:40:18
Having said that, if you're actually going to render the 2d skin somewhere, you really ought to apply the reflection to the skin vertices rather than the image file itself. Otherwise you will cause a lot of confusion for the user who has an expectation on what their skin looked like.
Yes, this is what I ended up doing. I want to be able to display the skin either for drawing on (probably not) or changing UV coordinates, so having the thing upside down is not an option.
Turns out that this is a common thing with the OBJ format as many of the loading examples I read had a bit of code involving flipping the v component of the texture coordinates.
Woohoo
#855 posted by necros [99.227.156.158] on 2012/08/11 06:27:00
OBJ -> MDL perfectly functional! FUCK YEAH.
Still can't import more than 1 frame, but I don't think that's going to be a big deal.
Coding Challenge
#856 posted by Preach [77.98.165.95] on 2012/08/11 12:39:05
Suppose that we have the following functions:
is_first_player: returns true if self is the first player in the server
is_last_player: returns true if self is the last player in the server
toggle_func_playerclips: finds all func_playerclip entities in the map and toggles them between SOLID_BSP and SOLID_NOT.
Your task: comment and critique the following scheme for implementing func_playerclip entities:
1) add
if(is_first_player())
&nbsp;&nbsp;&nbsp;&...
to PlayerPreThink.
2) add
if(is_last_player())
&nbsp;&nbsp;&nbsp;&...
to PlayerPostThink.
The main question for consideration: is there any way for a player to become 'stuck'? Extra credit for considering the implications if is_first_player and is_last_player are not available.
Coding Challenge Fixed
#857 posted by Preach [77.98.165.95] on 2012/08/11 12:40:22
Forgot to preview so the entities weren't encoded...
Your task: comment and critique the following scheme for implementing func_playerclip entities:
1) add
if(is_first_player())
toggle_func_playerclips();
to PlayerPreThink.
2) add
if(is_last_player())
toggle_func_playerclips();
to PlayerPostThink.
#858 posted by necros [99.227.156.158] on 2012/08/11 21:35:38
You might not be able to change .solid like that without setting model.
Might be safer to do the func_togglewall trick where you translate the clipmodel far away when 'off' and put it back into place when 'on'.
Beyond that, at first glance, seems like it would work?
Re: Gb
#859 posted by Tronyn [24.79.202.92] on 2012/08/11 23:51:12
Hey, thanks for the response - helpful stuff. What's your email / how can I contact you? PS all the stuff on your blog looks amazing. Cheers.
Clipped Out
#860 posted by Preach [77.98.165.95] on 2012/08/12 04:01:49
You might not be able to change .solid like that without setting model.
Might be safer to do the func_togglewall trick where you translate the clipmodel far away when 'off' and put it back into place when 'on'.
Yeah, I explained myself badly. The plan was to only describe abstract functions, so you could assume they were correct without getting bogged down. I leaked too many implementation details...
There's still one case I can think of where it breaks down though - the trick is to work out when the player moves other than within the player physics code. There's also a more philosophical quandary with pointcontents I meant to bring up in the first post.
I'm glad you mentioned togglewalls, because although they seem quite hackish with the far-away trick, it has some hidden advantages. The prime one is avoiding ever toggling solid status within a touch function - which can be a fatal error. Building a monsterclip with the same broad outline would now become feasible.
You do have to bind the toggling to calls to walkmove and movetogoal, because monsters don't have the simple physics-wrapping functions that players do. This means that the monsterclip is imperfect in a way that the playerclip is not: falling or jumping monsters will pass through monsterclip. Still, better than a kick in the teeth.
#861 posted by necros [99.227.156.158] on 2012/08/12 04:09:27
You do have to bind the toggling to calls to walkmove and movetogoal, because monsters don't have the simple physics-wrapping functions that players do.
This is what I do right now. To avoid toggling every monster clip, I bind specific clips to specific monsters via a target->targetname style link, that way if a monster has no monsterclips associated with it, it doesn't toggle anything otherwise, it's only toggling a single monsterclip.
I suppose you could just make one giant monster clip entity for the entire map, but I'm not sure if that's bad having an entity with collision cover such a huge area so I've left it as localized clips.
#862 posted by necros [99.227.156.158] on 2012/08/12 04:50:08
I think I got a little side-tracked...
There's still one case I can think of where it breaks down though - the trick is to work out when the player moves other than within the player physics code. There's also a more philosophical quandary with pointcontents I meant to bring up in the first post.
Why would it move outside of engine physics? Feeding .velocity in still has to wait for the engine to process it.
Unless you mean setorigin()? But I think it's safe to assume using setorigin is always going to break collision.
Hm.. well, unless you are doing some funny trickery where you are using a proxy to move the player...
#863 posted by Preach [77.98.165.95] on 2012/08/12 11:51:22
But I think it's safe to assume using setorigin is always going to break collision.
Yeah, agreed.
Why would it move outside of engine physics?
Still inside engine physics, but outside of the player's physics pass.
#864 posted by necros [99.227.156.158] on 2012/08/12 19:02:36
hm... Maybe when you're standing on a bmodel that's moving? ie: train or lift
#865 posted by Preach [77.98.165.95] on 2012/08/12 19:23:06
Maybe when you're standing on a bmodel that's moving? ie: train or lift
Bingo. Probably not a big deal once you know about it, because usually those things are put in for the benefit of the player and you're unlikely to intentionally clip off their route, just something that users of the entity would have to be aware of.
#866 posted by necros [99.227.156.158] on 2012/08/12 19:27:05
The whole player movement on other bmodels thing is kind of fuzzy for me. It's not true movement because the minute you jump, you loose the bmodel's velocity and just get your own personal velocity back.
It's too bad because it stops things like throwing the player off a fast moving object or launching them through the air from a catapult.
Jumping Monsters
#867 posted by sock [24.84.102.77] on 2012/08/19 23:01:04
I want to calculate the height and speed to make a monster jump a certain distance. I know the origin of both the start and finish points but I don't know how to calculate the speed and velocity in QC.
I know there is 'trigger_monsterjump' but you have to specify or just guess the right amounts until it is right. is there a way I can do a quick QC formula to get rid of the guess work?
Pretty Easy
#868 posted by Preach [77.98.165.95] on 2012/08/19 23:45:09
Start by picking a velocity upwards, call that v_z. Let d_z be the height of the end point minus the height of the start point.
Once your monster launches, the only thing affecting it is gravity, which accelerates at 800 units per second per second(*). We will appeal to one of the standard equations of motion (with constant acceleration)
s = ut + ½at²
Where s is the distance travelled, u is the initial velocity, t is time and a is the acceleration. Substituting the values from above and rearranging:
400 t² - v_z * t + d_z = 0
with solution t = (v_z + sqrt(v_z² - 1600 d_z) )/800
On level ground this simplifies to:
t = v_z / 400
Now that we have the time t it takes to fall to the correct height, we need only divide the horizontal distance between the start and end points by t to get the required horizontal velocity. Sum the horizontal and vertical velocity vectors and you are done.
We seem to have complete freedom on how to choose v_z, but there are two things to think about. Firstly flight time is roughly proportional to v_z, so vary it according to how long you'd like the jump to last. Secondly in the case that d_z is positive (the end point is above the start point), there is a minimum requirement to v_z. The value inside the square root must be positive for the equation to work, so take care.
Please also remember that although we have solved the equations exactly, the simulation of the motion in the engine will not be exact, and in any case rounding errors will occur. Do not rely on any method to give you 100% accuracy - the landing point may even vary a small amount depending on framerate.
The really interesting case is the one where you have a fixed total velocity and still want to try and land on a particular point, but no space for that right now.
(*) 800 is the default, but in reality you should use the value of sv_gravity. It should be easy to replace the constant, but the explanation focuses on the important variables for clarity.
#869 posted by Spirit [82.113.106.93] on 2012/08/19 23:47:54
check if some mods with "z-aware ogres" (one of the worst quake gameplay changes people like to call a bugfix) are open-source and allow copying, maybe you can use the calculation from their aim.
Confused
#870 posted by sock [24.84.102.77] on 2012/08/20 00:56:41
Thanks Preach for the explanation but I still don't understand what to code in QC. I know values for some stuff but not the initial velocity. Also I don't mind this is not 100% accurate, I just want something that is roughly right to the nearest 64 units.
I know the following values:
start and finish origin
(s) distance = finish_z - start_z
(a) acceleration = 800 (will use sv variable)
(t) time = s / 200 (wild guess)
(I assume objects travel 200 units forward top speed, it would be nice to know what this value is for monsters)
Things I don't know:
(u) initial velocity = ??
formula:
s = ut + ½at²
Expanded out:
s = u * t + sqr ( ( a * 0.5) * t )
I don't know what 'u' is, how do I re-arrange this to find initial velocity (u)?
@spirit, z aware monsters are not all bad, the problem is players don't expect the change and get angry because things are different for no apparent reason. The best solution for z aware monsters is to link them to a high skill level and make them visually different.
Arbitration
#871 posted by Preach [77.98.165.95] on 2012/08/20 01:27:06
u is defined in the first paragraph:
Start by picking a velocity upwards, call that v_z.
The terminology is a bit mixed up, u is the standard name for the initial velocity in that equation; the name v_z was chosen earlier to suggest a vector component style but in retrospect confuses things.
The key is that it's arbitrary, you have complete freedom to pick whatever upwards velocity you like, then you calculate a forwards velocity to match it. I say complete freedom, but there is some guidance in the paragraph 4th from the bottom.
#872 posted by necros [99.227.156.158] on 2012/08/20 01:48:48
It helps to break things down a bit to understand the problem.
So first is a very basic part of the whole problem, the horizontal speed.
If velocity is 600 units/sec, then that means, in 1 second, you have a distance covered of 600 units.
Seems dumb, but the next part is what you are trying to figure out:
If you KNOW the horizontal speed you want to move at, then what you need to do is find out how high to jump so that you stay in the air for that amount of time.
At this point, it can be easier to wing it.
First find the horizontal distance:
(self is some monster)
vector vec = self.origin - self.enemy.origin;
vector vec_z = 0; //this flatten the vector
float hdist = vlen(temp2 - temp1);
next we decide how fast we want the guy to jump:
float hspeed = 600; //fiends jump this fast horizontally
at this point, we could do something like this:
float vtime = hdist / hspeed;
vtime is now distance / speed (which is units of time)
So we have the amount of time needed to stay in the air.
vector dir = normalize(vec);
self.velocity = (vtime * 400) * '0 0 1' + (dir * hspeed);
the vtime * 400 part is the winging it bit. It works well for most values below super fast velocities.
#873 posted by necros [99.227.156.158] on 2012/08/20 01:50:30
also, it might be a good idea to cap some values. depending on how far the monster is from it's target, vtime can potentially be quite long, meaning the velocity needed to stay in the air will be silly high.
either that or disallow the jump completely beyond a range.
#874 posted by necros [99.227.156.158] on 2012/08/20 01:51:34
ohhh, finally, this is only accurate for cases where both monster and target are on equal height. for any other case, you will need to use the physics equation.
The Final Jump
#875 posted by sock [24.84.102.77] on 2012/08/20 04:49:22
Thanks for the help everyone, here is my final chunk of code for my jump system. It works really well and all the jumps I have setup always go downwards so the gravity/4 looks more natural than using /2.
some constants I used:
MONAI_JUMPSPEED = 200;
DEF_GRAVITY = 800;
Code:
local vector jumporg, jumptarg, jumpdir;
local float jumpdist, jumptime;
// Calculate jump velocity towards destination node
jumporg = self.origin;
jumptarg = self.target_ainode.origin;
jumporg_z = jumptarg_z = 0; // Flatten vectors
// turn towards jump destination node
self.ideal_yaw = vectoyaw(jumptarg - jumporg);
self.angles_y = self.ideal_yaw;
// Calculate distance and time
jumpdist = vlen(jumptarg - jumporg);
jumptime = jumpdist / MONAI_JUMPSPEED;
jumpdir = normalize(jumptarg - jumporg);
self.velocity = (jumptime * (DEF_GRAVITY/4) ) * '0 0 1' + (jumpdir * MONAI_JUMPSPEED);
Hopefully it might be helpful to someone else.
@ Tronyn
#876 posted by golden_boy [46.142.55.90] on 2012/10/03 01:57:21
Sorry, I only rarely read here, but I can be contacted via the e-mail in my profile. I'll gladly reply to mails.
It can be weeks before I check back here.
Stubborn Fox
#877 posted by MadFox [84.26.169.65] on 2012/10/22 23:06:02
Thanks for that bit of code, Sock.
Comes in handy for the RocketJumper I made.
I'm still experimenting with the amphibian.
I made a new model for it, but the code still won't work well.
http://members.home.nl/gimli/dwell...
I know it's rather a high catch to invite a monster in Quake
that runs also on land as swimming in water.
For so far I managed it to stand on land and run for the player,
follow it in water turning into a swim pose.
The pitty edge is to get it back into its walk state.
Now it jumps back on land in its swim pose, only turns back into the walk frames after it has bin shot.
The reason for this is that I made an addition in the Fight.Qc
float() HarpCheckAttack =
{
local vector spot1, spot2;
local entity targ;
local float chance;
//check we are in the water and also standing
if (self.waterlevel > 0 && self.th_stand == h_stand1)
{
dprint("standing on ground \n");
h_jive1();
harpi_to_harpo();
return TRUE;
}
//check we are in the water and also swimming
if (self.waterlevel < 2 && self.th_stand == h_dwell1)
{
dprint("shallow enough to stand\n");
h_mour1();
harpo_to_harpi();
return TRUE;
}
The first statement gives it a stand attitude on land :
if (self.waterlevel > 0
If I use another number, 2feet 3 chest 4 eyes, the monster won't go back up the land.
The impulse to do so comes in with
harpi_to_harpo();
The second statement gives it a swim pose in water :
if (self.waterlevel < 2
This impulse comes with
harpo_to_harpi();
harpi is in water
harpo is on land
So far it is clear to me.
I try to figure out what's going on when the monster comes out of the water and don't start the stand position.
#878 posted by necros [99.227.223.212] on 2012/10/22 23:47:14
it's difficult to see what to change without seeing more (or all) of the code.
you could try also adding the same checks you have into the first frame of the running sequence.
so what I would do is this:
make the state check it's own function
void() HarpCheckState =
{
//check we are in the water and also standing
if (self.waterlevel > 0 && self.th_stand == h_stand1)
{
dprint("standing on ground \n");
h_jive1();
harpi_to_harpo();
return;
}
//check we are in the water and also swimming
if (self.waterlevel < 2 && self.th_stand == h_dwell1)
{
dprint("shallow enough to stand\n");
h_mour1();
harpo_to_harpi();
return;
}
then add a call to it in the first run frame:
void() harp_run1 =[ $run1, harp_run2 ] {ai_run();HarpCheckState()};
the first run frame is called whenever monsters switch targets or wake up, so it should be more reliable than waiting for it to try to attack.
#879 posted by MadFox [84.26.169.65] on 2012/10/23 00:20:59
Right, in addition the corredsponding file only files that matter
There are two modelposes in one file.
The land state and the swim state.
Both are switched in the djive go-in-water and mour come-on-land poses.
harpio.qc
frames$
void() h_jive1 =[ $djive1, h_jive2 ] {harpi_to_harpo();};
...
void() h_jive10 =[ $djive10, h_swim1 ] {};
void() h_mour1 =[ $mour1, h_mour2 ] {harpo_to_harpi();};
...
void() h_mour10 =[ $mour10, h_run1 ] {};
void() harpo_to_harpi =
{
self.th_stand = h_stand1;
self.th_walk = h_walk1;
self.th_run = h_run1;
self.th_die = harpio_die;
self.th_pain = harpio_pain;
self.th_missile = h_attack1;
self.th_melee = harpi_melee;
self.flags = self.flags - (self.flags & FL_SWIM);
};
void() monster_harpio =
{
if (deathmatch)
{
remove(self);
return;
}
precache_model2 ("progs/harpio.mdl");
precache_model2 ("progs/slmbal.mdl");
precache_model2 ("progs/h_model.mdl");
precache_sound2 ("player/death2.wav");
precache_sound2 ("fish/bite.wav");
precache_sound2 ("fish/death.wav");
precache_sound2 ("harpi/sight.wav");
precache_sound2 ("enforcer/enfstop.wav")...
precache_sound2 ("enforcer/enfire.wav");
precache_sound2 ("knight/khurt.wav");
precache_sound2 ("harpi/idle.wav");
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP;
setmodel (self, "progs/harpio.mdl");
setsize (self, '-16 -16 -24', '16 16 24');
self.health = 200;
harpo_to_harpi ();
walkmonster_start ();
};
void() harpi_to_harpo =
{
self.th_stand = h_dwell1;
self.th_walk = h_swim1;
self.th_run = h_crawl1;
self.th_die = h_die1;
self.th_pain = newharpio_pain;
self.th_melee = harpo_melee;
self.th_missile = h_harp1;
self.flags = self.flags | FL_SWIM;
};
The reason it won't go back to its land state might be the start scene with harpo_to_harpi statement.
So as soon it reaches
void() h_mour10 =[ $mour10, h_run1 ] {};
its original state reminds to harpo_to_harpi
before the walk_monster_start in void()monster_harpio.(?)
I'll try the HarpCheckState(), but doesn't it need more addition to the Harp_Check_Attack?
Madfox
#880 posted by Preach [77.98.165.95] on 2012/10/23 00:34:49
Add a line to each of the of the h_mour functions where you send out a debug message like so
void() h_mour1 =[ $mour1, h_mour2 ] {harpo_to_harpi();
bprint("I am in h_mour1! \n");
};
Then see how far through the animation sequence it gets. This should help you work out where the problem is.
#881 posted by MadFox [84.26.169.65] on 2012/10/23 00:47:04
I don't get any messages in game although I have the bprint in calculated.
protocol 666?
Falling Through
#882 posted by MadFox [84.26.169.65] on 2012/10/23 02:00:48
It looks as if the standing on ground and shallow enough to stand cross over each other.
http://members.home.nl/gimli/frmco...
#883 posted by necros [99.227.223.212] on 2012/10/23 02:43:46
if you are using Bprint then the messages should always appear. If you are using Dprint, then you need to have 'developer 1' set.
Regarding the monster itself, the one major problem you are having has to do with how swimming is handled in quake.
Walking monsters are able to move into water but swimming monsters are never allowed to move out of water, so no matter where you check, it is impossible for the check to succeed and switch back to land movement.
There are ways to get around that of course, but they are all (at least what I am thinking about in my head) somewhat complex.
The first idea I'm thinking about is changing the land->water check from self.waterlevel > 0 to something like self.waterlevel > 1
This would make it switch when it's much deeper in the water.
Next for the water->land check, I would check the distance to the ground from the monster's 4 bottom corners of the boundingbox to see if there is ground close to it's feet.
Then I would check waterlevel <= 1 and if it checks out, transition water->land.
There might need to be a cooldown timer to prevent it from switching back and forth too quickly too.
The second option would be to use FLY instead of SWIM and then manually stop the monster to rising out of the water.
The best way to do that would be to make a wrapper on the monster's movetogoal calls so that it moves to a helper entity which is set to the player's horizontal position, but who's vertical position is set in such a way to keep the monster under the water.
Using fly would give you more freedom to getting the monster to move OUT of the water, and then you original checks might work out.
Both those options would need a fair bit of code rewriting, unfortunately. :(
Bprint
#884 posted by Preach [77.98.165.95] on 2012/10/23 09:00:35
I've started using bprint instead of dprint for debugging messages relating to coding, so that there's no way I can forget to remove them!
One way to make the monster can leave the water might be to use the ideas in this tutorial, a hybrid between movetogoal (which won't leave the water) and velocity (which will)
http://www.quaddicted.com/webarchi...
More Stubborn Monster
#885 posted by MadFox [84.26.169.65] on 2012/10/23 10:31:21
Thanks for the advice!
I have to read the tutorial, but parms like move to goal and velocity are lucide to me.
Not that I don't understand them but the referring code doesn't fit with my knowhow.
It is almost a jack-in-the-box to me how to define a working statement.
As soon as I change the parms the monster works alright,it only comes out swimming on land and I just have to shoot it back in its walk pose.
This works and for sofar I should be glad.
That it is not the bugfree method is something I'm trying to overcome.
Practical Problem
#886 posted by Preach [77.98.165.95] on 2012/12/01 03:06:42
OK, here's a genuine issue faced when creating the next version of Quoth. A mapper who I will not name has released a Quoth map which uses some entity hacks. In particular they've created a backpack model using the modelindex hack. The new patch precaches fewer models, and so changes which model appears for the modelindex which the hack uses. This breaks the map (from a visual point of view).
Which approach would you choose?
a) Let this entity-hack remain broken to discourage future hacks
b) Insert a piece of code which detects this particular map and fixes the models within it
c) Reorder the remaining precaches so that the backpack occupies the same precache slot in this map
d) Revert the model precache savings, as they are incompatible with existing maps
Remember, there are no right answers. Only wrong ones...
#887 posted by necros [99.227.223.212] on 2012/12/01 05:48:56
create a lookup table that matches the old precache numbers to the new ones?
haha that would be painful to do. :P
#888 posted by Spirit [89.204.137.151] on 2012/12/01 09:54:13
name it quothX.Y and tell people to state the tested quoth version(s) in their readme from now on. consider all existing maps requiring quoth2.
Not Really Related But A Selfmade Problem:
#889 posted by Spirit [89.204.137.151] on 2012/12/01 10:02:43
releasing quoth2 as quoth was annoying already (from a quaddicted viewpoint). I really do not want to replace it again and pretend comments and ratings are for the new version.
I guess one would need to add an dependency option of 'provides', so eg quoth2 provides/fulfills the dependency 'quoth'.
or how about calling them "quoth_pak1", "quoth_pak2", etc.
Look-up Sir, Arrgggh!
#890 posted by Preach [77.98.165.95] on 2012/12/01 10:23:40
That's more or less what option c) is, except you only match this particular model to the index it once had. If you're precaching fewer models, it's obvious that you can't do that for every model, so the solution would not be sustainable if this hack was common.
I thought the backpack would be reasonably easy to fix like this because it's in that big list of guaranteed precaches in world.qc. You'd just look at the list and move the backpack precache down as many precaches as it takes. On looking at it, this might not work; unfortunately the backpack is one of the last things to be precached. The number we'd have to give it would belong to the "variable precaches region" which changes depending on which entities the map includes.
You could try to add a wrapper to the precaching function which counts the number of calls, and then precaches the backpack after the right number of entries. There's two problems: First you need to filter out multiple calls to precache the same file (which is hard given QC's lack of support for arrays). This can be done with some effort though, because we know exactly how many precaches we need to remember - the number between the guaranteed precaches and the desired precache value for the backpack.
Then you need some way of dealing with the possibility that not enough models get precached, and so the backpack is never precached either. In QC you don't have any way of telling how many more entities there are to spawn, and when the last spawn function has run, you no longer have the ability to precache models. So you can't predict ahead of time you're in the state where the backpack won't get precached. Any solution for this case would need to be based on the idea of working round the backpack model missing its precache, which is actively detrimental.
So the only proper way to get this trick to work is to sniff which map is being loaded by some other means, and fix it up then. At this stage b) is probably more attractive.
Spirit
#891 posted by Preach [77.98.165.95] on 2012/12/01 10:29:05
There are lots of enhancments in new versions of a mod that benefit existing maps - like the rebalancing of annoying monsters, or the fixing of occasionally occuring bugs. So we need backwards compatibility.
#892 posted by negke [31.18.186.11] on 2012/12/01 10:29:19
Said mapper may also have included two versions of the hack to make it work on both Quoth 1 and 2, so if a new release were to address compatibility issues, it would probably need to take care of two modelindex shifts (unless Quoth 1 didn't change anything about it).
Yes
#893 posted by Preach [77.98.165.95] on 2012/12/01 10:46:18
I noticed that, and wondered if that might have been a hint to whoever it was not to indulge in that kind of behaviour. For the record, using a mapobject_custom is a much easier way to get the model you want (they aren't static entities by default). That would only require the hacky version in quoth 1, and then using a designed entity for other versions will be forward compatible.
Preach
#894 posted by Spirit [80.171.143.236] on 2012/12/01 10:53:59
Rebalancing monsters IS breaking backwards compatibility!
(Aaaaahh change!)
True
#895 posted by negke [31.18.186.11] on 2012/12/01 10:55:31
There should have been an item_custom entity to begin with.... Which new model does it display anyway?
Btw, since you're working on a new version of Quoth, do you have a list of desirable features mentioned over the years or something like that? Obvious things that Quoth is missing for no good reason. Not going for a public feature request discussion, just interested.
The List
#896 posted by Preach [77.98.165.95] on 2012/12/01 11:11:16
To make clear, the current patch is limited in scope, and the specific limit I am sticking to is no new entities. I do have other ideas for what might go into a future version of Quoth, custom items is not on it though...It currently displays a head gib with a very weird skin.
Spirit, backwards compatibility is not the same as identical behaviour, else fixing bugs is prohibited because the new patch doesn't crash to the console. For gameplay, I take backwards compatibility to be: from any map or set of circumstances that a player could complete a map in the previous patch, they still can in the new patch with no more difficulty. As someone pointed out in the Quoth 2 thread, making monsters weaker does not affect that. Obviously there are other measures of backwards compatibility that have to be considered, and visual fidelity is one.
#897 posted by onetruepurple [91.240.47.30] on 2012/12/01 11:33:07
Is the protocol 15 precache limit really that big of a problem in 2012?
Steam
#898 posted by Preach [77.98.165.95] on 2012/12/01 11:46:43
I'd hazard a guess that the majority of people with quake installed right now use protocol 15, because they bought it bundled on Steam and don't know anything about custom engines. I'm not imagining that every mapper will create maps that run on all engines. Still, if some mappers might want to, the mod should do all it can to enable that.
#899 posted by onetruepurple [91.240.47.30] on 2012/12/01 11:50:17
That's true, but if they know about custom maps in the first place, then knowing about custom engines is just a few inches away.
Unless it's not possible to run a custom engine with Quake on Steam?
Barriers
#900 posted by Preach [77.98.165.95] on 2012/12/01 11:55:58
I think you can, but each extra barrier to running the map is gonna turn away some fraction of your audience.
Follow-up
#901 posted by Preach [77.98.165.95] on 2012/12/02 11:33:58
If you did want to design a mod where it was clear to mappers what they were free to touch and what you were reserving for future modification, then you might be able to use some of the tricks in an article I've just posted:
http://tomeofpreach.wordpress.com/...
For people who think that it's all a bit crazy and worry for the future of Quoth, don't. Firstly it's too late to do all of this for Quoth, you'd need to have done it before you release the first version and people start making maps. Secondly, Quoth probably doesn't need it because it's fairly slow moving. The private fields stuff would be more useful when you're releasing new versions each month or something, keeping new features private while you're messing with the design, like an "under construction" gif.
If you still think it's a bit paranoid, hold on, because there's a much more interesting use of these field name tricks coming up.
#902 posted by necros [99.227.223.212] on 2012/12/02 15:41:36
That... Is very interesting. Would have been useful to protect stuff for sure. Although if you're going to limit things in that way then you should try to provide ways to do those things explicitely.
#903 posted by ZealousQuakeFan [82.16.91.127] on 2012/12/02 20:12:15
Could someone explain mechanically how Quake's monster stun effect works? Is it like Doom's where monsters have a percentage chance to be stunned, or does it go off the amount of damage cause etc?
#904 posted by Spirit [80.171.154.52] on 2012/12/02 20:24:24
I think it happens on specific animation frames
(i Guess You Mean The Sudden Attack Cancelling Effect)
#905 posted by Spirit [80.171.154.52] on 2012/12/02 20:24:43
Percentage
#906 posted by Preach [77.98.165.95] on 2012/12/02 22:23:20
It's something that varies from monster to monster, and it depends on a lot of things:
Random chance
Amount of damage
Time since last attack
Skill level
Each monster has an individual function to decide what to do, I'll explain two contrasting ones:
The grunt is on the weakest end of things. The first hit it takes will always send it into pain. At that moment one of the pain animations is selected, and it's pain_finished field is set so that it can't go into pain again for a short time. The pain_finished time for grunts is barely long enough for the pain animations to complete. Amount of damage has no effect.
The shambler is on the opposite end of things. When it takes a hit, it generates a random number between 0 and 400. If the amount of damage is less than the number generated, it ignores the hit. Otherwise it goes into pain, and sets pain_finished to 2 seconds into the future.
In between these two extremes there are lots of variations. The pain_finished time is unbreakable, but after that there is usually some amount of damage which guarantees a pain animation (e.g. we see the shambler takes 400, the death knight is only 30). Nightmare skill has a global effect - the minimum gap between pain animations is 5 seconds for everything.
#907 posted by ZealousQuakeFan [82.16.91.127] on 2012/12/02 23:13:11
ic, interesting. It certainly seems somewhat random but a lot less so than Doom's...
Cheers :)
Overlaps
#908 posted by Preach [77.98.165.95] on 2012/12/03 10:57:32
As promised, the second half of yesterday's article, with a more sensible use for private and hidden fields, after introducing overlapping fields.
http://tomeofpreach.wordpress.com/...
Re: Practical Problem
#909 posted by Scampie [72.12.65.92] on 2012/12/03 20:27:20
I know this is a hack, but could you not add special consideration to 'info_notnull' that when it has a 'model', you search for other entities with the same 'model', and set it's 'modelindex' the same?
So if I have a:
"classname" "info_notnull"
"origin" "# # #"
"think" "PlaceItem"
"nextthink" "0.2"
"touch" "BackpackTouch"
"model" "progs/backpack.mdl"
"modelindex" "#"
We'd look for another entity which already was 'model' 'progs/backpack.mdl', and use it's modelindex as our info_notnull's. This means the info_notnull would have to be later than an actual backpack (or whatever we were trying to copy)... but wouldn't that make the designer setting the correct modelindex irrelevent, and all he'd need to do is set a correct model?
Modelindex
#910 posted by Preach [77.98.165.95] on 2012/12/03 21:37:49
Yeah, there's really no need to be doing anything with the modelindex if the mod supports custom model setting. Thankfully Quoth provides that with the mapobject_custom entity - which will also precache whichever model you supply it so you aren't restricted to models the mod already uses. The mapobject_custom is basically an info_notnull in every other way, so if you were to apply other hacks to it they'd work fine.
I stopped short there of saying "go ahead and use hacks", as they can cause problems, but I do still keep them in mind when designing things. For example, one of the features I've considered for Quoth is aggressive entity optimisation. This would basically be runtime consideration of which entities don't actually need a full entity slot to themselves (yet), along with a way to let entities share slots for the time being.
The path_corner is the simplest example of how this would work. Even with all the exciting extra features added to them in Quoth, you only need about half a dozen fields to store everything relevant about a path_corner. I've written code that will take those vital fields and stash them into one of four field-sets, making an entity with a "library" of 4 path_corners. The pool of library entities also doubles as the active path_corner entities, which are dynamically generated as a monster heads towards one.
So if this is all done and working, then why isn't it going into 2.2? The reason is hacks. The logic that says you only need 6 fields to store the relevant info from a path_corner breaks down if someone performs any kind of hack on the path_corner. Because there's no limits to what might be hacked, you can't predict what extra fields you'd need to store to restore the hack to working order, and the fact that potentially you'd need to store any field means there's no space to store even a second path_corner. Even if you could, dynamically generating the path_corner is equally toxic to potential hacks, as the path_corner might not exist at the vital moment the hack is to be triggered.
I can see two ways round this: one would be to create a global opt-in flag like the model optimisation flags: the flag would mean "This map is free of hacks, so go ahead and aggressively optimise it". The alternative would be a local opt-out flag on entities, meaning "don't optimise this entity, I'm performing a hack on it!". While I'd prefer the latter, I suspect it wouldn't be backwards compatible.
Some might say that surely path_corner is a safe thing to optimise, that nobody would hack. However
1) There's already a hack involving them
2) Imagine the same principle applied to unspawned monsters
The take-home message is this: Map hackers of the world, you are being considered, but you make it hard on us!
Is That So?
#911 posted by onetruepurple [91.240.47.30] on 2012/12/03 21:53:04
The mapobject_custom is basically an info_notnull in every other way, so if you were to apply other hacks to it they'd work fine.
Applying the "give the player a DBS" fields from my info_notnull to my mapobject_custom made the backpack model not appear.
{
"classname" "mapobject_custom"
"origin" "320 184 -244"
"model" "progs/backpack.mdl"
"skin" "14"
"think" "InitTrigger"
"touch" "BackpackTouch"
"items" "2"
"netname" "Double-barreled Shotgun"
"nextthink" "0.2"
"targetname" "pack_dbs"
}
InitTrigger
#912 posted by Preach [77.98.165.95] on 2012/12/03 22:02:45
InitTrigger is to blame there, it intentionally hides the model of whatever it is run on, so that you can't see triggers.
I See.
#913 posted by onetruepurple [91.240.47.30] on 2012/12/03 22:10:18
Any way around it?
#914 posted by necros [99.227.223.212] on 2012/12/03 22:13:11
the flag would mean "This map is free of hacks, so go ahead and aggressively optimise it"
this isn't that bad an idea... like setting a strict doctype.
Two Replies In One
#915 posted by Preach [77.98.165.95] on 2012/12/03 23:15:56
onetruepurple: try swapping the classname and the think.
necros: yeah, I think it's what will have to happen. Maybe you can have the local opt-out flag as well. Opt-in flag means: "I will let you know when I'm hacking", opt-out means: "This is a place I am hacking"
What Else
#916 posted by ijed [200.73.66.2] on 2012/12/05 23:57:09
Other than a missing precache, causes Quake to crash without error?
Dusting off my coding skills and this has me stumped.
No errors in the compile either.. although I am running FTEQCC. It's definitely something I've done recently though.
Difficult
#917 posted by Preach [77.98.165.95] on 2012/12/06 00:31:41
Best advice I can offer is call traceon() in the worldspawn function, then run with -condebug on. You'll get a massive log file, but you probably only need the last few lines to find out what runs immediately before the crash. Otherwise, maybe try a few different engines, darkplaces will often tell you something that other engines don't.
#918 posted by rj [82.3.233.25] on 2012/12/06 00:47:21
infinite while loop? had that a few times
Ijed
#919 posted by Mandel [80.217.68.43] on 2012/12/06 06:39:39
Another crasher: whatever's happening on e2m2 when you pass the front gate without shooting both buttons on easy skill.
Thanks!
#920 posted by ijed [200.73.66.2] on 2012/12/06 13:55:54
Will get this figured out.
Overlapping Fields
#921 posted by necros [99.227.223.212] on 2012/12/12 15:28:37
Preach,
So recently, I treat QuakeC as if it were an OO language in that I ALWAYS create new .variables for all entities.
I do this mainly for readability/clarity reasons after getting burned when trying to make sense of some older code that was using things like t_width and lip all over the place.
Of course, now every entity has like an extra 100 variables attached to it. While it's unlikely to really impact the code much (it's not like we're lacking memory or processing power here) it does bug me a little in that it's not really the right thing to do for this language.
So... would the proper course of action be to declare a handful of invisible fields:
internal_float1__ ... internal_float10__
internal_vector1__ ... internal_vector5__
etc...
and then overlap the same fields with unique names?
Yeah, I Guess
#922 posted by Preach [77.98.165.95] on 2012/12/12 20:58:38
It could work, but you've got to be really careful that you're never overlapping fields that will cause problems. You don't need to give them names like internal_ etc, you can just pick one name as the original and overlap the others on that.
Doing this kind of overlapping in monster code might be safest. Make sure each monster has its own QC file, and define the overlapped field name at the top of the monster's own QC file. Then you can make sure all use of these fields is localized to functions in the one QC file. To be doubly sure, instead of using the "var" keyword to overlap, use a #DEFINE macro to give the field its name, and then undefine it at the bottom of the file. Then you only need to watch out for other QC files calling your functions (and map-hacks).
What would be really nice would be a sort of QC++ compiler which creates the idea of a "class". It would let you subclass entities, have functions that only accept particular classes of entity, and prevent you accessing entity members which aren't defined for that class (or parent classes) at compile time. Then the compiler would overlap private fields for you safely.
This isn't quite as crazy as it sounds, the original C++ compilers were source code transformers which output C code and then called a C compiler to build it. A fun project for someone who wants to learn Lexers and Parsers...
Wah
#923 posted by ijed [200.73.66.2] on 2012/12/13 22:23:41
After rolling back everything - models, sounds, code, maps I still got the crash on map load.
So I open up the map and clean out the values I'd added to control the enemy in question, and that solved it.
pose 0
skin -1
cnt 0
One of those was the guilty party I'm assuming the skin value - I was using -1 as 'random'.
(this is about a crash I mentioned earlier in the thread, thought it might be of passing interest)
Yeah
#924 posted by Preach [77.98.165.95] on 2012/12/14 00:29:43
I can imagine how that might cause a crash, yeah. Setting fields to zero in a map should do nothing at all, fields are zeroed to begin with. The -1 on the skin might do it though! The engine probably uses -1 as an offset into the skins array, and reads some random memory just before the site of the model data. If you still want to live dangerously and use -1 as the "random skin signifier", you can probably do it just by altering your QC a little - handle the -1 case and randomly select a skin before any call to setmodel.
#925 posted by metlslime [159.153.4.50] on 2012/12/14 01:52:21
With a skin of -1, MSG_WriteByte will coerce the value to a unsigned char, which I think gives it a value of 255? Then the client gets the 255 and and uses that index to look up the gltexture in an array. This array access is probably out of bounds, causing the crash.
Aha
#926 posted by ijed [200.73.66.2] on 2012/12/14 14:03:10
So when I cleaned up the code and put setmodel / setskin in the right places it broke...
I had to delete a lot of stuff, but I don't see it as wasted time - now I can build it much quicker and better.
It's for an undead enemy that 'hatches' from dead marine corpses - the unlucky guy's own skeletal and muscular structure, minus the skin.
OOB For Fun And Profit
#927 posted by Preach [77.98.165.95] on 2012/12/14 19:42:45
Another fun way to go out of bounds with skins is to use a skin coordinate which goes off the edges of the skin. GL engines handle this fine, they start tiling the skin which is very handy. Unfortunately software engines just do blind pointer arithmetic, and so start rendering the rest of the model data as if it were the skin...
Well
#928 posted by ijed [200.73.66.2] on 2012/12/14 20:29:41
I understand the problem well enough now to think of a workaround. A lot of the bad practices in Quake (mapping, sounds, coding etc) aren't really documented anywhere clearly and exist as generally known knowledge, making it hard to know it all...
Thanks for the help :)
Wicked Map
#929 posted by mechtech [65.190.42.20] on 2012/12/23 01:23:08
After playing Something Wicked This Way Comes. I started thinking about the expansion of the BSP format, the VIS times involved in expansive maps, and the task of managing large maps in an editor. Has anyone tried to create an engine/QC that would allow information sharing over multiple bsp files? I think that making the BSP format larger is one way to make larger maps but the time to compile/test/compile is going to be a problem. Fixing one end of the pipeline at the expense of the other. Wicked could have been split in two, both halves in memory and the textures loaded, a transition would be almost instant. I'm not a coder but that seems better than making vis run for weeks.
Well
#930 posted by ijed [200.73.66.2] on 2012/12/26 18:33:17
Vis doesn't need to run for weeks. What Quake is really missing is its own method for doing detail brushes.
If you look at those massive towers in something wicked, you'll see that they're both func_wall - noclip out of the world and they'll vanish.
If those had been part of the vis process then it would have taken significantly longer.
What the RMQ engine does to enable stupidly big and complex func_walls is to fix the entity flicker bug.
Normally, if you try to have a func_wall that stretches across various vis leafs, it flickers in game as the player moves between the different leafs.
With this bug fixed, you can make a func_wall that spans across practically a whole level, with lots of small details, curved surfaces or whatever and doesn't impact vis time at all.
There is of course a trade off in performance, but seeing as vis was designed to work on 1997 hardware - with 8K of ram - this point is pretty much moot.
These tools aren't automatic though, and a certain amount of technique must be learned in order for them to be used.
I'm far too impatient to vis a map for anything over an hour - and I don't want levels that are choppy either.
#931 posted by ZealousQuakeFan [86.190.187.103] on 2012/12/26 19:43:34
Is work continuing on the RMQ engine? I hope it does because it's performance is so much higher than other engines like Fitz which is much more interesting to me than extra eye candy :/
#932 posted by sock [200.82.42.123] on 2012/12/26 19:47:56
I know a lot of mappers use func_wall like detail but I always have problems with their shadows when aligned to world geometry. An object which is part of a wall will cast better shadows than the object as a func_wall placed against a wall.
Personally I think the compiler tools could do with some upgrades more than allowing mappers to create long range shooting galleries.
* Consist lighting across bmodels and geometry.
* bmodels having a minlighting options so it is easier to get rid of solid black surfaces.
* Having external light maps so the lightmap density can be increase (would need engine support)
* Being able to having textures with phong shading (probably need a new compiler tool to split the surfaces off in a separate area of the BSP for the light tool to work with them.)
Some of the most radical visual/mapping upgrades to Q3 was when Yndar was working with the compiler tools. I am sure it was the same when Bengt Jardrup worked on the Q1 tools.
External Lightmaps
#933 posted by necros [99.227.223.212] on 2012/12/26 20:02:23
this is interesting... what exactly is it, and how does it work? ie: external vs internal, is it just upping lightmap resolution?
I am sure it was the same when Bengt Jardrup worked on the Q1 tools.
Pretty much. It was a huge loss when he moved to q2. He consolidated like a dozen different compilers together and his work on the light utility in particular was amazing, not to mention vis progress saving.
RMQe
#934 posted by ijed [200.73.66.2] on 2012/12/26 20:32:31
Don't know - Mh was working on it.
The lack of shadows can be an issue, I tend to avoid it for large objects by placing a simplified mesh inside the func_wall - just a block usually, which won't upset vis but still give shadows.
Not perfect shadows, but the resolution of the shadowmap isn't great in any case and small details are usually lost.
For external lightmaps do you mean direct access to the lightmap texture? I think it can be extracted from the bsp, though not sure how.
And yeah, Bengt did amazing work to consolidate and improve everything.
#935 posted by sock [200.82.42.123] on 2012/12/26 22:43:24
@necros, external lightmaps are large (usually tga) files which only have light data. They are external because the internal bsp format only supports small light maps. By increasing the size of the maps the lightmaps on objects can be bigger (less fragments) and high density for remapped on to the relevant compiler surface. To create this feature would require a lot of effort to include in the Q1 compiler tools.
The most linked article on external light maps! :P
http://sgq3-mapping.blogspot.com.a...
Probably the most annoying thing about light maps is that they are rarely consistent across multiple object types (brushwork, models, entities) and will produce different results depending on how the compiler tools organize/sorts them. Even Quake 3 with all of its fancy features still has different compiler pipelines for models and brushwork which can cause strange lightmap errors.
Some examples of weird lightmap errors:
http://www.quake3world.com/forum/v...
#936 posted by necros [99.227.223.212] on 2012/12/27 04:18:16
That looks similar to making lightmaps in doom3.
I guess the compiler would need to both export the lightmap as a texture AND output uv information for the bsp faces being mapped?
I don't know how likely someone can do that for q1...
#937 posted by ZealousQuakeFan [86.190.187.103] on 2012/12/27 13:12:23
Looks very similar to what you could do in ut2k3, making entire levels out of meshes that were pre-lit in Max/Maya.
crappy screenshot
Was a gorgeous effect if used properly, fantastic lighting and such (on the environment at least), given that ut2k3 didn't have any kind of light bounce and meshes were purely vertex lit... problem was building an entire map in Max was a bastard :(
#938 posted by necros [99.227.223.212] on 2012/12/27 20:55:20
Yeah, that was the same thing for d3. I found building maps in max to be much easier too, because you could make complex shapes much more easily and utilize the scripting capabilities to quickly build stuff.
Question
#939 posted by ijed [200.73.66.2] on 2013/01/02 18:08:19
Do models have their own centre point that Quake somehow reads?
I'm trying to swap models and my enemy is getting stuff stuck in the floor. I set the bbox manually, but it seems to get confused depending on when I make the walkmove! check.
Might be better off just setting model to null until it needs to be alive and using a .entity for the other version.
I Know
#940 posted by ijed [200.73.66.2] on 2013/01/02 18:10:18
There's an 'eye plane' or something like that, but AFAIK it's just some helper that Qme included and didn't actually do anything.
#941 posted by necros [99.227.223.212] on 2013/01/02 19:43:06
There is an origin on quake models. You need to have the model's feet 24 units below the model origin in order for it to line up with the ground.
Ok
#942 posted by ijed [200.73.66.2] on 2013/01/02 20:15:39
Need to throw more DropToFloor experiments at it.
What's 24 units in normal dimensions? I googled a bit and this: http://www.gamers.org/dEngine/quak...
Tells me it's 72cm...
Feel like I should know all this stuff, but its the first time I've had this problem - or maybe just the first time I've noticed since I'm mixing legacy with new assets in the same monster.
#943 posted by necros [99.227.223.212] on 2013/01/02 20:19:34
The engine bounding box plays a role in determining the position of the model as well.
eg: '-16 -16 0' '16 16 64' is not the same as '-16 -16 -24' '16 16 40'
Aha
#944 posted by ijed [200.73.66.2] on 2013/01/02 20:29:08
That's probably it then - I've been getting odd behaviour and after the previous problem tried changing the order of where I was defining the box / changing the model asset.
Sure enough, things got free that weren't stuck before.
Weird how it was done. Especially using imperial measurements.
#945 posted by necros [99.227.223.212] on 2013/01/02 20:38:28
hold on... i might be wrong on my last post... it's been a while since i've messed with models and bboxes too. :S
Heheh
#946 posted by ijed [200.73.66.2] on 2013/01/02 20:48:25
Well, I'm going to be redoing the mesh from scratch anyway, but want to get something lashed together in the code for now anyway.
So will have to experiment and see what works.
FYI it's based off that model you sent me some time ago, although I've modified it pretty heavily into something else now, next step being an updated mesh...
Got It
#947 posted by ijed [200.73.66.2] on 2013/01/02 22:47:24
Thanks for the help :)
New Question
#948 posted by ijed [200.73.66.2] on 2013/01/09 23:38:38
How do I get a flyer to prefer to be higher than the standard 'eye level' fly height?
Searching around, it seems that movetogoal is the answer, but this is in the C code apparently and I want to stick to qc.
I don't exactly want a B0b vertical dodge - although that could be useful as well for when it's in hunt mode.
#949 posted by metlslime [166.137.191.15] on 2013/01/10 00:18:59
Look at rubicon 2 source, I have a hard-coded min and max altitude that can easily be changed. The trick is using an invisible dummy entity as .enemy to trick movetogoal into seeking the desired altitude.
#950 posted by necros [99.227.223.212] on 2013/01/10 01:19:43
That's how i've done it too. Here's my implementation: http://mobile.sheridanc.on.ca/~jon...
Great
#951 posted by ijed [186.37.203.37] on 2013/01/10 10:58:27
Thanks guys.
Perfect
#952 posted by ijed [200.73.66.2] on 2013/01/10 13:02:54
Lots of useful stuff to learn from.
Insightful to see two different implementations that work with slightly different approaches.
Oh Dear
#953 posted by Preach [77.98.165.95] on 2013/01/19 00:28:54
Oh no, another blog post. If you're using the hipnotic rotating entities, you might want to go grab this fix...
http://tomeofpreach.wordpress.com/...
#954 posted by necros [99.227.223.212] on 2013/01/19 01:28:00
Thank you!
Wait
#955 posted by necros [99.227.223.212] on 2013/01/19 01:29:13
did you have an old version of hipnotic? mine already had that fix in.
Maybe...
#956 posted by Preach [77.98.165.95] on 2013/01/19 11:48:52
I was working on Quoth when I noticed it, so that might have been out of date.
Stack Overflow
#957 posted by MadFox [84.26.94.131] on 2013/01/29 21:20:49
I'm puzzled about monster behaviour.
After setting the qc for self.th_stand, self.th_walk and self.th_run, self.th_pain and self.th_die I'm left with the self.th_melee and self.th_missile.
As long as I take the "SUB_NULL" everything goes fine, except the monster can't attack.
As soon as I give these parms a function the game returns them as
ai:CHeckAnyAttack
fight:CheckAttack
monster:monster_atk1
stack overflow.
Maybe it is my wrong assumption a monster can be added with only changing the monster's qc and leaving the ai and fight.qc aside.
Earlier I added monster with the enforcer.qc and there were no console messages.
I know I have to change things in ai.qc and fight.qc, as I did by adding a CheckAnyAttack in ai.qc and a CheckAttack in fight.qc.
Still the compiler sees no wrong but in game the console hangs on stack overflow.
Stack Overflow
#958 posted by Preach [77.98.165.95] on 2013/01/29 22:16:26
Stack overflow often comes from two functions calling each other in a loop that cannot be escaped. CheckAttack will run any function you put in th_missile or th_melee. If monster_atk1 calls CheckAttack, then the QC just goes back and forward between the two. The fix is to make sure that you don't call CheckAttack in monster_atk1.
What can make it harder is that you might not run CheckAttack directly - you might run ai_run which runs CheckAnyAttack which runs CheckAttack. So post exactly what your monster_atk1 does and we can see how to break the loop...
Acracadabra
#959 posted by MadFox [84.26.94.131] on 2013/01/30 00:22:45
The qc is a bit long so you find it here
As i started to make changes to the ai.qc with the CheckAnyAttack and the fight.qc with CheckAttack, I tried again with a cleansource.
Proqcc asks where def.qc 699 exp ; found
Ftecqc responds with no error, but on console
The idea was a quake1 Orb, that has a melee missile attack and a jump function like the demonfiend.
Read-only AI
#960 posted by Preach [77.98.165.95] on 2013/01/30 00:41:25
It's a good exercise to try and write a self-contained monster, which only calls the ai functions but doesn't alter them at all. It's also better for making a monster that "feels" like the originals. So stay strong, let's fix the file.
You are calling ai_run in all your attack functions, which isn't always the best plan. The problem with ai_run is that it looks like "this is the function I call to move n steps forward". It's actually more like "this is the function to look for something new to do, and run forward if I can't find anything".
So ai_run is looking for something better to do than just run, and it tries to attack. When it decides it can attack, it runs orb_jump1 to get the attack started. Then trouble: orb_jump1 calls ai_run again! So ai_run looks for something better to do that it was doing, and decides to attack...
...and the loop goes on until you crash.
If you look at demon.qc, you'll see that it doesn't use ai_run anywhere in the attack functions. When it needs to move, it uses ai_charge instead. There are other ways to get round the problem, but replacing ai_run with ai_charge in your attacks is a simple fix to start with.
Yeah
#961 posted by MadFox [84.26.94.131] on 2013/01/30 01:36:46
That's what I was looking for!
I didn't see the ai_charge. I kept changing the ai and fight.qc with the loop result.
Thanks for your clear explanation, after a few days I thought the orb could only crash.
Grr, Arg
#962 posted by Preach [77.98.165.95] on 2013/02/24 21:08:21
Darkplaces is the Internet Explorer of custom engines - it has features that nobody else offers, but creates awkward incompatibilities at the same time. Today's bugbear: it changes findradius so that non-solid entities are included in the search results. This makes it possible to create invisible ghost monsters in Quoth with a rocket in the wrong place...
#963 posted by necros [99.227.223.212] on 2013/02/24 21:19:40
yeah, i don't try to support it anymore. just too many things broken.
Look Through The Sv_gameplayfix_* Cvars
#964 posted by Supa [50.193.192.106] on 2013/02/25 00:16:04
The one you want to disable in this case is sv_gameplayfix_blowupfallenzombies
Total Opt-out
#965 posted by Preach [77.98.165.95] on 2013/02/25 01:51:16
Is there a cvar that says don't do anything present or future which messes with the QC? It wasn't hard to tweak the code to work around this - the cost is in spending the time testing in darkplaces for bugs that don't occur elsewhere, reproducing them reliably and working out what the engine does differently. I'd add "sv_gameplayfix_optout 1" to quake.rc in an instant.
No Opt-out
#966 posted by LordHavoc [50.193.192.106] on 2013/02/25 04:03:40
I have no plans for an "optout" cvar because it's like taking a sledgehammer to a nail, it isn't the right solution to the problem.
The right solution to the problem is one where users don't have to do anything.
I'm still evaluating the right solution.
Arrays
#967 posted by sock [186.108.77.104] on 2013/02/27 14:56:37
Is there a QC compiler that supports arrays?
#968 posted by Spirit [80.171.7.56] on 2013/02/27 15:46:23
fteqcc!
http://fteqw.com/wiki/index.php?ti...
#969 posted by Spirit [80.171.7.56] on 2013/02/27 15:46:23
fteqcc!
http://fteqw.com/wiki/index.php?ti...
Fteqcc!
#970 posted by ijed [200.73.66.2] on 2013/02/27 18:39:02
Yes, it's great :)
Compiler
#971 posted by sock [186.108.77.104] on 2013/02/27 19:56:01
@Spirit, I am using that already, thanks :)
V_angle / Mangle
#972 posted by sock [186.108.77.104] on 2013/02/27 20:11:40
I am trying to make an object look directly at another object. I want the v_angle/mangle (Pitch/Yaw/Roll) but I can't work out how to get them.
I know about vectoyaw:
dir_float = vectoyaw(destination.origin - source.origin)
This returns the Yaw angle, but I want the Pitch angle as well.
Any clues?
Pitched
#973 posted by sock [186.108.77.104] on 2013/02/27 20:57:36
For some reason the pitch is reversed. I got the following to work but not sure why the pitch needs to be fixed.
vector vec = destination.origin - source.origin;
vector vec_dir = vectoangles (vec);
vec_dir_x = 360 - vec_dir_x;
Protocol Question
#974 posted by Mandel [80.217.79.210] on 2013/03/01 19:33:04
Which maps, mods, or demos use all or most of the fitzquake protocol features? I've added fitzquake support to my demo parsing code and would like to exercise it a bit for the sake of quality.
DarkPlaces Sv_gameplayfix Cvars Are Now Opt-in, And New Build Posted
#975 posted by LordHavoc [50.193.192.106] on 2013/03/02 06:18:02
Posted a new build of DarkPlaces today with sv_gameplay fix cvars off by default (the disruptive ones, anyway), and some bugfixes for hipnotic (hip2m3 now completable) and other maps.
I have fitzquake protocol support in the works but not the time to finish it right now, I don't understand why fitzquake uses a protocol different than the QUAKEDP protocol that it clearly borrowed several bits of code from...
#976 posted by Spirit [80.187.110.71] on 2013/03/02 08:34:38
thank you very much!
@LordHavoc - DarkPlaces Constructive Criticism
#977 posted by Baker [69.47.162.203] on 2013/03/03 01:55:58
Everything has to be something.
Pushing the limits of future ideas is not something to be apologized for. Especially if you are doing it for free.
Mental exercises of the future are something anyone can do. You express these in code, something few to no one can do.
You've done it, do continue to do it and everyone sensible is inspired by it, everyone talented draws insight from your work.
Everything has to be something.
More importantly, BUT ** everything has to be something. **
DarkPlaces cannot both be an engine of the re-imagined future and a conservative engine of the past.
If you try to go there, you'll fail on both fronts and lose your identity in the process.
Everything has to be something. If DarkPlaces tries to be everything to everyone --- it is going to fail more spectacularly than any engine Quake has ever seen.
It is ok to be an engine that thinks of ONLY how things should be and never of how they are.
If you try to change that thought process, it will not lead to happiness. Your head is in the idea of a yet unwritten better future.
Give that up and it will most certainly break your heart. And make the world a worse place.
Bear the slings of arrows of re-imagining how perfect QuakeC and a perfect Quake engine might look EVEN if it breaks expectations.
Every engine author can do the conservative engine thing. This isn't why DarkPlaces is interesting. Just don't expect everyone to like "interesting" --- that isn't in human nature.
/End tl;dr post
Hey Sock
#978 posted by ijed [200.73.66.2] on 2013/03/06 13:12:18
How did you go about your particle field controls in ITS?
I've got an emission system implemented that can throw sprites, bsps and models, but was wondering about better particle controls.
The main thing I'm wondering about is movement - I can just attach particles to an otherwise invisible emission, but was wondering what method you used, and if it'd be cheaper / better / faster.
Cheers :)
@ijed
#979 posted by sock [186.108.77.104] on 2013/03/06 14:07:17
Posted reply in ITS mod thread. If you have anymore questions let me know, will be glad to help.
Thanks, Just Replied There
#980 posted by ijed [200.73.66.2] on 2013/03/06 15:02:35
DarkPlaces
#981 posted by sock [186.108.77.104] on 2013/03/08 14:13:12
Is there any way to detect if DP is active from QC? Is there a function I can test exists and then supply different fog parameters?
Fitz engine fog
<density> <red> <blue> <green>
DP engine fog
<density> <red> <blue> <green> <alpha> <mindist> <maxdist> <top> <fadedepth>
I can't add the extra parameters to fitz it produces an error, while in DP the DEFAULT parameters fog out the sky. /sigh
Any ideas?
DP Cvar
#982 posted by sock [186.108.77.104] on 2013/03/08 15:02:26
Does anyone know of a specific DP Cvar value I can check? (I tried version but it is a string which is impossible to check)
DP Only Cvar
#983 posted by sock [186.108.77.104] on 2013/03/08 15:26:28
Found the answer, QC example below:
float dpactive;
if ( cvar( "pr_checkextension" ) ) {
dpactive = TRUE;
}
else dpactive = FALSE;
Fix Your Ugly Brace Style PLEASE!
#984 posted by czg [212.16.188.76] on 2013/03/08 15:27:30
But That's A Cool Snippet Tho!
#985 posted by czg [212.16.188.76] on 2013/03/08 15:43:47
Feature Detection
#986 posted by Preach [87.139.242.110] on 2013/03/08 18:33:02
In theory other engines might implement checkextension at some point, so you should probably be testing for features in darkplaces, rather than for darkplaces itself. It's like "feature detection" as opposed to "browser detection" in web design - that IE analogy is working overtime today (which I'd like to add was too unfair on DP, especially given how responsive LordHavoc has been to our needs!)
I've wanted for a while to be able to detect .alpha support on engines, to create entities which fall back to safer models on engines without it. At the moment it would only be darkplaces which gets the alpha-supported version, which makes the feature a bit too niche.
Sv_gameplayfix_
#987 posted by negke [31.18.175.98] on 2013/03/08 18:55:44
Can't get more DPish than that, I suppose.
Enginer Developers
#988 posted by sock [186.108.77.104] on 2013/03/08 19:21:10
I honestly wish ALL engine developer would decide on a standard way of implementing features. Like for example fog, engine detection etc. As much as it is cool that everyone wants to do different things, trying to make content that works on multiple engine is just frustrating.
I don't want to create a preferred list of engines that work with my Quake content but engine coders really are shooting themselves in to the foot over this.
I choose "pr_checkextension" because it is highlighted at the top of the dpextensions.qc file (had to download a 220mb DP mod to get the file) as something to detect DP by. Maybe LordHavoc can say which variable we should all check by.
#989 posted by Spirit [80.187.111.49] on 2013/03/08 20:08:07
at least all normal engines have the same syntax but wait until you find out that the rendering of the fog itself differs... :/
Feature Detection - How To Really Do It
#990 posted by LordHavoc [50.193.192.106] on 2013/03/09 13:32:03
The pr_checkextension cvar only indicates that the checkextension builtin (#99) is available, which then lets you query engine capabilities.
pr_checkextension exists in TomazQuake, DarkPlaces, FTEQW, I think FitzQuake, and I'd bet a few others too.
What I recommend adding this to defs.qc at the end:
float ext_dp_gfx_fog;
void() InitExtensions =
{
if (!cvar("pr_checkextension&quo...
return;
ext_dp_gfx_fog= checkextension("DP_GFX_FOG&qu...
};
Then add this to the top of worldspawn():
InitExtensions();
Now you can check the ext_dp_gfx_fog variable at any time to see if the DP_GFX_FOG extension is supported, which describes how that fog command behaves.
This is not a general catch-all way to detect DP however as any engine can implement that extension, there is not any special way to detect a particular engine (nor any plans for one).
@LordHavoc
#991 posted by sock [186.124.37.216] on 2013/03/11 01:03:10
Awesome, thanks for the extra information. I only plan to check for the basic DP engine from your site, I am not interested in supporting any other version.
I plan to use the following QC:
(in defs.qc)
float dpactive;
float dpextrafog;
float(string s) checkextension = #99;
(world.qc, function - worldspawn)
dpactive = cvar( "pr_checkextension" );
if (dpactive) dpextrafog = checkextension( "DP_GFX_FOG" );
Standards
#992 posted by [78.37.171.41] on 2013/03/11 14:28:50
Several attempts to agree on anything failed. You could probably still find threads from like 15 and 10 years ago...
Engines
#993 posted by LordHavoc [50.193.192.106] on 2013/03/12 06:26:21
The other engines I listed are *not* darkplaces, they are completely unrelated popular engines written by Tomaz (TomazQuake), Spoike (FTEQW), metlslime (FitzQuake), and so on.
They support the same extension mechanism to detect capabilities, they offer different sets of capabilities but with significant overlap.
Again this is not detecting an engine, this is detecting a capability, it just happens that DP_GFX_FOG is supported only by darkplaces currently.
I checked TomazQuake 1.481, Fitzquake 0.85, and FTEQW svn 4255 (current as of this moment) and it is possible for other engines to add support for this same extension depending on the whims of their maintainers, but currently only DarkPlaces offers this extension, it was added back in 2001-04-05, been there a long long time.
#994 posted by sock [186.124.37.216] on 2013/03/12 13:24:46
I know about the other engines (Fitz/FTE) my problem was with DP because of the fog parameters being different. I thought if I made my MOD work with Fitz it would work with anything, but it seems DP has gone in a different direction, hence the QC hack for engine detection.
#995 posted by necros [142.245.59.17] on 2013/03/12 13:39:32
feature detection only works if the features behave identically in each engine. since they do not, there should be a way to identify engines instead.
Just A Note...
#996 posted by metlslime [159.153.4.50] on 2013/03/12 20:16:35
fitzquake does not actually support the quakec extension system. I support some extensions (e.g. worldspawn fog, worldspawn sky, entity alpha), but there's no quakec mechanism to detect them.
Most of the features I added in fitzquake are mapper-centric rather than modder-centric, which is why the extension system is missing (maps can't check it anyway, they can just add keys to entities or worldspawn and hope for the best.)
#997 posted by gb [46.142.47.255] on 2013/03/13 06:56:14
quite some maps do come with, or require, mods, though. The line is quite blurred tbh.
Walking On Walls
#998 posted by enliten [203.161.90.29] on 2013/03/28 08:44:47
Hey guys,
Are you aware if there are any (engine or quakec) mods out there that allow the player to walk on walls / ceiling?
I'm interested as I'm thinking that MC Escher-esque architecture would be fun to map.
-Ben
#999 posted by Spirit [80.171.51.21] on 2013/03/28 09:29:59
FTEQW supports that with some cvar but don't ask me which one.
There is also a map by Markus Klar I believe.
Blog Post 1000
#1000 posted by Preach [77.98.165.95] on 2013/03/28 12:32:38
(It's really just coding post 1000, it just feels like it ok...)
New post on the blog, laying the groundwork for a series on displaying text in exciting ways. This one doesn't break any new ground, it just describes the way to display text by sending it character by character. There is some more exciting stuff in the pipeline though.
http://tomeofpreach.wordpress.com/...
Comments, questions or solutions to the "homework" all welcome. Also is there an up-to-date mirror of the ai cafe site I can link to in the post?
#1001 posted by Spirit [80.171.51.21] on 2013/03/28 13:35:24
http://web.archive.org/web/2006021...
Spirit:
#1002 posted by metlslime [159.153.4.50] on 2013/03/28 20:33:43
you're probably thinking of that map "The Fly" by Markus Klar.
Cool Map
#1003 posted by rj [82.9.177.217] on 2013/03/28 20:57:37
doesn't nearly get mentioned enough
Cheers Spirit
#1004 posted by Preach [77.98.165.95] on 2013/03/29 16:22:36
Here's a second part hot on the heels of the first.
http://tomeofpreach.wordpress.com/...
This time we've got the exciting idea which gets around the lack of arrays in quake. We don't actually use it to do anything interesting yet...I promise that by part III the example code will produce something cool to look at.
And Part III
#1005 posted by Preach [86.128.104.128] on 2013/03/31 01:12:56
Here we actually get to the good stuff - how to make text appear like a split-flap board. Just need the sound effect...
http://tomeofpreach.wordpress.com/...
Hopefully the potential of the system shows at this point. I remember seeing how great the end screen for Qonquer was. My hope is by the end something like a menu drawn with a border which animates to open and close is made easy.
#1006 posted by enliten [203.161.90.29] on 2013/04/01 17:24:42
I just played the fly :)
it was a fun map...
I couldn't find any cvar except for wall jumping in FTEQW :(
Preach
#1007 posted by necros [99.227.223.212] on 2013/04/03 03:54:53
just had the chance to read some of your text manip articles, very awesome. I've known about this method for a while since you originally brought it up but this lays it out really well. Thanks!
Cheers Necros
#1008 posted by Preach [77.98.165.95] on 2013/04/04 00:44:21
I've got loads more ideas lined up (eventually the print functions should be able to draw a box round arbitrary scrollable text) but now I'm back to work posts will be more spaced out. Part IV should be out by the end of the week, it's about creating persistent state.
Credit where it's due: I first saw this idea in Pyrdon Gate by Frikac, which needed lots of menus and dynamic text, and did it like
textout('h', 'a', 'l', 'b', 'e', 'r', 'd', 0);
There was also a phase of creating text based games-within-games in quake mods, like Tetris and Snake. So using WriteByte to build centerprints is not original with me. The function-as-iterator and the dynamic print functions are new ideas though, and I'm pretty excited about them (as you might have guessed!)
Ceiling Running
#1009 posted by Spike [86.182.96.71] on 2013/04/05 23:26:05
fte features:
give self.gravitydir_z=1 (-1 or 0 to revert)
gravity will now point upwards.
give self.movetype=31 (3 to revert)
gravity will change based upon the angle of the surface below you. you can run up walls by just facing upwards and walking forwards.
they're intended to be used explicitly by mods, but you can force things with the 'give' command, and its ability in fte to poke various qc fields, but note that it only really works in single player.
The current SVN build will rotate the view along with gravity but slightly older ones won't, and this will be needed if you want to run on ceilings properly.
Double Damage
#1010 posted by Preach [77.98.165.95] on 2013/04/06 02:33:41
Didn't Matrix Quake have wall-walking of some description? I'm sure that was why it needed a custom engine...
While I'm posting something almost helpful I might as well link to the next Text post.
http://tomeofpreach.wordpress.com/...
I swear the next post does all the remaining heavy lifting, and the rest will just be 100 fun things to do with text on a screen.
#1011 posted by NotoriousRay [74.107.82.97] on 2013/04/07 18:24:28
Have there been any attempts to make a more modular/extensible qc engine? Like extending a known mod's (compiled) progs.dat with some of your own stuff (for mappers, mostly new point entities or monsters)? Is it even feasible?
#1012 posted by necros [99.227.223.212] on 2013/04/07 20:37:55
like modifying an existing mod?
or allowing modification of existing progs without source through the engine?
#1013 posted by necros [99.227.223.212] on 2013/04/07 20:39:36
Actually, that wouldn't be so bad. It would be cool if a compiler was integrated into the engine so you would just distribute qc files and when the engine starts it would compile it on the fly similar to Doom3/Quake4.
#1014 posted by NotoriousRay [74.107.82.97] on 2013/04/07 21:03:02
Yeah, without modifying the existing progs.
#1015 posted by necros [99.227.223.212] on 2013/04/09 00:54:37
same as with the mapping help thread, should a link to the wiki or the coding section of the wiki be added to the start of this thread?
Aaafter Ten Thousand Years...
#1016 posted by Preach [77.98.165.95] on 2013/04/27 15:44:37
Yeah, you've had enough of a rest now, time for more blogging!
http://tomeofpreach.wordpress.com/...
This is the last of the heavy-lifting, high-concept bit of messing around with text in quake articles. At the end of this one all the messy stuff to do with printing text is moved into the helper functions we've defined, and code that wants to print text looks uncluttered and simple.
Future articles will just be cool things you can do with this, like how to combine this printing with conventional strings, how to compare streamed text with a quake string, printing floats to the screen, automatically creating boxes for variable text. It's gonna be fun...
Path_corner Enemies
#1017 posted by ijed [200.73.66.2] on 2013/05/09 00:11:55
How can I get my enemies to do nothing on reaching the last path_corner?
Right now they begin walking towards the player (notarget or not) after ending the path.
I know I can just clean movetogoal or force idle instead of walk, but I'm pretty sure this has been fixed in other code bases, and by map hackery.
I've tried various ways of ending the path in editor, and nothing seems to work.
Relevant Code
#1018 posted by Preach [77.98.165.95] on 2013/05/09 01:04:01
self.goalentity = self.movetarget = find (world, targetname,
other.target);
self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
if (!self.movetarget)
{
self.pausetime = time + 999999;
self.th_stand ();
return;
}
So to get to the bit which makes the monster stop we need the find call to return world. The obvious move of not setting a target on the waypoint fails, because it's almost certain that the player's targetname is also "" (the empty string). This is why the monster instead starts walking towards the player.
So how can we make sure that find returns world instead? Well, find returns world if nothing matches. So what we do is choose a target is not equal to any targetname on the map, like "this is a made up targetname which nobody in their right mind would use".
Alternatively use Quoth and "wait" "-1".
I have to admit that before the post I had an misapprehension about find. I thought that the function looped around, so when it reached the last entity it would wrap around to world and back until it reached the start entity again. It doesn't do this - once it reach the end of the entity list it just stops and returns world.
Interesting
#1019 posted by ijed [200.73.66.2] on 2013/05/09 01:40:47
Thanks.
Does the player not having a null string targetname affect anything else like this?
Don't Think So
#1020 posted by Preach [77.98.165.95] on 2013/05/09 02:06:15
There's a single central function which handles how all entities get triggered - and that has a check for an empty target before it runs. The trigger_teleport_touch function also searches for a target, but the trigger_teleport spawn function throws an error if target is not set. Note that you could use a map-hack to get around the latter check, but it's a fairly useless effect.
#1021 posted by necros [99.227.215.224] on 2013/05/09 16:53:48
The obvious move of not setting a target on the waypoint fails, because it's almost certain that the player's targetname is also "" (the empty string).
...oh my god... all these years, I just assumed that that functionality was broken! that's why I added the wait -1 into quoth! I never saw the correlation between find matching an empty string.
I have to admit that before the post I had an misapprehension about find. I thought that the function looped around, so when it reached the last entity it would wrap around to world and back until it reached the start entity again. It doesn't do this - once it reach the end of the entity list it just stops and returns world.
This is also important to remember if you are using find when starting in the middle of the list. You need to remember to loop back around to the start of the list to cover all the entities you've missed by starting in the middle.
Blogosphere Time
#1022 posted by Preach [77.98.165.95] on 2013/05/10 21:59:05
So a new article for the blog, this time dealing with map hacks. I look at why one dodgy-looking part of a common map-hack technique is actually not so dodgy. It's also a chance to look at how floating point works. It's deceptively titled "Infinite ammo"
http://tomeofpreach.wordpress.com/...
For some more interesting reading on the issues of floating point maths I recommend Raymond Chan's recent post http://blogs.msdn.com/b/oldnewthin...
Demo Parsing Code
#1023 posted by Mandel [80.217.79.210] on 2013/05/17 20:45:49
For what it's worth, I've published my demo parsing code (a c library) at github:
git clone http://github.com/mandelmassa/libd...
Libdemo passes my test suite on x86 win32 and linux, arm, and mips, so it's theoretically endian safe. It should support standard and Fitzquake protocols but there has been minimal testing on fitz. I will accept patches but I'm new to github so I don't know for sure how it works. License is MIT - basically, do what you want with the code. I hope someone finds it useful.
The purpose of the code is to read a Quake .dem file from your file system into a linked list in memory. You can edit the linked list as you wish, and then use functions in the library to write the demo data to a file.
I've used older versions of this code to write numerous tools for Quake demo editing, mainly for QdQ movies.
Nextthink Time
#1024 posted by Qmaster [50.40.240.99] on 2013/05/21 22:09:35
Can I give nextthink a value of (time + FALSE); ?
#1025 posted by necros [99.227.215.224] on 2013/05/21 22:46:34
in qc (and c), 0 is false and all other numbers are true, so time + FALSE just means time + 0 or just time.
You can do this and it means that the think function will run on the very next frame.
Nextthink
#1026 posted by Preach [77.98.165.95] on 2013/05/21 22:54:43
On a normal entity you can do that, and even stuff like nextthink = 0.05 works as well. As long as nextthink is positive and before the server time + the current frame length, the function runs.
But since this comes after your posts about func_train, I'm guessing you want to do this on a MOVETYPE_PUSH/SOLID_BSP entity. The rules are much more complicated for them, you can read some of the detail from earlier in the thread:
http://celephais.net/board/view_th...
The most important thing is to always do self.think = self.ltime + delta, never compare to time directly, or you won't get the correct behaviour when your entity is blocked. delta must be strictly greater than zero, or the think function won't run.
If you need reliable, frame by frame functions to run on your MOVETYPE_PUSH entities, I'd recommend adding a function to startframe which finds all the relevant entities by classname and run your function on them there. You get the added bonus of getting to run the "think" function before they get physics run this frame.
#1027 posted by necros [99.227.215.224] on 2013/05/21 23:36:09
oh whoops, I missed that it was for a bsp entity.
Well, I'm Guessing
#1028 posted by Preach [77.98.165.95] on 2013/05/22 00:18:45
Because who would think to qualify their question like that, unless they already knew?
What it has reminded me is that I must find that old article I wrote about making smoothly-moving pusher entities, rewrite the bits that were hard to follow, and post it to the new blog. That and maybe some posts which condense all that info linked about about what physics happen when and why...it's all a bit random, needs a bit of a hook to hang it from.
Trying To Make A Perfectly Smooth Custom Func_train
#1029 posted by Qmaster [50.40.234.181] on 2013/05/22 05:31:16
No matter what I set delta to when I add it to ltime, it always stops for 0.100 secs at path corners. I've created all new functions just for my train entity so I can isolate what is going on.
I have only changed names and ltime increment values from 0.100 to 0.0001 (overkill precision for 1500fps (1/1500). 1500fps is the highest I've seen in Darpkplaces with my nose to the wall ;) ). I'm trying to get it to just go straight to the next frame without pausing.
In my SUB_CalcMove replacement there is a line:
self.nextthink = (self.ltime + traveltime);
This is the same as in subs.qc. Is this the culprit?
Oh And Also
#1030 posted by Qmaster [50.40.234.181] on 2013/05/22 05:33:20
I don't care about physics or player blocking. These trains are part of the environment and can be SOLID_NOT if that helps (crazy stuff if I just change it to SOLID_NOT though :D ).
Quick Check
#1031 posted by Preach [77.98.165.95] on 2013/05/22 09:36:04
Have you removed the following
if (traveltime < 0.1)
{
self.velocity = '0 0 0';
self.nextthink = self.ltime + 0.1;
return;
}
That sets 0.1 seconds as the minimum travel time.
Also yes, do change your entity to non solid with MOVETYPE_NOCLIP and you will have a much easier time. SOLID_BSP entities are hard!
Finally, you may not need to set think functions at all if you want an instant reaction. Why not just run the function directly?
Technoplats.qc - Not Sure What's Wrong
#1032 posted by Qmaster [50.40.234.181] on 2013/05/22 15:18:56
//Func_train Alternate: Goal is to have no 0.100sec
//stops at path_corners (unless wait is set)
//================================...
//by Qmaster for techno mod, specific for my base map
//Current issues:
//Train flickers back and forth along path.
// Apparently the train is moving along the path
// at the speed of frame instead of time
//Predecs
//-------
void () traintech_next;
void () traintech_find;
void () traintech_move_done;
//My version of SUB_CalcMove (see subs.qc line 142)
//--------------------------------...
void (vector tdest, float tspeed, void() func) techtrain_move = {
local vector vdestdelta;
local float len;
local float traveltime;
self.think1 = func;
self.finaldest = tdest;
self.think = traintech_move_done;
//Does self.origin ever reach tdest? Should I do something like len below
// and do "if ( (len < 0.001) )" for this if statment to work?
if ( (tdest == self.origin) ) {
traintech_move_done();
}
vdestdelta = (tdest - self.origin); //get vector dist to target
len = vlen (vdestdelta); //floatify distance to target
traveltime = (len / tspeed);
self.nextthink = (time + traveltime);
self.velocity = (vdestdelta * (TRUE / traveltime));
};
//My version of SUB_CalcMoveDone (see subs.qc, line 178)
//--------------------------------...
void () traintech_move_done = {
setorigin (self,self.finaldest);
self.velocity = VEC_ORIGIN;
self.nextthink = CONTENT_EMPTY;
if ( self.think1 ) {
self.think1 ();
}
};
//Targetname is set
//-----------------
void () traintech_use = {
if ( (self.think != traintech_find) ) {
return ;
}
traintech_next ();
};
//Path_corner has wait set, so wait
//--------------------------------...
void () traintech_wait = {
if ( self.wait ) {
self.nextthink = (time + self.wait);
sound (self,CHAN_VOICE,self.noise,TRUE,A...
} else {
train_next();
}
};
//Find next path_corner from self.target
//--------------------------------...
void () traintech_next = {
local entity targ;
targ = find (world,targetname,self.target);
self.target = targ.target;
if ( targ.speed ) {
self.speed = targ.speed;
}
if ( !self.target ) {
objerror ("traintech_next: no next target");
}
if ( targ.wait ) {
self.wait = targ.wait;
} else {
self.wait = FALSE;
}
sound (self,CHAN_VOICE,self.noise1,TRUE,...
techtrain_move ((targ.origin - self.mins),self.speed,train_wait);
};
//Find inital path_corner from self.target and setorigin to it
//--------------------------------...
void () traintech_find = {
local entity targ;
targ = find (world,targetname,self.target);
self.target = targ.target;
setorigin (self,(targ.origin - self.mins));
if ( !self.targetname ) {
self.nextthink = (time + 0.100);
self.think = traintech_next;
}
};
//The train entity
//-----------------
void () func_traintech = {
if ( !self.speed ) {
self.speed = 24.000;
}
if ( !self.target ) {
objerror ("func_traintech without a target");
}
if ( !self.noise ) {
self.noise = "misc/null.wav";
precache_sound ("misc/null.wav");
}
if ( !self.noise1 ) {
self.noise1 = "misc/null.wav";
precache_sound ("misc/null.wav");
}
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NOCLIP;
self.use = traintech_use;
self.classname = "train"; //I hope this doesn't set anything engine-side
setmodel (self,self.model);
setsize (self,self.mins,self.maxs);
setorigin (self,self.origin);
self.think = traintech_find; //Needed for use function
traintech_find();
};
Umm...
#1033 posted by Qmaster [50.40.244.117] on 2013/05/22 22:02:13
you can ignore that previous post. I'm implementing Custents trains (with tweaks of my own haha)
Text...or Should I Say Numbers
#1034 posted by Preach [77.98.165.95] on 2013/06/05 02:17:02
So BSP things take a back seat to another Text article:
http://tomeofpreach.wordpress.com/...
Today we take a look at printing out numbers, one of those things you just take for granted in normal string code. But it's really important if you're going to use this to display, say, a custom scorecard like Qonquer did using screen prints.
I actually tore through making this one, because I was excited about an esoteric application of it - creating strings like "*21" or "*87" with any number, using the text streaming. It turns out that the loopholes for turning text streams back into normal QC strings are smaller than I imagined - just a bit too hacky. So that got cut for now, but I've not totally give up hope of making it useful.
#1035 posted by necros [99.227.215.224] on 2013/06/05 04:40:45
just took a quick scan over the article... the expanding code box doesn't seem to work for me. it expands, but it's all on a single line (the <br /> tags are visible).
Grr, Wordpress
#1036 posted by Preach [77.98.165.95] on 2013/06/05 11:55:01
That was working a few days ago, and then the gremlins messed with it...fixed!
Setsize With Respect To Other Vectors
#1037 posted by Teknoskillz [75.68.212.63] on 2013/06/16 20:08:23
HI:
I know the only 3 valid sizes are point, shambler and player, but I am just experimenting with the superspike model, attempting to have it do things like ricochet off bsp's for example, and one problem was it was not pointing in the new direction after rebounding, but I found vectoangles of the velocity ought to fix it? However I was wondering how could we use that to look at the spikes angles, and do a setsize that takes us out of the models default point size, and give it a better bounding box, that is more shaped to the model? I did some rudimentary tests, and found that:
setsize (self, '1 -0.5 -0.5', '8 0.5 0.5');
(self being the spike)
Works , except if its first angle coordinate, which I am guessing is its yaw? ,Is the larger value of the 2? Im just curious if anyone here can see a formula where we could resize the bounding box depending on its angles , or maybe the velocity, so that its always covering the spike....?

Post A Reply:

Name: |
Title: |
Body: |

Website copyright © 2002-2013 John Fitzgibbons. All posts are copyright their respective authors.