AIRIAL LASERDROME WEAPON UNIT

 

 

philip
profile | email

posted 10-31-98 6:16 PM CT (US)
Title: AIRIAL LASERDROME WEAPON UNIT
Difficulty: Hard (as in stiff)
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net
Date: 10-31-98
Note: Please remember to give credit where credit is due.
======================================================

This is a tutorial which I put together which is somewhat
of an expansion of the principles of one of my previous
tutorials (gas grenade). I wanted for sometime to be able
to put together a weapon which railed the hell out of
any players within a certain radius and which floated
around in the air seeking victims. Well, introducing
the AIRIAL LASERDROME UNIT!!

PRELUDE:

You're in some map somewhere on a distant planet and you're
in a really bad situation. For some unknown reason, you
need to keep guard over a large area of the map with orders
to kill any players which would dare cross this territory.
But, nature calls and you want to be focusing your efforts
doing something other than targeting players for the next
minute or two. Well, what's a Marine to do? Ahaa!

The newly released Airial LaserDrone Weapon Unit..

So, you takes out your LaserDrone unit from your backpack
(about the size of a small TV) and you open the side case
and snap in your 50 powercells to power it up. Then the
LaserDrone launches off your hands as its anti-grav boosters
kick-in with a hissing sound.. Your new LaserDrome is off in
the air. A message flashes across your HUD warning you that
the drone will activate its targeting mechansim in 5 secs
and that you better get away! Not knowing exactly what to
expect, you dive for cover behind some big rocks!! Ahhh..
nice place to relieve yourself while your new LaserDrone is
protecting the area for you.. As you bear-down somewhat,
you can hear the LaserDrone's targeting gyro 'hummmm' into
action as the drones targeting mechanism goes ACTIVE!

As you squat, yet another message appears on your HUD informing
you that the LaserDrone has gone Active! Good! You think.. I'll
just squat here for a few more seconds.. But, your curiosity gets
the best of you. After all, you've never seen one of these babies
in action before! With your pants still around your ankles and
your pussy catching a fresh breeze, you rise a bit to peek your
head out around the sides of these rocks you've been squatting
behind (just to see what the drone is doing). WHAM!!! You get a
laser beam railed straight through your skull with such
force that parts of your body completely blow off. Guess
what, sister? YOU'RE DEAD!! Now I ask you, Did curiosity kill
this cat?

The LaserDrone model you were supplied with didn't differentiate
between the good guys and the bad guys. It's a pure killing
machine! JUST LIKE THIS ONE!!

ABOUT:

This is an entirely new weapon made completely from standard
issue Q2 components so nothing new is needed for the client.

Perhaps, somebody may want to take a crack at upgrading this
new weapon unit with some AI so that it'll move around after
players better..

WHAT IS IT:

It looks like a small TV set with one side panel open and a
relatively tall antenna sticking out of the top of it. This
standard issue model must be used somewhere in the Quake2
single-player gameplay but I don't recall where...

As it floats, it slowly rotates around scanning its horizon
over its pre-defined search radius for potential victims.
Once a victim has been targeted, the drone's antenna powers
up and, from near the top of its antenna, a green laser beam
blasts at the victim. The beam cuts across the air so fast
it leaves a bubble trail of vaporized gas in its wake! It hits
the victim with such force that the victim literally explodes
into a spray of blood and body parts. Then, like nothing ever
happened, it slowly rotates around some more moving gently to
the left and right randomly seeking out another victim. This
is an Awesome weapon!!

HOW IT OPERATES:

To launch a LaserDrone, the player must have at least 50
powercells in their inventory. Or, they can power up the
LaserDrone at a cost of 5 frags from their score because
some maps don't have any powercells at all. And NO! you
can't go into negative frags.. So, either 5 frags or 50
powercells will activate your LaserDrone. You hit your
aliased key and it launches gently off above your head.
You'll hear a hissing sound as its anti-grav boosters kick in.

Once launched, the drone will float gently upward at a very
slow pace. A message appears on your HUD letting you know
that the Drone has been launched and that you have 5 seconds
to get the hell away. The drone's targeting mechanism is
deactivated for the first 5 seconds of launch to give its
owner a fighting chance to run for cover! After the 5 secs,
its targeting mechanism will powerup with a small click and
a humming noise and the drown will begin to slowly rotate
around and around while it hovers in the air as it scans its
pre-defined radius for any living creature! You'll have a
total of 60 seconds of drone seek-and-destroy time after
its targeting mechanism has activated. And YES! you'll be
racking up all those frags it gets during its 60 sec lifespan.

