The Flyer

 

 

philip
profile | email

posted 11-20-98 11:52 AM CT (US)
Title: The Flyer
Difficulty: Moderate
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net
Date: 11-19-98
Note: Please give credit where credit is due.
======================================================

Hey Space Marines, you're fortunes have just taken a
turn for the better! Why? You ask digging one finger
into your nose while scratching your ass with the other
hand.. Well... You see, the weapons designers back
at Space Marine Weapons Research have been diligently
working on reverse-engineering a couple of those 'Flyer'
monsters which had been captured by those gallant men and
women who fought ever so bravely on Stroggos!! Ahhhhh Huh..
Huh, Huh Huhh Uhhh Huhhhh... Hey grunt! You rememeber
one of those flyer monsters from Q2 single player, don't
you? The ones which look like a kind of flat bat-winged
creature with a sort of brain in the center of it and it
shoots out hyperblaster bolts. They usually run in packs
and will chase your ass down until you are dead? "Yeah,
I've taken out a few of those bastards", you reply. "But",
you ask, "what ever does all this have to do with the price
to get laid on the lower backside of upper Bumfuct"?

Okay, Okay... I'll get to the point. What if the weapons
designers reverse engineered one of those Flyer monsters
and made one for you so that you could ride on its back?
Ahhhh. That got your interest.. And, what if it chased
down the bad-guys and blasted them with it's hyperblaster
while you were riding on top of it? YES!! What if you could
swivel around and independently use your own weapons to
blast away at all those bastards below while your Flyer
navigated, evaded, and sought out your enemies for you??
YES! OH YES!! And, what if the Flyer had a nice powerscreen
to protect you and it from direct hits! AAAAHHHHH!!! Well,
allow me to introduce the newest weapon in your arsenal, called
affectionately "The Flyer" !!!

What is it:

For the cost of 50 powercells or 3 frags, you hit your aliased
key and shooting out in front of you (with a trail of sparks
and some powerup sound) comes one of these Flyers! It will
stop about 100 units in front of you and about 50 units up
(which isn't really that far but its not right in your face!).
When the particle trail dies down, you see it hovering in front
of you facing in your forward looking direction... The Flyer is
always in search and destroy mode (as usual) so you need to get
on its back ASAP or GET THE HELL AWAY because once you are in
'riding' mode, it can't see you anymore!

We've lost quite a few recruits testing this baby out because
they failed to get onboard in the time that it takes for the
Flyer's search and targeting mechanism to go active!! So either
run and hide and teleport onto its back later or do it right away
but don't just stand there like a dumbass because you'll get drilled!!

As soon as the Flyer stops its forward motion after its launch, it'll
slowly turn facing you because it is now picking you up as a viable
target. So, get onboard!!

To get aboard your Flyer, you hit your aliased key again and you get
teleported onto the Flyer's back!! It may buck a bit (like a
bronco) but it'll settle in once it realizes you are a good guy
(system takes a couple of frames to get everything set up)..
Don't worry, just bronco ride it.. If you have trouble with it
then just quickly teleport off and teleport back on!! Shouldn't
be a problem once it gets use to you.

