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
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. 
Practical Problem 
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... 
 
create a lookup table that matches the old precache numbers to the new ones?

haha that would be painful to do. :P 
 
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: 
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! 
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 
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. 
 
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). 
Yes 
I noticed that, and wondered if that might have been a hint to whoever it was not to indulge in that kind of behaviour. For the record, using a mapobject_custom is a much easier way to get the model you want (they aren't static entities by default). That would only require the hacky version in quoth 1, and then using a designed entity for other versions will be forward compatible. 
Preach 
Rebalancing monsters IS breaking backwards compatibility!

(Aaaaahh change!) 
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.