Also, for the first 5 seconds after launch (before the drone
self-activates its targeting mechanism) the drone is immune
to damage. So, for the first 5 seconds the drone cannot be
killed! It can only start receiving damage once it's targeting
mechanism is activated. This is to give the drone a chance to
get away from it's owner and to only be able to receive damage
once it can adequately defend itself. Only fair! You'll know
the drone has turned on its targeting mechanism because the drone
will start to rotate around and around and there is a noise played
too (but you may be out of range to hear it). If the drown is
rotating then it can kill you! Or, it can be killed! So...
Hey! Hey! ... Let's be careful out there!!

If you hear your brass balls clanking or your panties are starting
to moisten-up because you're feeling quick on the draw, then by all
means go ahead and take a shot at the drone! If you kill the drone
its worth 5 frags to you!! But be aware... The drone's outer shell
is made of titanium giving it a health of 200 units at launch. So,
you'll have to hit it hard a couple of times before it's health gets
down to zero.

Here's a tip to help you out!! The drone's search radius is affected
by the amount of health the drone has remaining (ie, the damage the
drone has sustained). The more damage you inflict on the drone the
shorter it's search radius gets! This means that you can even closer
to the drone to inflict even more damage on it because it can't see
you outside of its search radius.

For Example:

The starting search radius is 1200 units (which is pretty far in
Q2 units) and the max_health of the drone is 200 units. So, if
you, for instance, hit it with some weapon so its health went to
100 units (reducing it by 50%) then the long range vision of the
drone's search radius gets reduced by 50% too! In this example,
the search radius will be only 600 units because of the sustained
damage to the drone. You get the idea.. More damage, Less health,
Shorter vision radius!

Also, after launch, the drone remains in-touch with its owner
so the drone knows when its owner has died! And, since
the LaserDrone technology is so crucial to the ongoing efforts,
the drone self destructs in mid-air rather than let its
technology fall into the wrong hands. The debris from the
exploded drone rains down from mid-air on top of the piles
of dead bodies scattered all over the ground below. hehe
So, if the owner of the drone dies before the powercells of
the drone run out, then the drone explodes into a violent
fireball killing anybody within its blast radius.

HINT: You want to get rid of that drone which keeps
killing you over and over again? Then, track down the drones
owner! You kill that bastard and you cause the drone to
self-destruct. Better yet! Go after the drone! You coward!

Lastly, the client's variable 'laserdrone' not only turns
the drone on but the drone's thinking routine will initiate
the self-destruct sequence as soon as it detects that this
laserdrone variable has been set to 0. Once you launch your
drone, if you hit your aliased 'drone' key during the life
cycle (60 secs) of the laserdrone, you will destroy it! So,
be careful with that key..

Okay, enough of this bullshit. Let's get start!!

philip
profile | email

posted 10-31-98 6:18 PM CT (US)
===========================================================

The first thing we need to do is set up some variables in
your g_locals.h. Open you g_locals.h file and at the
bottom of your struct gclient_s add this:

int laserdrone; // LaserDrone 1=ON, 0=OFF

===========================================================

Also, in your g_locals.h file we need to add a new MOD_*
flag for the client obituaries.. Add the MOD_LASERDRONE
flag as the next incremented flag in the sequence as shown
by example..

#define MOD_ROCKET_BOMBS 0x00000025
#define MOD_CLUSTER_BOMBS 0x00000024
#define MOD_ZYLON_GAS 0x00000025
#define MOD_RAILSTRIKE 0x00000026
#define MOD_BFG_NUKE 0x00000027
#define MOD_DECOY 0x00000028
#define MOD_LASERDRONE 0x00000029 // NEW FLAG HERE
#define MOD_FRIENDLY_FIRE 0x10000000 // DON'T TOUCH THIS ONE!!!

Note, it doesn't matter what the number really is but it makes
things much easier in the long run if you make it the next
integer in the numerical sequence..

==========================================================

Okay, lets initialize this variable at the bottom of you
InitClientPersistant() function (p_client.c file) by putting
in this:

client->laserdrone=0; // default to OFF.


===========================================================

Let's go and find your ClientObituary() function and let's
add some obits to your gameplay..