Okay, so now you are on top of this baby (in crouch mode to make
you less of a target) and the Flyer doesn't know that you are
there (at least its targeting mechanism doesn't detect you)! If
no bad guys are in the area then the Flyer's targeting mechanism
tells it to look around for viable targets. Meanwhile, you can
fire at will. I didn't make it so that the Flyer targets what
you are targeting because that would limit the number of kills you
both could be racking up if you worked independently!! So, between
the two of you, you should be really racking up the frags!!

You can't steer the Flyer. You should let the Flyer's complex search
and destroy mechanisms do all the finding and evading while it is
in attack mode and you should concentrate on aiming and firing!! It
was the unanimous opinion of the Weapons Designers (me) that if you
had to steer the Flyer during combat that it'd take away from the total
killing package of this new weapon because you'd be pre-occuppied in
trying to maneuver around and not concentrating on fraggin!!

Several things...


The Flyer has a life span of 3 full minutes (which is pretty long
for the price)!!

The Flyer has a PowerScreen feature too!!

Also, You only can have 1 Flyer active at any one time. So, if you
want another Flyer in another location, you have to wait for it to
either get shot down or have it's lifecycle finish up. There is
no way that you can destroy your own Flyer. When your 3 minutes of
Flyer lifespan are just about up, a warning flashes across your HUD
warning you that the Flyer is going to detonate in 10 seconds. So,
if you are riding your Flyer and the message comes up on your HUD,
then teleport off the Flyer right away!! Like I said before, you can
repeatedly teleport on and off your Flyer. So, if you are really getting
the snot beat out of you then quickly teleport off your flyer to some
distant respawn point..

The Flyer can be shot out from under you!! If that happens you
probably will sustain some radius damage and then you'll fall to
the ground. And, as usual, you can get picked clean off the Flyer
by a sniper if you are not careful. A player who takes out the Flyer
picks up 2 frags! And if they take you out with it, then that makes 3!

The Flyer stays alive for its entire life span whether or not you
are alive.

You get all the frags for its kills. So, you can ride and fight
atop your Flyer or you can teleport off whenever you want and let
it fight on without you. Like anything new, you'll have to play
around with riding and fighting to get use to using your new weapon
in real action..

There is a HUD display of the total health of the Flyer (a number
appears similar to the Cloaking Timer of on of my previous tutorials
so you can monitor the overall health of your Flyer. If it is really
getting down on its health then teleport off of it!!

THIS is an awesome weapon!!

Okay, let's get started!

============================================================
============================================================
NOTE: I've used these helper routines in many of my previous
tutorials so you may already have them someplace in your source.
If you already do have them, then you don't need them anymore.
Your compiler should warn you that you've already got these
functions previously defined..

Okay, add the following text to the top of your g_weapons.c file.

------------------ START HERE ------------------------

//======================================================
// 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 b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b3=ent->health > 0;
return (b3 || b2 || b1);
}

//======================================================
// 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);
}

//======================================================
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);
}

//======================================================
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);
}
===============================================================
Okay, we need to add some variables to your edict_s Structure
in your g_local.h file.. Note, you can substitute these edict_t
vars for other ones already present in the structure if you'd
like to save some memory. Just be sure to make all the right
substitutions if you do so..


moveinfo_t moveinfo;
monsterinfo_t monsterinfo;

------- ADD THESE LINES HERE -----------

edict_t *vehicle; // Monster Vehicle
edict_t *rider; // Vehicle Rider..
qboolean is_riding;// ent is on top of Vehicle..

-----------------------------------------------

These are the vars which we'll be using globally in the code.

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

Open up your g_cmds.c file and go near the bottom of your
ClientCommand() function and add the following as shown
by example..

---------- IF-ELSE STATEMENTS --------
else if (Q_stricmp(cmd, "decoy") == 0 )
SP_Decoy(ent);
else if (Q_stricmp(cmd, "drone") == 0 )
Cmd_LaserDrone_f(ent);
else if (Q_stricmp(cmd, "teleport") == 0)
Cmd_Teleport_f(ent);
----------- ADD THESE LINES ------
else if (Q_stricmp(cmd, "flyer") == 0 )
Cmd_Vehicle_f(ent);

This will activate your Ratapult weapon.

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

Add this prototype declaration to the top of you g_cmds.c
file:

void Cmd_Vehicle_f(edict_t *ent);

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

Okay, now open up your g_ai.c file and find your visible()
function and add the lines as shown..

