|
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. |
|
|
Pretty Easy
#868 posted by Preach 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 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 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 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 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 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 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 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
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 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.gif
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 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 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 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 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 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/frmcount.jpg
#883 posted by necros 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 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/webarchive/minion.planetquake.gamespy.com/tutorial/speed.htm
More Stubborn Monster
#885 posted by madfox 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 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 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 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 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 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 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 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).
|
|
You must be logged in to post in this thread.
|
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.
|
|