void ClientObituary() {


---------- Find these lines here -------------

case MOD_TARGET_BLASTER:
message="got blasted";
break;

---------- Add these lines right here ---------

case MOD_LASERDRONE:
message="was railed by the drone";
break;


Further down in this same function:


------------- Find these lines -----------

case MOD_G_SPLASH:
if (IsFemale(self))
message="tripped on her own grenade";
else
message="tripped on his own grenade";
break;

--------- Add these lines here -------------

case MOD_LASERDRONE:
if (IsFemale(self))
message="got railed by her drone";
else
message="got railed by his drone";
break;


Lastly, go further down in this function:


-------------- Find these lines ----------

case MOD_TELEFRAG:
message="tried to invade";
message2="'s personal space";
break;

--------- Add these lines right here -------

case MOD_LASERDRONE:
message="was railed by";
message2="'s drone";
break;


Note: You should feel free to change these obituary messages
to whatever you think is more appropriate for your mod...

===========================================================

Open up your g_cmds.c file and find the ClientCommand()
function. Near the bottom, add this into one of the else if
statements like so..

else if (Q_stricmp(cmd, "weapprev") == 0)
Cmd_WeapPrev_f(ent);
else if (Q_stricmp(cmd, "weapnext") == 0)
Cmd_WeapNext_f(ent);
else if (Q_stricmp(cmd, "drone") == 0 ) //<= NEW LINE HERE
Cmd_LaserDrone_f(ent); //<= NEW LINE HERE
else if ....

This will start the drone process when the player hits
their aliased key..

===========================================================

Also, at a point near the top of g_cmds.c we need to add
this forward declaration:

void Cmd_LaserDrone_f(edict_t *ent);

Just like that. Paste it in anywhere near the top and
you'll be okay..

===========================================================

Let's open up our g_combat.c file and find your Killed()
function. Add the following lines at the top of this function
as shown:

void Killed(....){

// Was this the LaserDrone which just got killed?
if (Q_stricmp(targ->classname, "LaserDrone")==0) {
// Tell drone to detonate on next frame.
targ->owner->client->laserdrone=0;
// Give the attacker 5 frags for killing the Drone..
if (G_EntExists(attacker) && (attacker!=targ->owner))
attacker->client->resp.score += 5;
return; }


This function is called from T_Damage() if the health of
the target falls below zero. In this instance, if the
target which 'died' happens to be the drone, then we need
to award the drone's attacker with the 5 frags credit
(providing they're still in the game, of course)!

===========================================================

Now comes the easy part! Create a new file called g_drone.c
and paste in all the following code from START to STOP.

Remember, be sure to check to make sure that the include
statement matches your particular setup.. Also, if there
are any function calls here which may have caps in their
letters (which makes them different than your functions)
then change my function names.. I've added cap letters to
some of my functions names which may make them different
than yours. If so, then change mine to match yours.. Your
compiler should let you know which, if any, need to be
changed.

---------------- START CUTTING HERE ----------------------

include "g_locals.h"

//======================================================
//=========== AIRIAL LASERDRONE WEAPON =================
//======================================================


//======================================================
//======================================================
//======== SOME NECESSARY HELPER FUNCTIONS =============
//======================================================
//======================================================

//======================================================
// True if Ent is valid, has client, and edict_t inuse.
//======================================================
qboolean G_EntExists(edict_t *ent) {
return ((ent) && (ent->client) && (ent->inuse));
}

//======================================================
// True if ent is not DEAD or DEAD or DEAD (and BURIED!)
//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean buried=true;
qboolean b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b5=ent->health > 0;
return (b3||b2||b1)&&(buried);
}

//======================================================
// True if ent is not DEAD and not just did a Respawn.
//======================================================
qboolean G_ClientInGame(edict_t *ent) {
if (!G_EntExists(ent)) return false;
if (!G_ClientNotDead(ent)) return false;
return (ent->client->respawn_time + 5.0 < level.time);
}

//======================================================
//========== Spawn Temp Entity Functions ===============
//======================================================
/*
Spawns (type) Splash with {count} particles of {color} at {start} moving
in {direction} and Broadcasts to all in Potentially Visible Set from
vector (origin)

TE_LASER_SPARKS - Splash particles obey gravity
TE_WELDING_SPARKS - Splash particles with flash of light at {origin}
TE_SPLASH - Randomly shaded shower of particles

colors:
1 - red/gold - blaster type sparks
2 - blue/white - blue
3 - brown - brown
4 - green/white - slime green
5 - red/orange - lava red
6 - red - blood red
All others are grey
*/
//======================================================
void G_Spawn_Splash(int type, int count, int color, vec3_t start, vec3_t movdir, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WriteByte(count);
gi.WritePosition(start);
gi.WriteDir(movdir);
gi.WriteByte(color);
gi.multicast(origin, MULTICAST_PVS);
}

//======================================================
//======================================================
//======================================================
/*
Spawns a string of successive (type) models of from record (rec_no)
from (start) to (endpos) which are offset by vector (offset) and
Broadcasts to all in Potentially Visible Set from vector (origin)

Type:
TE_GRAPPLE_CABLE - The grappling hook cable
TE_MEDIC_CABLE_ATTACK - NOT IMPLEMENTED IN ENGINE
TE_PARASITE_ATTACK - NOT IMPLEMENTED IN ENGINE
*/
//======================================================
void G_Spawn_Models(int type, short rec_no, vec3_t start, vec3_t endpos, vec3_t offset, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WriteShort(rec_no);
gi.WritePosition(start);
gi.WritePosition(endpos);
gi.WritePosition(offset);
gi.multicast(origin, MULTICAST_PVS);
}

//======================================================
//======================================================
//======================================================
/*
Spawns a trail of (type) from {start} to {end} and Broadcasts to all
in Potentially Visible Set from vector (origin)

TE_BFG_LASER - Spawns a green laser
TE_BUBBLETRAIL - Spawns a trail of bubbles
TE_PLASMATRAIL - NOT IMPLEMENTED IN ENGINE
TE_RAILTRAIL - Spawns a blue spiral trail filled with white smoke
*/
//======================================================
void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.WritePosition(endpos);
gi.multicast(origin, MULTICAST_PVS);
}

//======================================================
//======================================================
//======================================================
/*
Spawns sparks of (type) from {start} in direction of {movdir} and
Broadcasts to all in Potentially Visible Set from vector (origin)

TE_BLASTER - Spawns a blaster sparks
TE_BLOOD - Spawns a spurt of red blood
TE_BULLET_SPARKS - Same as TE_SPARKS, with a bullet puff and richochet sound
TE_GREENBLOOD - NOT IMPLEMENTED - Spawns a spurt of green blood
TE_GUNSHOT - Spawns a grey splash of particles, with a bullet puff
TE_SCREEN_SPARKS - Spawns a large green/white splash of sparks
TE_SHIELD_SPARKS - Spawns a large blue/violet splash of sparks
TE_SHOTGUN - Spawns a small grey splash of spark particles, with a bullet puff
TE_SPARKS - Spawns a red/gold splash of spark particles
*/
//======================================================
void G_Spawn_Sparks(int type, vec3_t start, vec3_t movdir, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.WriteDir(movdir);
gi.multicast(origin, MULTICAST_PVS);
}

//======================================================
//======================================================
//======================================================
/*
Spawns a (type) explosion at (start} and Broadcasts to all Potentially
Visible Sets from {origin}

TE_BFG_BIGEXPLOSION - Spawns a BFG particle explosion
TE_BFG_EXPLOSION - Spawns a BFG explosion sprite
TE_BOSSTPORT - Spawns a mushroom-cloud particle effect
TE_EXPLOSION1 - Spawns a mid-air-style explosion
TE_EXPLOSION2 - Spawns a nuclear-style explosion
TE_GRENADE_EXPLOSION - Spawns a grenade explosion
TE_GRENADE_EXPLOSION_WATER - Spawns an underwater grenade explosion
TE_ROCKET_EXPLOSION - Spawns a rocket explosion
TE_ROCKET_EXPLOSION_WATER - Spawns an underwater rocket explosion

Note: The last four EXPLOSION entries overlap to some degree.
TE_GRENADE_EXPLOSION is the same as TE_EXPLOSION2,
TE_ROCKET_EXPLOSION is the same as TE_EXPLOSION1,
and both of the EXPLOSION_WATER entries are the same, visually.
*/
//======================================================
void G_Spawn_Explosion(int type, vec3_t start, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.multicast(origin, MULTICAST_PVS);
}

//======================================================
//============ LASERDRONE WEAPON ROUTINES ==============
//======================================================

#define DEBRIS1_MODEL "models/objects/debris1/tris.md2"
#define DEBRIS2_MODEL "models/objects/debris2/tris.md2"
#define DEBRIS3_MODEL "models/objects/debris3/tris.md2"

#define ITEM_IN_ENTS_INVENTORY ent->client->pers.inventory[index]

void ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin);
qboolean visible(edict_t *self, edict_t *other);

