|
Posted by metlslime 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. |
|
|
There Is
#302 posted by ijed 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 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 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 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 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 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 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 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/rquake/trunk/source/rjs.qc?revision=68&view=markup
#310 posted by Lardarse 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 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 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 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 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 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 on 2009/10/04 20:01:10
great, thanks for the info. :)
#317 posted by necros 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 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 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 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 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 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 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 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 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 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?
|
|
You must be logged in to post in this thread.
|
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.
|
|