#651 posted by necros 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 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 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 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 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 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 on 2012/01/01 22:09:49
i'd totally test this, except it's random. XD
Return Value
#658 posted by Preach 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 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 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 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 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 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 on 2012/01/02 22:05:16
excellent! everything is peachy, preachy!
Wow
you are a poet!
#666 posted by JneeraZ 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 JneeraZ 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 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 JneeraZ 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 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 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 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 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 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.00000000000000000000000000000000000000000000056051938;
And so on...wasn't there a compiler that let you define integer constants like %4.
HUD Text Shizzles
#675 posted by Kinn 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?
|