#define LAUNCH_SOUND gi.soundindex("world/airhiss1.wav")
#define ACTIVATION_SOUND gi.soundindex("world/boss3/W_loop.wav")
#define LASER_FIRE_SOUND gi.soundindex("world/Gladiator/Railgun.wav")

//======================================================
void LaserDrone_Fire(edict_t *LaserDrone, edict_t *target){
trace_t tr;
vec3_t dummy=(0,0,0);
vec3_t start,end;

VectorCopy(LaserDrone->s.origin, start);
VectorCopy(target->s.origin, end);

// some minor adjustments to positioning
start[2] += 70; end[2] += 8;

// Find out what we hit at trace end point, if anything..
tr = gi.trace(start, target->mins, target->maxs, end, NULL, MASK_SHOT);

// Fire the laser beam with bubbletrail from start to end..
G_Spawn_Trails(TE_BFG_LASER, start, tr.endpos, tr.endpos);
G_Spawn_Trails(TE_BUBBLETRAIL, start, tr.endpos, tr.endpos);

// If end point entity (tr.ent) exists and can takedamage, then...
if (G_EntExists(tr.ent) && (tr.ent->takedamage)) {
// Display a splash of blood from victim at impact point.
G_Spawn_Sparks(TE_BLOOD, tr.endpos, tr.plane.normal, tr.endpos);
// Apply damage to this victim and return..
T_Damage(tr.ent, LaserDrone->owner, LaserDrone->owner, dummy, start, dummy, LaserDrone->dmg, 1, 0, MOD_LASERDRONE);
} // endif
else
// Else, if the trace end point was NOT the sky, then ...
if (!((tr.surface) && (tr.surface->flags & SURF_SKY))){
// Show sparks at point of impact..
G_Spawn_Sparks(TE_SCREEN_SPARKS, tr.endpos, tr.plane.normal, tr.endpos);
// Do a radius damage of 100 units from sparks impact point..
T_RadiusDamage(tr.ent, LaserDrone->owner, 100, NULL, LaserDrone->dmg, MOD_SPLASH);
} // endif

// Must play sound AFTER firing else targeting accuracy affected.. (??)
gi.sound(LaserDrone, CHAN_VOICE, LASER_FIRE_SOUND, 1, ATTN_NORM, 0);

LaserDrone->nextthink = level.time + 1.0; // Re-target in 1 second...
}

