Step 1.
First, you'll open defs.qc and put these lines at the very bottom:
Code: Select all
.float grenade_time;
.vector last_enemy_origin, grenade_velocity;
These lines will define the fields used by the Ogre, a dot float and two dot vectors. Any definition beginning with a dot (.) is called a field, which is a float, vector, entity assigned to another entity, like self.ammo_shells -- gets the quantity of shells of the player.
grenade_time will define how often the ogre will check for shooting grenades at your cover, as well as how often he actually shoots grenades at you.
last_enemy_origin will be the point where the ogre has last seen you.
grenade_velocity will be the proper grenade's velocity for reaching your cover.
Step 2.
Save and close defs.qc, and open ai.qc. Before ai_run(), paste this code (Warning! ignore this step if you're using DarkPlaces!):
Code: Select all
float(float n) sqrt =
{
local float divisor, result, i;
if (n < 0)
{
dprint ("Negative sqrt is impossible.\n");
return -1;
}
if (n == 0 || n == 1)
return n;
divisor = 2;
result = n / divisor;
while (divisor != result && i < 100)
{
divisor = (divisor + result) / 2;
result = n / divisor;
i = i + 1;
}
return result;
};
Now to explain it:
First we declared 3 local floats. A local float is a float only used on a specific function. Then we have:
Code: Select all
if (n < 0)
{
dprint ("Negative sqrt is impossible.\n");
return -1;
}
Then later we have:
Code: Select all
if (n == 0 || n == 1)
return n;
Now we have the main part of the function:
Code: Select all
divisor = 2;
result = n / divisor;
while (divisor != result && i < 100)
{
divisor = (divisor + result) / 2;
result = n / divisor;
i = i + 1;
}
return result;
Inside that while(), the divisor will calculate the average value between the previos divisor and result, and then we divide 'n' by that new divisor, and finally we add 1 to 'i', which is the number of tries. And the cycle repeats until we get the square root.
Step 3.
Ignore step 2 if you're using DarkPlaces. Just before ai_run() (and after the QC sqrt()) paste this:
Code: Select all
float(vector org, float radius) MonsterInRange =
{
local entity head;
local float yes;
head = findradius(org, radius); // finds entities wihthin a specified radius
while (head) // while 'head' is not world...
{
if (head.health > 0) // it must be alive
if (head.flags & FL_MONSTER) // must be a monster
if (head != self.enemy) // must not be the ogre's enemy (he may get mad at another monster...
{
traceline (org, head.origin + head.view_ofs, TRUE, self); // see if he can 'see' his enemy from his grenade launcher instead of his eyes
if (trace_fraction == 1.0) // trace_fraction of 1 means he can see it
yes = TRUE; // set yes to TRUE
}
head = head.chain; // go for next entity in the chain
}
return yes; // returns 'yes' value, TRUE or FALSE
};
float() crandom; // crandom() is declared on weapons.qc, which is compiled after ai.qc, so we have to redeclare it here
vector(entity e, vector spot1, vector spot2) CheckToss =
{
local vector midpoint, apex, vel;
local float grav, dist1, dist2, time1, time2, timeinv;
grav = cvar("sv_gravity"); // get sv_gravity value and store it on a float
if (spot2_z - spot1_z > 500)
return '0 0 0'; // if the enemy is too high above the ogre, do nothing, as the grenade can't reach there, and return '0 0 0'
makevectors (e.angles); // gets forward, right and up vectors for assigned entitty (e)
// these two lines will spread a little bit to left-right and forward-back the position where the grenade is launched. crandom() returns between -1 to 1,
// and here we multiply it by 8 and 16
spot2 = spot2 + v_right * (crandom()*8 + crandom()*16);
spot2 = spot2 + v_forward * (crandom()*8 + crandom()*16);
midpoint = spot1 + (spot2 - spot1) * 0.5; // detects midpoint of grenade trajectory with a simple calculation
traceline (midpoint, midpoint + '0 0 500', TRUE, e); // traces a line 500 units above the midpoint, ignoring monsters and other entities, and the ogre
// himself
midpoint = trace_endpos; // set midpoint to the end position of the traceline() (may be a ceiling)
midpoint_z = midpoint_z - 15; // subtract midpoint down to 15 units
if (midpoint_z < spot1_z || midpoint_z < spot2_z)
return '0 0 0'; // if the midpoint height (z means up-down value) is lower than 'spot1' (the ogre's origin) OR lower than 'spot2'
// (the ogre's enemy origin), then don't launch a grenade
dist1 = midpoint_z - spot1_z; // get distance between midpoint and ogre
dist2 = midpoint_z - spot2_z; // get distance between midpoint and the enemy (usually the player)
time1 = sqrt(dist1 / (0.5 * grav)); // calculate time it takes for grenade to reach the ogre (in seconds), use sqrt of dist1, divided by sv_gravity
// multiplied by half
time2 = sqrt(dist2 / (0.5 * grav)); // same as above, but this gets time to reach the enemy
if (time1 < 0.1)
return '0 0 0'; // if it takes less than a tenth of a second from midpoint to the ogre, don't shoot
timeinv = 1 / (time1 + time2); // inverts time1 + time2 (not negative)
vel = (spot2 - spot1) * timeinv; // calculates velocity based on distance from the enemy to the ogre, multiplied by the inverted time
vel_z = grav * time1; // calculate upwards velocity of the grenade, which is sv_gravity multiplied by the time it takes to reach from the ogre
// to the midpoint
apex = spot1 + vel * time1; // now, we calculate the apex of the shot, adding the ogre's origin to the grenade's velocity and multiplying by the time
// it takes to reach the midpoint from the ogre
apex_z = midpoint_z; // the apex's height is the same as the midpoint height, so the grenade will hit the ceiling as less as possible
traceline (spot1, apex, FALSE, e); // trace a line between the ogre and the apex, and don't ignore monsters
if (trace_fraction != 1.0)
return '0 0 0'; // if there's something blocking the grenade's path, don't shoot!
traceline (spot2, apex, TRUE, e); // trace a line between the ogre's enemy and the apex, but ignore monsters
if (trace_fraction != 1.0)
return '0 0 0'; // if there's a wall blocking the enemy's path, don't shoot!
return vel; // return the grenade's velocity
};
float(vector src) CheckThrowGrenade =
{
local vector vec, dist2d, toss;
if (time < self.grenade_time)
return FALSE; // if ogre has previously checked or lobbed a grenade, do nothing and return FALSE
if (!(self.enemy.flags & FL_ONGROUND) && self.last_enemy_origin_z > self.absmax_z)
return FALSE; // if the enemy is not on the ground, and the point where we last seen our enemy is above the top of the ogre's bounding box,
// do nothing
if (random() <= 0.5) // find feet, 50% chance
{ // magically know where they are
vec = self.enemy.origin; // this will throw a grenade at the enemy's feet
vec_z = self.enemy.absmin_z;
}
else
vec = self.last_enemy_origin; // toss it to where you last saw them
if (MonsterInRange(vec, 256)) // if there's another monster nearby, don't shoot!
{
self.grenade_time = time + 1; // check again in 1 second
return FALSE;
}
dist2d = vec - src; // calculate distance...
dist2d_z = 0; // ...without regarding height
if (vlen(dist2d) <= 256) // if length is less than or equal to 256 units...
{
self.grenade_time = time + 1; // again delay 1 second, do nothing after
return FALSE;
}
if (vlen(dist2d) > 1000) // if 2d distance is over 1000 units...
{
self.grenade_time = time + 1; // same as above, too far away
return FALSE;
}
toss = CheckToss(self, src, vec); // call CheckToss()... for the ogre, the ogre's origin (src), and the enemy's origin (vec)
if (toss != '0 0 0') // if 'toss' doesn't return '0 0 0'...
{
self.grenade_velocity = toss; // assing self.grenade_velocity to returned 'toss' vector
self.grenade_time = time + 0.3; // check again in the next third of a second
return TRUE; // return TRUE, this menas he can shoot a grenade at your cover!
}
self.grenade_time = time + 1; // if all above fails, delay 1 second, return FALSE, meaning it can't shoot :(
return FALSE;
};
Step 4.
Just before ai_run(), put this line:
Code: Select all
void() ogre_gren1;
Now, scroll down to ai_run(), until you see this:
Code: Select all
enemy_vis = visible(self.enemy);
if (enemy_vis)
self.search_time = time + 5;
Code: Select all
if (enemy_vis)
self.search_time = time + 5;
Code: Select all
if (enemy_vis)
{
self.last_enemy_origin = self.enemy.origin;
self.search_time = time + 5;
}
Step 5.
Now before these lines:
Code: Select all
if (CheckAnyAttack ())
return; // beginning an attack
Code: Select all
if (!enemy_vis && (self.classname == "monster_ogre" || self.classname == "monster_ogre_marksman") && skill > 0)
{
if (CheckThrowGrenade(self.origin))
{
self.grenade_time = time + 6;
ogre_gren1 ();
return;
}
}
"If I can't see my enemy, and I'm an Ogre OR and Ogre Marksman, and the difficulty level is more than Easy (skill 0), then I will check if I can lob a grenade into his sorry ass, and if I can successfully lob one, I will check for it again in 6 seconds, call my shoot animation, and do nothing after this."
Step 6.
Save and close ai.qc, and now open ogre.qc. After OgreFireGrenade(), I want you to paste this:
Code: Select all
void() OgreFireGrenade2 =
{
local entity missile;
self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_BOUNCE;
missile.solid = SOLID_BBOX;
// set missile speed
makevectors (self.angles);
missile.velocity = self.grenade_velocity;
missile.avelocity = '300 300 300';
missile.angles = vectoangles(missile.velocity);
missile.touch = GrenadeTouch;
// set missile duration
missile.nextthink = time + 2.5;
missile.think = GrenadeExplode;
setmodel (missile, "progs/grenade.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin);
};
Code: Select all
missile.velocity = self.grenade_velocity;
Step 7.
Scroll down a little more, and you'll find this:
Code: Select all
void() ogre_nail1 =[ $shoot1, ogre_nail2 ] {ai_face();};
void() ogre_nail2 =[ $shoot2, ogre_nail3 ] {ai_face();};
void() ogre_nail3 =[ $shoot2, ogre_nail4 ] {ai_face();};
void() ogre_nail4 =[ $shoot3, ogre_nail5 ] {ai_face();OgreFireGrenade();};
void() ogre_nail5 =[ $shoot4, ogre_nail6 ] {ai_face();};
void() ogre_nail6 =[ $shoot5, ogre_nail7 ] {ai_face();};
void() ogre_nail7 =[ $shoot6, ogre_run1 ] {ai_face();};
Code: Select all
void() ai_facetoss =
{
self.ideal_yaw = vectoyaw(self.origin + self.grenade_velocity * 64);
ChangeYaw ();
};
void() ogre_gren1 =[ $shoot1, ogre_gren2 ] {ai_facetoss();};
void() ogre_gren2 =[ $shoot2, ogre_gren3 ] {ai_facetoss();};
void() ogre_gren3 =[ $shoot2, ogre_gren4 ] {ai_facetoss();};
void() ogre_gren4 =[ $shoot3, ogre_gren5 ] {ai_facetoss();OgreFireGrenade2();};
void() ogre_gren5 =[ $shoot4, ogre_gren6 ] {ai_facetoss();};
void() ogre_gren6 =[ $shoot5, ogre_gren7 ] {ai_facetoss();};
void() ogre_gren7 =[ $shoot6, ogre_run1 ] {ai_facetoss();};
vectoyaw() converts a vector into yaw angles only, that means angles_y. And we detect the direction the grenade will go by adding self.grenade_velocity to self.origin, and we multiply by 64 just in case it gets too low.
ChangeYaw() is a built-in function, which turns a monster towards self.ideal_yaw limited by self.yaw_speed.
Later we created ogre_gren1(), followed by the next animation frames. We call ai_facetoss() on all of them, and OgreFireGrenade2() (our new grenade launch function), on the fourth frame, which is exactly when he is shooting a grenade. Each animation frames lasts for a tenth of a second (0.1), unless if you set a nextthink higher or lower than 0.1 on each animation function. So, this animation lasts for 0.7 seconds, as it has 7 frames (shoot2 is repeated here, it actually has 6).
After that, you can save, compile, and run.