|
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. |
|
|
I Never Did Much In D3
#286 posted by bear 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 on 2009/07/14 04:54:32
*godly chorus sings*
i r wins
#288 posted by megaman on 2009/07/16 19:34:21
i see how a visual language makes debugging much easier :)
Walkmove Returns
#289 posted by Preach 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 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 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 on 2009/09/22 04:46:01
Gets cogs turning.
Ways to get the fiends more agile.
Preach:
#293 posted by metlslime 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 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 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 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 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 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 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 on 2009/09/23 20:03:05
To do this make the actual player entity an invisible movetype_noclip entity...
#301 posted by necros 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 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...)
|
|
You must be logged in to post in this thread.
|
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.
|
|