//======================================================
void LaserDrone_Die(edict_t *LaserDrone) {

LaserDrone->owner->client->laserdrone=0; // Immediately Flag as OFF

// Blow up the LaserDrone Unit and let debris rain down...
G_Spawn_Explosion(TE_EXPLOSION2, LaserDrone->s.origin, LaserDrone->s.origin);

// Let it rain, rain, rain...
ThrowDebris(LaserDrone, DEBRIS3_MODEL, 3.75, LaserDrone->s.origin);
ThrowDebris(LaserDrone, DEBRIS3_MODEL, 2.50, LaserDrone->s.origin);
ThrowDebris(LaserDrone, DEBRIS2_MODEL, 4.60, LaserDrone->s.origin);
ThrowDebris(LaserDrone, DEBRIS2_MODEL, 1.50, LaserDrone->s.origin);
ThrowDebris(LaserDrone, DEBRIS3_MODEL, 2.30, LaserDrone->s.origin);
ThrowDebris(LaserDrone, DEBRIS3_MODEL, 4.50, LaserDrone->s.origin);

// Do damage to all within ents within blast radius.
if (!G_ClientInGame(LaserDrone->owner))
// Owner has since died so don't assign damage frags to LaserDrone's owner.
T_RadiusDamage(LaserDrone, LaserDrone, LaserDrone->dmg, NULL, LaserDrone->dmg_radius, MOD_LASERDRONE);
else
// Owner alive, so assign all frags to the LaserDrone's owner..
T_RadiusDamage(LaserDrone, LaserDrone->owner, LaserDrone->dmg, NULL, LaserDrone->dmg_radius, MOD_LASERDRONE);

G_FreeEdict(LaserDrone); // Free the LaserDrone entity.
}

