|
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
|