[Tutorial] Making Ogres lob grenades into your cover

Discuss programming in the QuakeC language.
Post Reply
Orion
Posts: 476
Joined: Fri Jan 12, 2007 6:32 pm
Location: Brazil

[Tutorial] Making Ogres lob grenades into your cover

Post by Orion »

Do you think the original Ogre isn't challenging enough? In this tutorial we'll make him shoot grenades at your cover, remember the Half-Life HECU soldiers? This code is strongly inspired on the soldier's AI.


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;
Attention!! This tutorial requires sqrt() function, I'll post a QuakeC sqrt() later, but if you want to run this on DarkPlaces, make sure you have dpextensions.qc, and put "dpextensions.qc" without quotes after "defs.qc", on your progs.src.

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;
};
This cute little function is a QC version of sqrt(), for those who don't want to use DarkPlaces. It's slow, but has little margin of error.

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;
}
This merely checks if 'n' is negative (less than zero), where 'n' is any number called to that function, like sqrt(81) will return 9. If 'n' is negative, it will print a developer message (only seen if developer is set to 1) and return -1, because a negative square root is impossible.

Then later we have:

Code: Select all

if (n == 0 || n == 1)
	return n;
This checks if 'n' is exactly 0 or 1, if so, will return itself.
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;
First, we declare a minimum divisor (2), and the result, is 'n' divided by that number. Then, a while() loop begins. If we can reach our square root in less than 100 tries, then we're done. Otherwise, we mayn't reach our exact square root in 100 tries, but we'll quit to avoid runaway loop errors.

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;
};
Explanations are all commented within the code...


Step 4.

Just before ai_run(), put this line:

Code: Select all

void() ogre_gren1;
This will declare here the ogre's lob animation, which we will create later in ogre.qc.

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;
Here the monster will check if he can see his enemy, and if so, search for him for 5 seconds. I want you to replace this:

Code: Select all

if (enemy_vis)
	self.search_time = time + 5;
With this:

Code: Select all

if (enemy_vis)
{
        self.last_enemy_origin = self.enemy.origin;
	self.search_time = time + 5;
}
This single line we added, will make the monster update self.last_enemy_origin every time he can see his enemy.


Step 5.

Now before these lines:

Code: Select all

if (CheckAnyAttack ())
	return;					// beginning an attack
Add this:

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;
	}
}
In English, this if() statement says:
"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);
};
This is merely a copy of OgreFireGrenade(), we called it OgreFireGrenade2(), notice this line:

Code: Select all

missile.velocity = self.grenade_velocity;
This will make the grenade's velocity to be that CheckToss() returned, to reach your cover.


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();};
After that whole code, paste this:

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();};
ai_facetoss() is a new function, which will make the ogre face the direction the grenade is launched, using vectoyaw(self.origin + self.grenade_velocity * 64);
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. :)
gnounc
Posts: 428
Joined: Mon Apr 06, 2009 6:26 am

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by gnounc »

Its a bit of a long post, and i havent had a day off to look at it with rested eyes yet, but i DID want to make sure you knew this post didnt go to waste. thanks for posting it, i'll be taking a look at it : )
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by qbism »

Would be interesting to compare this to bot grenade code posted in this thread - http://forums.inside3d.com/viewtopic.ph ... 7&start=60
Dr. Shadowborg
InsideQC Staff
Posts: 1120
Joined: Sat Oct 16, 2004 3:34 pm

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Dr. Shadowborg »

This benchpresses what I did in there.

And unfortunatly, just created a nice big stack more work for me to revamp everything with some adaptations on this. :shock:

On the upside, Orion just made the credits list of Smash and FBX+1 or whatever it ends up getting called.
Cobalt
Posts: 445
Joined: Wed Jun 10, 2009 2:58 am
Location: New England, USA
Contact:

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Cobalt »

I just put it into a coop mod I am doing w DP. Compiles ok but I didnt notice any better aim from the ogres grenades when I tested. Does the skill have to be
set to a certain value?
Dr. Shadowborg
InsideQC Staff
Posts: 1120
Joined: Sat Oct 16, 2004 3:34 pm

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Dr. Shadowborg »

Cobalt wrote:I just put it into a coop mod I am doing w DP. Compiles ok but I didnt notice any better aim from the ogres grenades when I tested. Does the skill have to be
set to a certain value?
Looking at the code, your skill needs to be higher than 0, and you need to be not visible to the ogre (plus no other monsters within the blast radius) before anything happens.