//======================================================
// True if start and end are within radius distance..
//======================================================
qboolean Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
for (j=0; j<3; j++)>
eorg[j]=abs(start[j]-end[j]);
return (VectorLength(eorg)
}

//======================================================
// True if ent meets Drone's targeting criteria..
//======================================================
qboolean Good_Target(edict_t *LaserDrone, edict_t *ent) {
float rad;

if ((G_ClientInGame(ent)) && (ent!=LaserDrone)
&& (ent->takedamage) && (visible(LaserDrone,ent))) {
// Targeting radius decreases with health of Drone.
rad=LaserDrone->wait*(LaserDrone->health/LaserDrone->max_health);
// Is this good ent within the targeting radius??
return Within_Radius(LaserDrone->s.origin, ent->s.origin, rad);
} // endif

return false;
}

//======================================================
// True if LaserDrone has been deactivated (exploded!).
//======================================================
qboolean DeActivated(edict_t *LaserDrone) {

// Has time expired? Or, Owner since died?
if ((LaserDrone->delay < level.time)
|| (LaserDrone->owner->client->laserdrone==0)) {
LaserDrone_Die(LaserDrone);
return true; }
else
return false;
}

//======================================================
// Need dummy touch because BBOX hits world surfaces..
//======================================================
void LaserDrone_Touch(edict_t *LaserDrone, edict_t *other, cplane_t *plane, csurface_t *surf)
{}

//======================================================
// UNUSED - Search ALL Ents, then fire at the closest one!
//======================================================
void LaserDrone_ThinkSlow(edict_t *LaserDrone){
edict_t *ent=NULL;
edict_t *pBest=NULL;
vec3_t vDist;
long int i, tDist, closest=99999;

for(i=0;i
ent=g_edicts+i+1;
if (DeActivated(LaserDrone)) return;
if (!Good_Target(LaserDrone,ent)) continue;
VectorSubtract(ent->s.origin, LaserDrone->s.origin, vDist);
tDist=VectorLength(vDist);
if (tDist < closest) {
pBest = ent; // closest ent so far..
closest = tDist;} // shortest distance so far.
} // end for

// Re-test because Owner may have since moved or died.
if (Good_Target(LaserDrone,pBest)) {
LaserDrone_Fire(LaserDrone, pBest);
return; }

// Nobody around? so, re-target on next frame.
LaserDrone->nextthink = level.time + 0.1;
}

//==========================================================
// Search and Fire at the first visible ent.. (VERY FAST!)
//==========================================================
void LaserDrone_ThinkFast(edict_t *LaserDrone) {
edict_t *ent=NULL;
int i;

for(i=0;i
ent=g_edicts+i+1;
if (DeActivated(LaserDrone)) return; // Drone has exploded!
// if (ent==LaserDrone->owner) continue; // Don't Attack Owner??
if (!Good_Target(LaserDrone,ent)) continue;
LaserDrone_Fire(LaserDrone, ent);
return;
} // end for

// Nobody around? Re-target on next frame.
LaserDrone->nextthink = level.time + 0.1;
}

//======================================================
void Activate_LaserDrone(edict_t *LaserDrone) {

// Drone can take damage upon activation..
LaserDrone->takedamage = DAMAGE_YES;
// Play Drone activation sound..
gi.sound(LaserDrone, CHAN_VOICE, ACTIVATION_SOUND, 1, ATTN_NORM, 0);
// Let Owner know that LaserDrone is now Active..
gi.centerprintf(LaserDrone->owner, "LaserDrone Activated\n");

LaserDrone->think = LaserDrone_ThinkFast; // Use the FAST TARGETING routine.
LaserDrone->nextthink = level.time + 0.1; // Start thinking on next frame..
}

