News | Forum | People | FAQ | Links | Search | Register | Log in
Coding Help
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 | Previous | Next | Last
 
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. 
 
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... 
 
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. 
 
hm... Maybe when you're standing on a bmodel that's moving? ie: train or lift 
 
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. 
 
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 
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 
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. 
 
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 
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 
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. 
 
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. 
 
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. 
 
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 
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 
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. 
 
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. 
 
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 
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. 
 
I don't get any messages in game although I have the bprint in calculated.
protocol 666? 
Falling Through 
It looks as if the standing on ground and shallow enough to stand cross over each other.

http://members.home.nl/gimli/frmcount.jpg 
 
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 
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 
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. 
First | Previous | Next | Last
You must be logged in to post in this thread.
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.