A week or so ago I saw a tutorial on Coffee's site called "Better Jumping". I read it, I read it again and then looked puzzled. Unfortunately that tutorial is incorrect! I'll go through it, not to embarass the author - we're all grateful someone submits tutorials - but to clarify a few points so no-one gets things wrong in the future. Here's that original tutorial, with my comments added around it:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void() check_for_ledge = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { local vector spot; if (!(self.flags & FL_ONGROUND)) // The bot can ONLY jump if he is on the ground return; // If he's not then don't jump makevectors (self.angles); // Jump the way he's a facin
spot = self.origin + (v_forward * 5); // Coffee put that at 60.. I think he was drunk
Now we have ...
spot = spot - '0 0 45'; // WOW he's checking WAY to deep for a ledge! Let's fix that! (Higher value = Less deep)
All the work for this tutorial takes place in Tutor.qc so open it up now and get ready for a long haul. First scroll down to jump_forward() and replace it with this. It's not much different, but it works the way I want it to.
// ----------------------------------------- void() jump_forward = // ----------------------------------------- { // propels him into the air if (!(self.flags & FL_ONGROUND)) return; //RiEvEr BETTER JUMPING FOR SURE self.flags = self.flags - (self.flags & FL_ONGROUND); makevectors(self.angles); self.velocity = self.velocity + (v_forward * 250); self.velocity_z = self.velocity_z + 250; //R BJFS };
All we've done is detach the bot from the ground and push it forwards and up at the player jump rate. That was the easy bit. Now scroll down to check_for_ledge() and get replace the whole ting with the two routines below. Just do it now and then find out what it does after you've finished pasting it.
//RiEvEr // BETTER JUMPING FOR SURE! // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ float() check_for_ledge = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Return values: // 0 = NOJUMP // 1 = JUMP { local float contents; local vector start, end; makevectors (self.angles); // movetogoal() will never move over a ledge, so we have to // check for a break in front of him and force him to jump if (!(self.flags & FL_ONGROUND)) return(0); // Where the bot's feet are now start = self.origin - '0 0 24'; end = start + v_forward *96; // Check 96 Quake units in front of the bot at foot height traceline (start, end, TRUE, self); // If there's a wall or obstacle, return NOJUMP if (trace_fraction != 1.0) return (0); // NOJUMP start = end; end = start - '0 0 256'; //256 units down // Now look down and see how far we drop and what's there (MAX 256 units) traceline (start, end, TRUE, self); contents = pointcontents (trace_endpos); // If it's LAVA, SLIME or a long way down and no ledge to jump to, return DANGER if ( (trace_fraction == 1.0) || (contents == CONTENT_LAVA) || (contents == CONTENT_SLIME) ) return (0); // NOJUMP else { bot_jump1(); //player jumping sound sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); return(1);// JUMP } return (0); // NOJUMP }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ float() bot_move_safely = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Return codes // 0 = DANGER // 1 = SAFE // 2 = JUMPING { local float contents; local vector start, end; if (!(self.flags & FL_ONGROUND)) return (1); // SAFE makevectors (self.angles); // Where the bot's waist is now start = self.origin; end = start + v_forward *48; // Check 48 Quake units in front of the bot at waist height traceline (start, end, TRUE, self); // If there's a wall or obstacle use that as our test point if (trace_fraction != 1.0) start = trace_endpos; else start = end; end = start - '0 0 280'; //256 units down + 24 units for bot waist height // Now look down and see how far we drop and what's there (MAX 256 units) traceline (start, end, TRUE, self); // If it's a short drop, just drop if( (trace_fraction * 280) < 48) return (1); // SAFE contents = pointcontents (trace_endpos); // If it's LAVA, SLIME or a long way down and no ledge to jump to, return DANGER if ( (trace_fraction == 1.0) || (contents == CONTENT_LAVA) || (contents == CONTENT_SLIME) ) { if ( check_for_ledge() ) return(2); // JUMPING else return (0); // DANGER } if ( check_for_ledge() ) return(2); // JUMPING // else return SAFE return (1); }; //R BJFS
Yes - we've discovered that QuakeC routines can return values like real C routines. Excellent! The second of those two functions checks for a safe landing spot, this will make it useful even if we call it from somewhere else in the code later. Normally this is only called when the bot is running with MoveToGoal action, but these additions mean that - if you think about it - you can make the bot jump during other parts of its movement routines too.
The function returns 3 values: DANGER, SAFE and JUMPING. You see how easy it is to tell another part of the program what you've done? The trace command is very powerful. It is the basis of all collision based movement routines and aiming routines. We are using it here for
and we've barely scratched the surface of what it can do. Check out some of the documentation on Inside3D to find out more about it. Now, scroll down to bot_walk and make the changes marked below:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void() bot_walk = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { local float status; //RiEvEr BETTER JUMPING FOR SURE // this is his main AI routine, where he will look for items and enemies if (!(self.flags & FL_ONGROUND)) return; if (should_rocket_jump()) return; //RiEvEr BETTER JUMPING FOR SURE status = bot_move_safely(); if( status == 0) // LAVA, SLIME, LONG DROP { // Can't carry on - DANGER // Need to change direction (90 degrees should do it) self.angles_y = self.angles_y + self.button1; makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_forward * 150; // slightly slower than normal return; } else if (status == 2) // JUMPING { return; } //R BJFS bot_look_for_bots(); bot_look_for_players(); bot_search_for_items(); bot_grab_items(); //RiEvEr - removed the check_for_ledges() line check_for_water(); // of course movetogoal() is id's C function, it moves randomly // toward what his self.goalentity is; don't let it worry you, // this function takes a long time to get where its going // the coffee_move is his cool running function if (self.goalentity != world) movetogoal(20); else coffee_move(); };
We've removed the ledge checking routine and call our own safety checking routine first. If it's not safe, we force the bot to go another direction, currently just turn 90 degrees. It would not be hard to improve on this direction change code.
Finally we change the jumping animation code. I like the rockatt2 frame for jumps, you may have other preferences so youi can change the frames in the first part marked with a dollar sign, but leave the rest alone.
//RiEvEr BETTER JUMPING FOR SURE void() bot_jump1 =[ $rockatt2, bot_jump2 ] {};// let the bot get off the ground first void() bot_jump2 =[ $rockatt2, bot_jump3 ] {jump_forward();}; void() bot_jump3 =[ $rockatt2, bot_jump4 ] {}; void() bot_jump4 =[ $rockatt2, bot_jump5 ] {}; void() bot_jump5 =[ $rockatt2, bot_walk1 ] {}; //R BJFS
That's it - they now jump when they get to a ledge, if it's safe to do so. Play with it, improve it, let me know in Coffee's forum what you did - or about any problems you have :)