//======================================================
void Launch_LaserDrone(edict_t *ent) {
edict_t *LaserDrone;
int x=(random()>0.5?-1:1);
int y=(random()>0.5?-1:1);
vec3_t last_angles={0,0,0};

LaserDrone = G_Spawn();
LaserDrone->classname = "LaserDrone";
LaserDrone->owner = ent;

VectorCopy(ent->s.origin, LaserDrone->s.origin);
VectorCopy(ent->s.old_origin, LaserDrone->s.old_origin);
LaserDrone->s.origin[0] += (random()*20+1)*x;
LaserDrone->s.origin[1] += (random()*20+1)*y;
LaserDrone->s.origin[2] += ent->viewheight+8;
VectorCopy(ent->move_angles,last_angles);
LaserDrone->velocity[2] = (random()*40);
LaserDrone->velocity[1] = ((int)((random()*40)+10+last_angles[1])%60)*y;
LaserDrone->velocity[0] = ((int)((random()*40)+20+last_angles[0])%60)*x;
VectorCopy(LaserDrone->velocity, last_angles);

LaserDrone->movetype = MOVETYPE_FLY;// Float around gently.
LaserDrone->solid = SOLID_BBOX; // Enable touch capability.
LaserDrone->takedamage = DAMAGE_NO; // No Damage until Activation.
LaserDrone->clipmask = MASK_SHOT; // Ability to be hit by weapon's fire.
LaserDrone->s.effects = EF_ROTATE; // Spin Drone around slowly.

LaserDrone->s.modelindex = gi.modelindex("models/items/keys/target/tris.md2");
LaserDrone->s.modelindex2 = 0; // no gun model..

VectorSet(LaserDrone->mins, -20, -20, -40); // size of bbox for touch
VectorSet(LaserDrone->maxs, 20, 20, 60); // size of bbox for touch

LaserDrone->dmg = 80; // 80 units damaging power.
LaserDrone->dmg_radius = 300; // 300 unit radius damage upon explosion.

LaserDrone->health=200; // titanium hull makes it tough to kill!!
LaserDrone->max_health=200;
LaserDrone->wait=1200; // Used as search radius var!!

LaserDrone->delay = level.time + 60.0; // This tape will self-destruct in 60 secs. Good Luck Jim!

LaserDrone->touch=LaserDrone_Touch; // Dummy Function..

LaserDrone->think = Activate_LaserDrone; // Done only once!

LaserDrone->nextthink = level.time + 5.0; // 5 Sec delay before Activation..

gi.linkentity (LaserDrone);

ent->client->laserdrone = 1; // Flag LaserDrone as ON.

gi.centerprintf(ent,"LaserDrone Launched\n\nMOVE AWAY NOW!!\n\n5 secs to Activation!\n");

gi.sound(LaserDrone, CHAN_VOICE, LAUNCH_SOUND, 1, ATTN_NORM, 0);
}

//======================================================
// Pay the Piper to initiate the Arial LaserDrone Unit
//======================================================
void Cmd_LaserDrone_f(edict_t *ent) {
int index;

// Don't allow dead/respawning players to launch drone!
if (!G_ClientInGame(ent)) return;

// If LaserDrone not already ON, then..
if (ent->client->laserdrone==0) {
// Check if ent has any powercells at all..
index = ITEM_INDEX(FindItem("cells"));
// If not enough cells or not enough frags then notify ent..
if ((ITEM_IN_ENTS_INVENTORY < 50) && (ent->client->resp.score < 5)){
gi.centerprintf(ent, "LaserDrone requires 50 PowerCells\n\nor 5 Frags to launch!");
return; }
// Try to deduct the cells first!
if (ITEM_IN_ENTS_INVENTORY >= 50) {
// Deduct 50 cells from inventory
ITEM_IN_ENTS_INVENTORY -= 50;
// And, activate the LaserDrone..
Launch_LaserDrone(ent);
return;
} // endif
// Otherwise, if ent has enough frags,
if (ent->client->resp.score > 5) {
// Then, deduct frags from ent's Score
ent->client->resp.score -= 5;
// And, activate the LaserDrone..
Launch_LaserDrone(ent);
return;
} // endif
} // endif
else
// Turn OFF - Activate Self Destruct.
ent->client->laserdrone=0;
}

------------------ STOP CUTTING HERE ---------------------

===========================================================

Lastly, be sure to bind a key in your autoexec.cfg file as
shown below by way of example...

bind d "drone"

This will toggle the drone ON/OFF with a single keystroke.

That's it!! NOW GET OUT THERE AND KILL ALL THOSE BASTARDS!!

regards,
philip

philip
profile | email

posted 10-31-98 8:24 PM CT (US)
For some reason the Within_Radius() function missed something in the return statement.

//======================================================
// True if start and end are within radius distance..
//======================================================
qboolean Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
for (j=0; j<3; j++)>
eorg[j]=abs(start[j]-end[j]);
return (VectorLength(eorg)<= FIX THIS LINE HERE
}

philip
profile | email

posted 10-31-98 8:28 PM CT (US)
Man this is really beginning to piss me off! I copy it from my code and when I paste it into this message body it doesn't show up!???