Also, this doesn't do anything for regular ogre grenades.

Just as a follow up on my use of this: I ended up not using this in the FBX update I'm working on, there were too many tracelines involved with the function to be called every frame.
Cobalt
Posts: 445
Joined: Wed Jun 10, 2009 2:58 am
Location: New England, USA
Contact:

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Cobalt »

Oh ok. I played it some more and yea it did seem to happen when something like a pillar was between me and the ogre.

Interesting the ogres never run out of grenades, but the pack they drop always has 2 , lol.

Also, if you are in a higher elevation than the ogre, he does not seem to aim up any higher. Theres probably some bot code , maybe the one mentioned in the other link that would be an upgrade here as well.
Dr. Shadowborg wrote:
Cobalt wrote:I just put it into a coop mod I am doing w DP. Compiles ok but I didnt notice any better aim from the ogres grenades when I tested. Does the skill have to be
set to a certain value?
Looking at the code, your skill needs to be higher than 0, and you need to be not visible to the ogre (plus no other monsters within the blast radius) before anything happens.

Also, this doesn't do anything for regular ogre grenades.

Just as a follow up on my use of this: I ended up not using this in the FBX update I'm working on, there were too many tracelines involved with the function to be called every frame.
Orion
Posts: 476
Joined: Fri Jan 12, 2007 6:32 pm
Location: Brazil

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Orion »

Cobalt wrote:Also, if you are in a higher elevation than the ogre, he does not seem to aim up any higher. Theres probably some bot code , maybe the one mentioned in the other link that would be an upgrade here as well.
The ogre will randomly choose to lob a grenade either where he last saw you, or where you are. He doesn't always know exactly where you are when he can't see you, so he'll try to toss elsewhere. :wink:
ratbert
Posts: 37
Joined: Thu Nov 19, 2009 3:47 pm

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by ratbert »

You maybe want to play with something new on grenade related things. Code is based off original ogre grenade throwing code with some Patrick Martin aiming code thrown in and some ideas I added in to it make it more interesting. You see references in the function names starting with "potty" which is for a porta potty monster. I think the mod was called "Burnman" quake mod (close to that name) that had a bunch of wacky monster replacements for original quake monsters. There a was a porta-potty monster that was direct replacement for the quake ogre.

I added an some code so when the porta-potty monster door opens it throws out crap at you (toilet seat, play head or gibs) using modified ogre grenade throwing function with the added Patrick Martin aiming code for better aim. So it does work pretty well for what it is worth and does give the monster better aim. Now thing is the grenade has two possible ways of working either lobbed at you directly or has a semi bouncing/homing effect that tries to follow you around to get you for a short period before explodes. Semi "bouncing/homing" effect I took from some rocket homing code functions and adapted so it was not so precise but it does the job anyhow. It would not be that hard to adapt for the quake ogre to use if anyone is interested in it. Controlling number of bounces/homing counter is in function PottyBounceGrenadeTouch using variable self.count.

I do same Semi "bouncing/homing" effect code adapted for Ogre when they throw Nuke Grenade (aka big nuke bomb) at you. It so fun trying to out run or dodge one them dang things.




Code Part
----------------------------------------------

//--------------------------------------------------------------------------
// This returns the z-velocity added to a monster's grenade based on
// the distance between attacker and target and on gravity.
//--------------------------------------------------------------------------
float(float dist) Monster_GrenadeFly =
{
local float g; // Gravity -- g = 1 is Earth gravity.
local float base; // This dictates how fast the projectile
// will fly up when it is launched.

//------------------------------------------------------//
// 800 is normal (Earth) gravity. 800 * 0.00125 = 1. //
//------------------------------------------------------//
g = (cvar("sv_gravity")) * 0.00125; // Find gravity in terms of g.

if (dist > 900) base = 200+((dist - (200 - (dist * 0.2)))*0.5);
else if (dist > 700) base = 200+((dist - (200 - (dist * 0.15)))*0.5);
else if (dist > 500) base = 200+((dist - (200 - (dist * 0.1)))*0.5);
else if (dist > 300) base = 200+((dist - 200)*0.5);
else if (dist > 200) base = 200;
else if (dist > 100) base = dist;
else base = 100;

return (base * g);
};