qboolean visible(edict_t *self, edict_t *other) {
vec3_t spot1;
vec3_t spot2;
trace_t trace;

------------ ADD THESE LINES RIGHT HERE ----------
// Is this XVehicle looking for visible targets?
if (Q_stricmp(self->classname, "XVehicle")==0)
// Is ent currently riding this Vehicle?
if (self->rider->is_riding)
// Then Vehicle can't see rider!
if (self->rider==other)
return false;
--------------------------------------------------

This will make it so the Flyer can't see you once you are
riding on top of it!!

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

Find your Killed() function in your g_combat.c file and
add the following lines as shown:

void Killed(......)
{
if (targ->health < -999)
targ->health = -999;

------------- ADD THESE LINES RIGHT HERE ------------

if (deathmatch->value)
// Was this the XVehicle which just got shot down?
if (Q_stricmp(targ->classname, "XVehicle")==0)
if (G_EntExists(attacker) && (attacker!=targ->rider)) {
gi.centerprintf(attacker,"2 Frags for downing a Flyer!\n");
attacker->client->resp.score += 2; }

-------------------------------------------------------------

This will award the 2 frags to the player who shoots down
your Flyer!!

============================================================
Find your ClientObituary() function and add this code at
the very top..

// --------------------------------------------------------
// Are we in DM Mode?
if (deathmatch->value)
// Make sure both attacker and victim still in game!
if ((G_EntExists(attacker)) && (G_EntExists(self)))
// Was the attacker the XVehicle?
if (Q_stricmp(attacker->classname, "XVehicle")==0)
// XVehicle's rider still exists?
if (G_EntExists(attacker->rider)) {
// Assign XVehicle's kills to rider's score!!
gi.bprintf(PRINT_MEDIUM,"%s was drilled by %s's Flyer\n",
self->client->pers.netname,
attacker->rider->client->pers.netname);
attacker->rider->client->resp.score += 1;
return; }

// --------------------------------------------------------

This will make sure that the Flyer's owner gets the frags
for its kills!

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

Okay, now we need to do some really interesting stuff..

Find your ClientBeginServerFrame() function and make these
changes as indicated..

void ClientBeginServerFrame(edict_t *ent)
{
gclient_t *client;
int buttonMask;

if (level.intermissiontime)
return;

------------ ADD THESE LINES RIGHT HERE -----------
// Is ent riding a vehicle?
if (ent->is_riding) {
// Move ent and ent's view with vehicle movement.
VectorCopy(ent->vehicle->s.origin, ent->s.origin);
VectorCopy(ent->vehicle->s.angles, ent->s.angles);
} // endif

------------------------------------------------------

This will make sure that the ent moves with the Flyer
while riding it..

====================================================
Okay, now find your G_SetClientFrame() function and make
these changes...


void G_SetClientFrame(edict_t *ent) {
gclient_t *client;
qboolean duck, run;

if (ent->s.modelindex != 255)
return; // not a player!

client = ent->client;

-------- ADD THESE LINES RIGHT HERE ---------

// If ent is rider then flag as crouched.
if (ent->is_riding)
client->ps.pmove.pm_flags = PMF_DUCKED;

--------------------------------------------

And, further down in this same function add these lines:

newanim:
// return to either a running or standing frame
client->anim_priority = ANIM_BASIC;
client->anim_duck = duck;
client->anim_run = run;

-------------- ADD THESE LINES HERE -----------
// Rider does crouched Anim Frames..
if (ent->is_riding) {
ent->s.frame = FRAME_crstnd01;
client->anim_end = FRAME_crstnd19; }
else
-----------------------------------------------
if (!ent->groundentity) {
client->anim_priority = ANIM_JUMP;
if (ent->s.frame != FRAME_jump2)
ent->s.frame = FRAME_jump1;
client->anim_end = FRAME_jump2; }

This will make sure that the ent's crouching Frames will
be played continuously while the ent is on top of the
Flyer...

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

Find your G_SetStats() function and paste in the lines as
indicated:

void G_SetStats(edict_t *ent)
{
gitem_t *item;
int index, cells;
int power_armor_type;


Pretty far down in this function find the 'timers' line...

//
// timers
//

----------ADD THESE LINES HERE ----------------

// Show Flyer's Total Health..
if (ent->vehicle) {
ent->client->ps.stats[STAT_TIMER_ICON]=gi.imageindex("k_pyramid");
ent->client->ps.stats[STAT_TIMER] = (short)ent->vehicle->health; }
else
-----------------------------------------------------------

if (ent->client->quad_framenum > level.framenum)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_quad");
ent->client->ps.stats[STAT_TIMER] =(ent->client->quad_framenum - level.framenum)/10;
}


This will keep the Hud display updated while you have a vehicle...

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

Lastly, we need to expand the display of the timer because
the timer only displays a max of 2 digits and the timer for
the cloaking can get alot higher than that. Quick change!

Got into g_spawn.c and find this declaration:

----- find this line here ----------

char *single_statusbar =

--- followed by alot of bullshit numbers and stuff ----

Follow the code down until you see the part for '// timer'
which should look exactly like this:

// timer
"if 9 "
" xv 262 "
" num 2 10 " <==== Change the 2 to a 3!!
" xv 296 "
" pic 9 "
"endif "

Make the change as indicated above!

Also, find this declaration further on down in the same file:

-------- find this line here --------

char *dm_statusbar =

Follow this one down until you see the part for the '// timer'
which looks like this:

// timer
"if 9 "
" xv 246 "
" num 2 10 " <==== Change the 2 to a 3!!
" xv 296 "
" pic 9 "
"endif "

Make the change as indicated above!

This will make the display 3 digits wide instead of 2..
======================================================

Okay, now open up your m_flyer.c file and find your flyer_die()
function and add these lines:

void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{

------------ ADD THESE LINES HERE ------------

// Rider for this Flyer?
if (self->rider) {
gi.centerprintf(self->rider,"FLYER DESTROYED\n");
self->rider->vehicle=NULL;
self->rider->is_riding=false;
gi.linkentity(self->rider); } // Immediately update world()..

------------------------------------------------------

This will tell the owner that the Flyer has died and turn
off some of the owner's special flags..

===========================================================
Now comes the easy part!!

Add all these new routines to the bottom of your m_flyer.c
file and you should be okay..

--------------------- START HERE ------------------
//======================================================
//============= FLYER VEHICLE ROUTINES =================
//======================================================

#define POWER1_SOUND gi.soundindex("misc/power1.wav")

//======================================================
void Teleport_Now(edict_t *ent) {
vec3_t spawn_origin={0,0,0},
spawn_angles={0,0,0};
vec3_t zvec=(0,0,0);
vec3_t up;

// Large particle effect at player's old spot
G_Spawn_Splash(TE_LASER_SPARKS, 24, 0xe2e5e3e6, ent->s.origin, zvec, ent->s.origin);

// Ent wants to jump on to the Flyer?
if (ent->vehicle && !ent->is_riding) {
// Drop ent slightly above the top of Vehicle.
AngleVectors(ent->vehicle->s.angles, NULL, NULL, up);
VectorMA(ent->vehicle->s.origin, 15, up, ent->s.origin);
// Facing in the same direction as vehicle.
VectorCopy(ent->vehicle->s.angles, ent->s.angles);
ent->is_riding=true; }
else {
ent->is_riding=false;
SelectSpawnPoint(ent, spawn_origin, spawn_angles);
ent->client->ps.pmove.origin[0] = spawn_origin[0]*8;
ent->client->ps.pmove.origin[1] = spawn_origin[1]*8;
ent->client->ps.pmove.origin[2] = spawn_origin[2]*8;
VectorCopy(spawn_origin, ent->s.origin); }

// Teleport particle effect at ent's new origin.
ent->s.event = EV_PLAYER_TELEPORT;

// play teleport sound effects.
gi.sound(ent, CHAN_VOICE, gi.soundindex("misc/tele1.wav"), 1, ATTN_NORM, 0);
}

//======================================================
// Owner = timer->owner
// Vehicle = timer->owner->vehicle
//======================================================
void Vehicle_Explode(edict_t *timer) {
vec3_t zvec={0,0,0};

if (timer->owner->vehicle)
T_Damage(timer->owner->vehicle, timer->owner, timer->owner, zvec, timer->owner->vehicle->s.origin, NULL, 500, 1, 0, MOD_SPLASH);

G_FreeEdict(timer);
}

//======================================================
void Timer_Think(edict_t *timer) {

if (timer->delay - 10 <= level.time) {
gi.centerprintf(timer->owner,"10 SECONDS TO DETONATION\n");
timer->think=Vehicle_Explode;
timer->nextthink = level.time + 10.0; } // 10 secs left!!
else
timer->nextthink = level.time + 1.0;
}

//======================================================
void Teleport_To_Vehicle(edict_t *timer) {

timer->delay = level.time + 170.0; // When to notify Rider..
timer->think=Timer_Think;
timer->nextthink=level.time + 1.0; // Start timer in 1 sec.
}

//======================================================
void Reset_Effects(edict_t *timer) {

// Restore some of Vehicle's states.
timer->owner->vehicle->s.effects=0; // Turn Off particle trail

// Set Timer to Teleport ent atop Vehicle
timer->think=Teleport_To_Vehicle;
timer->nextthink=level.time + 1.0; // 1 Secs to transport!
}

//=========================================================
void Spawn_Vehicle(edict_t *ent) {
edict_t *flyer, *timer;
vec3_t forward, up, torigin;
int temp;

// Start power-up launch sound now..
gi.sound(ent, CHAN_VOICE, POWER1_SOUND, 1, ATTN_IDLE, 0);

AngleVectors(ent->s.angles, forward, NULL, up);
VectorCopy(ent->s.origin, torigin);
VectorMA(torigin, 25, forward, torigin);

// Create basic entity stuff...
flyer = G_Spawn();
flyer->classname = "XVehicle"; // Visible(), Killed(), ClientObit()
flyer->rider = ent; // Link back to Owner.
VectorCopy(torigin, flyer->s.origin);
gi.linkentity(flyer);

//
// Now the Vehicle Stuff..
//

skill->value=3; // Toggle Advanced AI Mode.

temp=deathmatch->value;
deathmatch->value=0;

// Bat-like monster with wings
SP_monster_flyer(flyer);

deathmatch->value=temp; // Restore to previous value.

// Make some subtle changes to it's record..
flyer->monsterinfo.aiflags &= AI_BRUTAL;
flyer->health = 150;
flyer->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
flyer->monsterinfo.power_armor_power = 100;

// Physically throw the Flyer forward and up!
flyer->s.effects=EF_ROCKET;
VectorCopy(ent->s.angles, flyer->s.angles);
VectorCopy(forward, flyer->movedir);
VectorClear(flyer->velocity);
VectorMA(flyer->velocity, 800, forward, flyer->velocity);
VectorMA(flyer->velocity, 200, up, flyer->velocity);

timer=G_Spawn();
timer->owner=ent; // Link to Owner
timer->takedamage=DAMAGE_NO;
timer->solid = SOLID_NOT;
timer->movetype=MOVETYPE_NONE;
VectorClear(timer->s.origin);
VectorClear(timer->mins);
VectorClear(timer->maxs);
timer->think=Reset_Effects;
timer->nextthink=level.time + 0.5; // In 1/2 sec turn off effects;
gi.linkentity(timer);

ent->vehicle = flyer; // Link to this Vehicle.
ent->mynoise2=timer; // Link back to timer..
}

//======================================================
// Pay the Piper for your Radio Flyer
//======================================================
void Cmd_Vehicle_f(edict_t *ent) {
int index;

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

// Only 1 per customer.
if (ent->vehicle) {
Teleport_Now(ent);
return; }

// Well.. Make the damn thing already!
if (deathmatch->value) {
// 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 ((ent->client->pers.inventory[index] < 50) && (ent->client->resp.score < 3)){
gi.centerprintf(ent, "Flyer requires 50 PowerCells\n\nor 3 Frags to Launch!");
return; }
// Try to deduct the powercells first!
if (ent->client->pers.inventory[index] >= 50) {
ent->client->pers.inventory[index] -= 50;
Spawn_Vehicle(ent);
return; }
// Otherwise, if ent has enough frags,
if (ent->client->resp.score >= 3) {
ent->client->resp.score -= 3;
Spawn_Vehicle(ent);
return; }
} // endif
else {
// Single-Player price
if (ent->health < 25)
gi.centerprintf(ent, "Flyer costs 25 Health Units\n!");
else {
ent->health -= 25;
Spawn_Vehicle(ent); }
} // end else
}

--------------------- STOP HERE -------------------

===============================================================
Lastly..

Remember to bind a key in your Autoexec.cfg file as shown
by example below:

bind f "flyer"

That's it!! Ride this baby to the top of the FragList!!

And while you are riding atop of your new toy, KILL ALL
THOSE BASTARDS DOWN BELOW!

Have Fun!!

philip

philip
profile | email

posted 11-20-98 8:50 PM CT (US)
The standard weapon for the Flyer is the blaster (and it is not even the hyperblaster but the standard issue blaster!)

So, I was trying to figure out how I could switch the Flyer's weapon to a Chaingun (better to mow them down with) when it is
a vehicle and keep everything the same otherwise..

Here is how you do it!!

You need to add a new function to the very top of your g_monster.c file.. (and make sure you prototype it in you g_local.h file!)..

void monster_fire_chaingun(edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread)
{
fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_CHAINGUN);

gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte((MZ_CHAINGUN1+7));
gi.multicast(start, MULTICAST_PVS);
}

======================================================================
Okay, now find your void flyer_fire() function in your m_flyer.c file
and near the bottom make this change..

if (self->rider)
monster_fire_chaingun(self, start, dir, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD);
else
monster_fire_blaster(self, start, dir, 1, 1000, flash_number, effect);

This will turn ON your new monster chaingun weapon when the Flyer has a rider and the standard blaster will be used instead at all other times..

That's it!! Recompile and MOW THE BASTARDS DOWN!!

regards,
philip