the return statement should read like this..
return (VectorLength(eorg) < rad);

I think that the '<' symbol means to ignore the rest of the line! That really sucks!! now I'm gonna have to check every stinking line of code to see what else it stripped out!
philip

philip
profile | email

posted 10-31-98 8:52 PM CT (US)
Okay, the code is clean (give or take a few mispellings in the comments).. Must've been something peculiar with that one line of code. Hmmm...
Have fun!!
philip

philip
profile | email

posted 11-01-98 2:15 PM CT (US)
Hey Coders,
Turns out that the for-loop in both of the think functions also had some chars stripped out of it too.
The for-loop-control statement for each is the same. It should look like this:

for (i = 0; i < game.maxclients; i++) {

Go ahead and make that change. Gee, this message board buffer stuff is really a bummer!

Hope that's it!!
philip

Coreth
profile | email

posted 11-02-98 12:37 AM CT (US)
I got many errors here, for your tutorial. I am using LCC, by the way. Here are some of the many errors i got:

Error c_drone.c 16 syntax error; found `G_EntExists' expecting `;'
Error c_drone.c 16 syntax error; found `*' expecting `)'
Error c_drone.c 16 skipping `*' `ent'
Error c_drone.c 17 undeclared identifier `ent'
Error c_drone.c 17 left operand of -> has incompatible type `int'
Error c_drone.c 17 left operand of -> has incompatible type `int'
Error c_drone.c 23 syntax error; found `G_ClientNotDead' expecting `;'
Error c_drone.c 23 syntax error; found `*' expecting `)'
Error c_drone.c 23 skipping `*' `ent'
Error c_drone.c 24 syntax error; found `buried' expecting `;'
Error c_drone.c 24 undeclared identifier `buried'
Error c_drone.c 24 undeclared identifier `true'
Error c_drone.c 25 syntax error; found `b1' expecting `;'
Error c_drone.c 25 undeclared identifier `b1'
Error c_drone.c 25 undeclared identifier `ent'
Error c_drone.c 25 left operand of -> has incompatible type `int'
Error c_drone.c 25 left operand of -> has incompatible type `int'
Error c_drone.c 25 left operand of . has incompatible type `int'
Error c_drone.c 25 left operand of . has incompatible type `int'
Error c_drone.c 25 undeclared identifier `PM_DEAD'
Error c_drone.c 26 too many errors

As, you can see, it didn't get very far before deciding that I suck and have too many errors. These errors are VERY strange, though, and I don't know what it's all about!

-Coreth

philip
profile | email

posted 11-02-98 2:18 AM CT (US)
MINOR BUG FIX to G_ClientNotDead()...

: Hey Coders,

//======================================================
// True if ent is not DEAD or DEAD or DEAD (and BURIED!)
//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean buried=true;
qboolean b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b3=ent->health > 0; <=== SHOULD BE B3 and NOT B5 !!
return (b3||b2||b1)&&(buried);
}

: regards,
: philip

philip
profile | email

posted 11-02-98 2:22 AM CT (US)
Coreth,
Don't know what you've done wrong.. Has anybody coded this to completion yet? If so, some feedback would be nice.
Anyway, I've compiled this thing probably in excess of 50 times (at least!) during development. Did you read the above text on some of the problems I encountered posting this tute? Perhaps that will shed some light on your errors. Looks like they are cascading from a single point before line 16. I'd start there to find out what you are missing.
regards,
philip

philip
profile | email

posted 11-02-98 9:38 PM CT (US)
After reading Stark's comments (on the other board) I was thinking, as opposed to searching and firing at the first visible ent then exiting (as before) this routine only checks for DeActiviation once then it does its search radius loop where it'll fire
at ALL visible entities before exiting out of the loop or self-destructing..

//==========================================================
// Search and Fire at all visible ents.. (MODIFIED)
//==========================================================
void LaserDrone_ThinkFast(edict_t *LaserDrone) {
edict_t *ent=NULL;
int i;

if (DeActivated(LaserDrone)) return; // Drone has exploded!

for(i=0;i < game.maxclients;i++) {
ent=g_edicts+i+1;
if (!Good_Target(LaserDrone,ent)) continue;
LaserDrone_Fire(LaserDrone, ent);
} // end for

// Immediately re-target on next frame.
LaserDrone->nextthink = level.time + 0.1;
}