void() PottyGrenadeExplode2 =
{
T_RadiusDamage (self, self.owner, 160 , world);

WriteByte (#MSG_MULTICAST, #SVC_TEMPENTITY);
WriteByte (#MSG_MULTICAST, #TE_EXPLOSION);
WriteCoord (#MSG_MULTICAST, self.origin_x);
WriteCoord (#MSG_MULTICAST, self.origin_y);
WriteCoord (#MSG_MULTICAST, self.origin_z);
multicast (self.origin, #MULTICAST_PHS);

remove (self);
};

void() PottyGrenadeTouch2 =
{
if (other == self.owner)
return; // don't explode on owner
if (other.takedamage == #DAMAGE_AIM)
{
PottyGrenadeExplode2();
return;
}
sound (self, #CHAN_VOICE, "potty/potbounce.wav", 1, #ATTN_NORM); // bounce sound
if (self.velocity == '0 0 0')
self.avelocity = '0 0 0';
};

void() PottyBounceGrenadeThink =
{
self.angles = vectoangles(self.velocity);
self.nextthink = time + 0.1;
};

void() PottyBounceGrenadeSeek =
{
self.flags = self.flags - (self.flags & #FL_ONGROUND);
setorigin(self, (self.origin + '0 0 2'));
self.velocity = normalize((self.enemy.origin + self.enemy.view_ofs) - self.origin);
self.velocity = self.velocity * 600;
self.velocity_z = self.velocity_z + vlen(self.enemy.origin - self.origin);
self.angles = vectoangles(self.velocity);
self.nextthink = time + 0.01;
self.think = PottyBounceGrenadeThink;

};


void() PottyBounceGrenadeTouch =
{
if (other.health)
{
PottyGrenadeExplode2();
return;
}
if (self.count >= 11)
{
PottyGrenadeExplode2();
return;
}

sound (self, #CHAN_VOICE, "potty/potbounce.wav", 1, #ATTN_NORM); // bounce sound

self.count = self.count + 1;
self.think = PottyBounceGrenadeSeek;
self.nextthink = time + 0.01;

};

void() PottyFireGrenade =
{
local entity missile;
local float whichpart;
local float rnumwhich;
//-------------------------------------------------------- New Code --------
local float dist; // Distance between attacker and target.
local float fly; // This dictates how fast the projectile
// will fly up when it is launched.
local float airtime; // Approximate time for grenade to
// reach target.
local vector move; // xy-velocity of target.
local vector spot; // Spot where grenade will land with leading.
local vector org; // Location where grenade is spawned.
local float missilespeed;
//--------------------------------------------------------------------------

muzzleflash ();
SuperDamageSound();
sound (self, #CHAN_WEAPON, "weapons/grenade.wav", 1, #ATTN_NORM);

rnumwhich = random();
if (rnumwhich > 0.6)
monster_deathattack_messagev2(" got flushed by a", self.netname, "");
else if (rnumwhich > 0.3)
monster_deathattack_messagev2(" got the crap kicked out of them by a ", self.netname, "");
else
monster_deathattack_messagev2(" pissed off the wrong", self.netname, "");

missilespeed = 600;

missile = spawn ();
missile.owner = self;
missile.movetype = #MOVETYPE_BOUNCE;
missile.solid = #SOLID_BBOX;

// set missile speed

makevectors (self.angles);

//-------------------------------------------------------- New Code --------
org = self.origin + v_forward * 32 + v_right * -6 + '0 0 16';

dist = vlen(self.enemy.origin - org);

airtime = dist / missilespeed;
move = self.enemy.velocity;
move_z = 0;
spot = self.enemy.origin + airtime * move;

//-----------------------------------------------------------//
// Since grenades are affected by gravity, make adjustments //
// based on the anticipated location of attacker's target. //
//-----------------------------------------------------------//
if (vlen(spot - org) < dist)
spot = self.enemy.origin + airtime * move * 0.7;
else if (vlen(spot - org) > dist)
spot = self.enemy.origin + airtime * move * 1.2;

if (skill > 1)
// Use this for complete tracking.
missile.velocity = normalize (spot - org);
else
// Use this for distance tracking only.
missile.velocity = normalize (self.enemy.origin - org);
//--------------------------------------------------------------------------
missile.velocity = missile.velocity * missilespeed;
//-------------------------------------------------------- New Code --------
dist = vlen(spot - org);
//--------------------------------------------------------------//
// The monster will lob a projectile that is guaranteed to hit //
// a stationary target within a radius of 1000. //
//--------------------------------------------------------------//
fly = Monster_GrenadeFly(dist);

missile.velocity_z = (missile.velocity_z + fly);
//---------------------------------------------------------------- END -----

missile.avelocity = '300 300 300';
missile.angles = vectoangles(missile.velocity);


if (random() > 0.50)
{
missile.touch = PottyBounceGrenadeTouch;
missile.nextthink = time + 0.1;
missile.think = PottyBounceGrenadeThink;
missile.count = 0;
}
else
{
missile.nextthink = time + 2.5;
missile.think = PottyGrenadeExplode2;
missile.touch = PottyGrenadeTouch2;
}


whichpart = random();
if (whichpart > 0.66)
setmodel (missile, "progs/h_ppotty.mdl");
else if (whichpart > 0.33)
setmodel (missile, "progs/h_player.mdl");
else
setmodel (missile, "progs/gib3.mdl");


setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, org);

};
Cobalt
Posts: 445
Joined: Wed Jun 10, 2009 2:58 am
Location: New England, USA
Contact:

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Cobalt »

Me and Primallove have been experimenting with this in our Creepcam2 mod for DP...Codename: "Creepiercam" :)

We just recently added "axis and Leading Shot" enhancements to the Zombies missile and also to the Orgres regular grenade chick firing.

Seems to be an improvement however still seeing cases perhaps at extreme differences in height of target where they are overcalculating the lob. so if there is perhaps a bridge in the path of the lob, they will just keep chucking it there over and over. Likewise if you are in a cave, the lob hits the top enterance of the cave over and over.

Am surprised tho , they can chuck very good through grated window openings !

Also, we were talking aboiut the fact Ogres have a never ending supply of grenades, yet the backpack always has exactly (2) :)

(Anyone every ask Romero whats goin on here? :) )

So we were thinking perhaps assign .ammo_rockets to the ogres depending on skill level, perhaps:

Skill 0 = 10 , Skill 1 = 15, skill 2 = 25 , skill 3 = 35 ?

Other option is like a recharge timer so even if they run out , and a certain amount of time passes their supply starts to recharge slowly....
Dr. Shadowborg
InsideQC Staff
Posts: 1120
Joined: Sat Oct 16, 2004 3:34 pm

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Dr. Shadowborg »

Cobalt wrote: Also, we were talking aboiut the fact Ogres have a never ending supply of grenades, yet the backpack always has exactly (2) :)

(Anyone every ask Romero whats goin on here? :) )

So we were thinking perhaps assign .ammo_rockets to the ogres depending on skill level, perhaps:

Skill 0 = 10 , Skill 1 = 15, skill 2 = 25 , skill 3 = 35 ?

Other option is like a recharge timer so even if they run out , and a certain amount of time passes their supply starts to recharge slowly....
Purely a gameplay dynamic I should think. Keep in mind that ogre grenades are substantially weaker than player grenades too.

Making them run out of grenades essentially means making them extremely vulnerable to the player after they run out (melee only, and they don't move that fast, plus they can't normally jump off of ledges.)

Skill Level ammo supply generates a whole host of gameplay issues, namely rockets end up becoming as common as shells are.

Recharging their supply causes the same logic problems too. You could give them a backup weapon like a blaster, but given how primitive ogres look... And they don't exactly look all that magical either, so magic blasters are kinda out as well.

This could of course be solved by adding a blaster that the player could use to quake (playing rubicon 2 would clearly demonstrate why this is a good idea, given how scarce the thunderbolt is in that mod, and how common cells from enforcers become :P ), then you could explain it away by how they even have chainsaws and grenades to begin with. (I never got the whole enforcers have built-in blasters, from their model they clearly don't have them integrated into their arm the way Stroggs do.)
Cobalt
Posts: 445
Joined: Wed Jun 10, 2009 2:58 am
Location: New England, USA
Contact:

Re: [Tutorial] Making Ogres lob grenades into your cover

Post by Cobalt »

True, the ogre would make more sense with a sling or a bow and arrow.

Im pretty sure the chainsaw got in there from that movie: Army of Darkness. Same for the "boomstick" reference on the shotgun kills.

As for grenades it may have had to do with an older Dungeons and Dragons module where there was a possible tie in of the "Middle Earth" into the future
through time portals. It would also explain the slipgates.


Dr. Shadowborg wrote:
Cobalt wrote: This could of course be solved by adding a blaster that the player could use to quake (playing rubicon 2 would clearly demonstrate why this is a good idea, given how scarce the thunderbolt is in that mod, and how common cells from enforcers become :P ), then you could explain it away by how they even have chainsaws and grenades to begin with. (I never got the whole enforcers have built-in blasters, from their model they clearly don't have them integrated into their arm the way Stroggs do.)
Post Reply