VIPER AIRSTRIKE 'AutoLauncher' II

 

 

philip
profile | email

posted 09-17-98 11:33 PM CT (US)
Title: VIPER AIRSTRIKE 'AutoLauncher' II
Difficulty: Difficult to Write: Easy to Implement!
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net // please report any bugs(??)
Date: 9-17-98
Note: Please give credit where credit is due.

=============================================================
In putting my bot detection stuff together I had to build
linked lists of players and a ghost entity. In the course
of putting all of this together I came across the idea of
trying to see if a ghost entity could initiate other
entities and cause things to happen. Well.. I awoke this
morning with the idea of having an entity autolaunch
random airstrikes at 1 minute intervals (just to keep'em
all hopping!!). I decided to take a much needed break
from the bot detection stuff and code this mod today.
Works great. Here's the post of my latest tutorial.

Everything from the previous airstrike stuff still works
with the addition of the RailStrike!! (Like a lightning
bolt out of the sky straight down into the top of a player's
head!!) Then, they explode of course!!

To all who have implemented my previous Viper Airstrike
Tutorial, I suggest that you put this entire code into your
mod and take out any other airstrike code that is already in
there from the other airstrike tutorials that I have posted.
The older coded tutorial will work with this one but you'll
have to chase down the changes yourself. Too many tweaks
to discuss them all!

Anyway,..

ABOUT NEW VIPER AUTOLAUNCHER TUTORIAL:

Every player which connects to the game is put onto a linked
list. This information is later used by an 'Auto-Launcher' as
discussed below.

There is an AutoLauncher entity which is spawned immediately
after the first player connects and begins the deathmatch.
The AutoLauncher has its own timer. On a pre-set timed
basis, the launcher scans the list of players to find one
at random. If that player is out in the open, (ie, there
is sky above the player at that moment in time) then the
AutoLauncher 'steals' that player's origin. If that player
is not out in the open then another player is selected at
random.

Once it has obtained an origin from an unsuspecting player, the
AutoLauncher then initiates a random airstrike of one of the
types (NUKE, CLUSTER, ROCKETS, RAILSTRIKE) onto that position

A message is sent to the entity which got their coordinates
'borrowed' letting them know that there is incoming! They
then have 3 seconds to get out of the way!! Else KABOOM!!

After an 'AutoLaunched' airstrike has commenced, the auto
launcher shuts down for 60 seconds then starts looking for
players out in the open again. If one is found, then
the process repeats itself. Else, the AutoLauncher waits
patiently for 5 seconds and then starts checking player
positions once again. The process repeats until one is found.

Also, I also added the RAILSTRIKE which fires a railslug into
the targeted position with radius damage at the point of
impact. This railslug will splatter a player on a direct
hit or kick them a country mile!!

Also, the cluster bombs and rocket bombs have been given
a larger 'spread' so that they all don't hit the ground
at the same spot. In other words, you better clear the
targeted area because, anything which is around is most
likely going to end up completely shredded!!

Also, I added obituaries to include kills from the airstrikes!
This was wholly absent from the previous tutorial on this topic.
This took quite a bit of re-engineering because you have to pass
around a MOD_ flag all the way down into the T_Damage() function
Sounds easy but it took alot just to get these little obits working.
Nice touch though!

Also, I found that my use of the ents airstrike_targetdir was
actually not needed!! I achieved the same thing by setting the
targetdir vector in launch_airstrike_salvo() to {0,0,-1};...
Interesting..

All the original Viper Airstrike features are still there so
airstrikes can be called by the players too!

Alot of minor tweaks and bug fixes.

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

This is a heavily modified version of the airstrike tutorial
posted on QDevels by Chris Hilton and another posted on QDevels
by Jonathan Benson. Many changes but they provided much
originality and inspiration.

About:

GREAT for getting entrenched campers out of high up, well
defended and otherwise unreachable places.

In this modification, a Strogg Viper airship is called in on a
targeted position. The player targets a position by pointing
their crosshair at a point (player, ground, building, wall, etc)
and then initiates the airstrike by hitting their airstrike
key (aliased in the player's autoexec.cfg: see below) which
radios the selected position to the Viper airship commander.

Once targeted, the ship commander issues a voice command letting
the player know that the position has been targeted and that
the airship is on its way. Exactly 10 seconds later, the
ship appears in the sky and drops the desired airstrike
salvo (cluster or rocket bombs) directly onto the targeted
position. Afterwards, the ship flies off into the sunset.

If, for any reason, the desired position cannot be targeted (ie,
the targeted position of the crosshair is pointing to someone or
something which is underneath something else and not directly
reachable from the sky) then the ship commander radios back to
the player to let them know that the target is not reachable from
the air and that a another position needs to be targeted.

The player must have the prerequisite ammo amounts in their possession
before the airstrike is called. If not, then the airstrike cannot
be completed.
For the Cluster_Bombs a total of 10 Grenades is required
For the Rocket_Bombs a total of 6 Rockets is required
For the BFG_NUKE a total of 50 PowerCells is required.
For the Lightning a total of 50 PowerCells is required.

The player gets the frag credits for all players killed in the
bombing raid either by direct hits or by radius damage.

GREAT for deathmatch levesl 'THE EDGE' and 'TOKAYS TOWERS'.

Features:
: Original Strogg Viper airship model used from standard Q2 files.
: Ship voice commands and communications are fully enabled.
: All sounds are standard Q2 files so no client pak files needed.
: Any target with sky overhead is bombable (even inside structures!).
: Airstrikes are launched in 10 sec. and can be called off.
: Two types of bombing raids (cluster or rockets) enabled.
: Cluster bombing raid drops 10 single bombs (which bounce around).
: Cluster bombs have random vectors for maximum bouncing spread.
: Cluster bombs have random radius damage ranges.
: Rocket bombing raid drops 6 airial bombs.
: Rocket bombs have differing velocities and radius damages.
: Rocket bombs 'float' down rotating sideways as in real life..
: Players can be killed by their own bombing raids!!
: Rocket 'flyby' sound gives victim notice of incoming ship.
: Many more subtleties. Try it out for yourself.
: BFG Nukes also selectable..
: RailStrikes are also selectable.


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

NOTE: You are responsible for the forward declarations.
If you don't know what that means then put these at the
bottom of your q_shared.h file..

// EntList Routines
void G_AddToEntList(edict_t *ent);
void G_RemoveFromEntList(edict_t *ent);
void G_ScrubEntList(void);
edict_t *G_GetRandomEnt(void);
qboolean G_EntInGame(edict_t *ent);
edict_t *G_GetBestScoreEnt(void);
edict_t *G_GetNextEnt(void);

// Airstrike Functions
void Get_Target_Position(edict_t *ent, vec3_t endpos);
void drop_clusterbomb(edict_t *shooter, vec3_t start, vec3_t aimdir);
void drop_rocket_bomb(edict_t *shooter, vec3_t start, vec3_t dir, int damage,int speed);
void launch_airstrike_salvo(edict_t *ent, int strike_type);
qboolean Launch_Airstrike(edict_t *ent, int cmd);
void spawn_aircraft(edict_t *ent);

// Airstrike firing Functions
void Fire_BFG(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);void Weapon_Grenade_Fire(edict_t *ent, qboolean held);
void Fire_BFG_Nuke(edict_t *shooter, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);
void Fire_RailStrike_Bolt(edict_t *shooter, vec3_t start, vec3_t aimdir, int damage, int kick);


void G_Spawn_Splash(int type, int count, int color, vec3_t start, vec3_t movdir, vec3_t origin);
void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin);
void G_Spawn_Sparks(int type, vec3_t start, vec3_t movdir, vec3_t origin);
void G_Spawn_Explosion(int type, vec3_t start, vec3_t origin);


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

Also: in some of my standard Q2 functions, I've added
cap's to some of the function names which will cause your
compiler not to find the function. If my function names
don't match your function names then, by all means, change
may names to work for your code!

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

Okay, Let's get started...

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

First, set up some global definitions by placing the following
in q_shared.h

#define MAX_WORLD_HEIGHT 8192

#define PRESENT_TIME level.time
#define FRAMETIME 0.1

#define MOD_ROCKET_BOMBS 0x00000023 // Rocket Airstrike
#define MOD_CLUSTER_BOMBS 0x00000024 // Cluster Airstrike
#define MOD_RAILSTRIKE 0x00000025 // BFG Nuke Airstrike
#define MOD_BFG_NUKE 0x00000026 // RailStrike

#define BOMB_MODEL "models/objects/bomb/tris.md2"
#define ROCKET_MODEL "models/objects/rocket/tris.md2"
#define GRENADE_MODEL "models/objects/grenade/tris.md2"
#define VIPER_MODEL "models/ships/viper/tris.md2"

// Global sound definitions.
#define PILOT1_SOUND gi.soundindex("world/pilot1.wav")
#define PILOT2_SOUND gi.soundindex("world/pilot2.wav")
#define PILOT3_SOUND gi.soundindex("world/pilot3.wav")
#define FLYBY1_SOUND gi.soundindex("world/flyby1.wav")
#define BFG_EXPLODE_SOUND gi.soundindex("weapons/bfg__x1b.wav")

// Type of airstrike desired
#define ROCKET_BOMBS 1 // airstrike1
#define CLUSTER_BOMBS 2 // airstrike2
#define BFG_NUKE 3 // airstrike3
#define RAILSTRIKE 4 // airstrike4

#define ENTS_VIEW_HEIGHT ent->viewheight
#define ENTS_V_ANGLE ent->client->v_angle
#define ENTS_S_ORIGIN ent->s.origin
#define ENTS_NETNAME ent->client->pers.netname

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

Add the following to the bottom of edict_t struct.

// AirStrike Variables
qboolean airstrike_called; // TRUE if Airstrike called
vec3_t airstrike_start; // Position of Targeted Entity
float airstrike_time; // Timer for incoming missiles
int airstrike_type; // 1=Rocket, 2=Cluster, etc.

========================================================
==========================================================
Okay, let's set up all the linked list stuff!!

==================
Put the following struct definition in your local.h

typedef struct ent_list_s {
edict_t *entptr;
struct ent_list_s *next;
} entlist;

This is the structure for the linked list routines...

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

Put this global variable in your local.h

extern qboolean launcher_spawned;

At declare it at the top of g_main.c like this:

qboolean launcher_spawned=false; // Default to Not Spawned.

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

At the bottom of q_utils.c paste in all of the following
(from start to end as marked below)..

-------------- CUT START -----------------

//======================================================
//=========== LINKED LIST ROUTINES =====================
//======================================================

entlist *ptr1=NULL,*ptr2=NULL; // ptr1 ALWAYS points to first rec.
int num_players=0;
entlist *nextptr=NULL;

//=========================================================
// Returns next non-NULL Entity in Linked List.
//=========================================================
edict_t *G_GetNextEnt(void) {
entlist *tptr=NULL;

if (!nextptr) nextptr=ptr1;

while (nextptr!=NULL) {
tptr=nextptr; // one to return;
nextptr=nextptr->next; // move to next rec in list.
if (nextptr==NULL)
nextptr=ptr1;
if (tptr->entptr!=NULL)
break;
} // end while

return tptr->entptr;
}

//=========================================================
// Returns a Random non-NULL Entity in Linked List.
//=========================================================
edict_t *G_GetRandomEnt(void) {
int i,rec;
double d;
float g;

d=10^(num_players < 4 ? num_players : 4);
g=random()*(float)d+0.5;
rec=(((int)g)%num_players)+1;

// gi.dprintf("int i = %d\n",i);

ptr2=ptr1; // Always start at first rec #1

for (i=2; i<=rec; i++)
ptr2=ptr2->next;

if (ptr2==NULL) ptr2=ptr1; // Little Safeguarding...

// gi.dprintf("Returning Random Ent: %s\n",ptr2->entptr->client->pers.netname);

return ptr2->entptr;
}

//=========================================================
// Returns a non-NULL Entity with Highest Score
//=========================================================
edict_t *G_GetBestScoreEnt(void) {
edict_t *bestptr=NULL;
int bestscore=-999;

ptr2=ptr1; // ptr2 ALWAYS starts at ptr1

// scan thru entire list..
while (ptr2!=NULL) {
if (ptr2->entptr->deadflag!=DEAD_DEAD)
if (ptr2->entptr->client->resp.score > bestscore) {
bestptr=ptr2->entptr; // found one. Can it be beat?
bestscore=ptr2->entptr->client->resp.score; }
ptr2=ptr2->next; }

return bestptr;
}

//=========================================================
// Entity is added to List in ClientBeginDeathmatch()
//=========================================================
void G_AddToEntlist(edict_t *ent) {
entlist *newptr;

// create the next rec in list
newptr = malloc(sizeof(entlist));
newptr->entptr = ent; // make the assignment
newptr->next = NULL; // tie off end pointer

if (ptr1==NULL) // if this is first rec then
ptr1 = newptr; // set ptr1 to point to rec
else {
ptr2=ptr1; // ptr2 ALWAYS starts at ptr1
while (ptr2->next!=NULL)
ptr2=ptr2->next; // move quickly to last rec.
ptr2->next=newptr;} // connect the dots..

num_players+=1; // add another player to list.
}

//=========================================================
// Entity is removed from List in ClientDisconnect()
//=========================================================
void G_RemoveFromEntList(edict_t *ent) {
entlist *tptr,*ptr3;

ptr3=ptr2=ptr1; // ptr2 ALWAYS starts at ptr1

// scan all links in list.
while (ptr2 != NULL) {
if (ptr2->entptr==ent) {
tptr=ptr2; // we're going to free() this record.
ptr2=ptr2->next; // bump to next rect
ptr3->next=ptr2; // connect the dots..
free(tptr); // put for garbage collection..
num_players-=1; // one less player on list.
return; } // Okay, we're done, so Exit
ptr3=ptr2;
ptr2=ptr2->next; // move to next rec in list
} // end while
}

//======================================================
// Returns true if ent is in the game and not DEAD
//======================================================
qboolean G_EntInGame(edict_t *ent) {

return ((ent->deadflag != DEAD_DEAD)
&& (ent->client->respawn_time + 10.0 < level.time));
}

//=========================================================
// Removes all Entities from EntList in EndDMLevel()
//=========================================================
void G_ScrubEntList(void) {
entlist *tptr;

ptr2=ptr1; // ptr2 ALWAYS starts at ptr1

// step across entire list.
while (ptr2 != NULL) {
tptr=ptr2;
ptr2=ptr2->next;
free(tptr); }

ptr1=NULL; // reset ptr to start of list.
num_players=0; // nobody on list anymore.
}

---------------- CUT STOP -------------------

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

Now, let's setup of all the linked list pieces of code that
need to be inserted here and there:

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

At the top of EndDMLevel() put the following line of code:

G_ScrubEntList(); // Entity List Cleanup


This will clean up the linked list at the end of each
deathmatch level.

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

In ClientBeginDeathmatch(), right after PutClientInServer();
put the following:

G_AddToEntlist(ent); // Add Entity to EntList


This will add each new player to the linked list as they
enter the deathmatch.

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

Put this in ClientDisconnect() before the printf as shown:

G_RemoveFromEntList(ent); // Remove from EntList

---- put it right before this line ---------

gi.bprintf(PRINT_HIGH, "%s disconnected\n", ENTS_NETNAME);


This will remove a player from the linked list when they
disconnect from the server.

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

Okay, that's all the linked list setup stuff..

Now, lets do the "AUTOLAUNCHER' routines..

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

Create a new file called g_airstrike.c and paste in ALL the
following from start to end as indicated..

Make sure that your #include's are correct for your
particular setup!!

------------ Start here ------------------

#include "xxxxxxx.h"
#include "xxxxxxx.h"

//======================================================
//============ Airstrike Targeting Routine =============
//======================================================
void Get_Target_Position(edict_t *ent, vec3_t endpos) {
vec3_t start={0,0,0}, forward={0,0,0},
endpt={0,0,0}, targetdir={0,0,0};
trace_t tr, tr_2;

// find the target's end point
VectorCopy(ENTS_S_ORIGIN, start);
start[2] += ENTS_VIEW_HEIGHT;
AngleVectors(ENTS_V_ANGLE, forward, NULL, NULL);
VectorMA(start, MAX_WORLD_HEIGHT, forward, endpt);
tr=gi.trace(start, NULL, NULL, endpt, ent, MASK_SHOT|MASK_LIQUID);

// find the direction from the entry point to the target
VectorSubtract(tr.endpos, endpos, targetdir);
VectorNormalize(targetdir);
VectorAdd(endpos, targetdir, start);

tr_2=gi.trace(start, NULL, NULL, tr.endpos, ent, MASK_SHOT|MASK_LIQUID);

// Do we have a clear line of fire?
if (CONTENTS_AT_POINT(start) == CONTENTS_SOLID || tr_2.fraction < 1.0) {
// Clear out the airstrike positioning vectors.
ent->airstrike_called=false; // Call off airstrike..
gi.cprintf(ent, PRINT_HIGH, "No Line-Of-Fire to Target!!\n");
gi.sound(ent, CHAN_ITEM, PILOT1_SOUND, 0.8, ATTN_NORM, 0);
return; }

VectorCopy(start, ent->airstrike_start);
}

//=====================================================
//=========== Airstrike 'Salvo' Routine ===============
//=====================================================
void launch_airstrike_salvo(edict_t *ent, int strike_type) {
vec3_t start={0,0,0},targetdir={0,0,-1};
int i;

VectorCopy(ent->airstrike_start, start);

switch (strike_type) {
case CLUSTER_BOMBS:
for (i=1;i<=10;i++) {
drop_clusterbomb(ent, start, targetdir);
start[0]+=i*20*random();
start[1]+=i*30*random(); }
break;
case ROCKET_BOMBS:
drop_rocket_bomb(ent, start, targetdir, 400, 250);
start[0]+=200*random();
start[1]+=200*random();
drop_rocket_bomb(ent, start, targetdir, 500, 450);
start[0]-=500*random();
start[1]+=500*random();
drop_rocket_bomb(ent, start, targetdir, 600, 150);
start[0]+=400*random();
start[1]-=400*random();
drop_rocket_bomb(ent, start, targetdir, 600, 210);
start[0]+=250*random();
start[1]+=250*random();
drop_rocket_bomb(ent, start, targetdir, 600, 430);
start[0]-=425*random();
start[1]+=425*random();
drop_rocket_bomb(ent, start, targetdir, 500, 330);
break;
case BFG_NUKE:
Fire_BFG_Nuke(ent, start, targetdir, 200, 800, 1000);
break;
case RAILSTRIKE:
Fire_RailStrike_Bolt(ent, start, targetdir, 50, 400);
break;
} // end switch

// Clear out the airstrike vector.
VectorClear(ent->airstrike_start);

ent->airstrike_called=false; // Airstrike Reset
}

//=====================================================
void AirCraft_Touch(edict_t *craft, edict_t *other, cplane_t *plane, csurface_t *surf){
G_FreeEdict(craft);
}

//======================================================
//============ Airstrike 'AirCraft' Routine ============
//======================================================
void spawn_aircraft(edict_t *ent) {
vec3_t start={0,0,0}, dir={0,1,0};
edict_t *craft=NULL;

VectorCopy(ent->airstrike_start, start);

craft=G_Spawn(); // Spawn Craft Entity
craft->classname="aircraft";
VectorCopy(start, craft->s.origin);
VectorCopy(dir, craft->movedir); // Craft Move direction
vectoangles(dir, craft->s.angles); // Vector angle of direction
craft->velocity[0]=0;
craft->velocity[1]=120; // pretty slow velocity...
craft->velocity[2]=0;
craft->clipmask=MASK_SHOT;
craft->movetype=MOVETYPE_FLYMISSILE;// Movetype = FLY
craft->solid=SOLID_BBOX; // Craft Body Box
VectorClear(craft->mins); // Must Clear these out
VectorClear(craft->maxs); // Must Clear these out.
craft->s.modelindex=gi.modelindex(VIPER_MODEL);
craft->owner=ent;
craft->takedamage=DAMAGE_NO;

craft->touch=AirCraft_Touch;
craft->think=G_FreeEdict;
craft->nextthink=level.time+30;

gi.linkentity(craft);

gi.sound(ent, CHAN_AUTO, FLYBY1_SOUND, 0.7, ATTN_NORM, 0);

launch_airstrike_salvo(ent, ent->airstrike_type);
}

//======================================================
void Viper_Think(edict_t *Viper) {

spawn_aircraft(Viper); // Launch the Viper's Craft!!

// After 50 Seconds, release Viper Entity.
Viper->think=G_FreeEdict;
Viper->nextthink=level.time + 50.0; // Keep Alive for Client Obits!!
}

//======================================================
edict_t *G_Spawn_Viper(edict_t *ent) {
edict_t *Viper;
int clientnum;

Viper=G_Spawn();
Viper->movetype= MOVETYPE_NONE;
Viper->classname="Viper";

clientnum=Viper-g_edicts-1;
Viper->client=&game.clients[clientnum];
sprintf(Viper->client->pers.netname, "Viper");

Viper->takedamage=DAMAGE_NO;
Viper->solid = SOLID_NOT;
Viper->clipmask = MASK_ALL;
Viper->model = "";
Viper->s.modelindex = 0;
Viper->s.modelindex2 = 0;
Viper->s.frame = 0;
Viper->s.effects = 0;
Viper->s.renderfx = 0;
VectorCopy(ent->s.origin, Viper->s.origin);
VectorClear(Viper->velocity);

Viper->use = NULL;
Viper->die = NULL;
Viper->touch = NULL;
Viper->think = NULL;
Viper->nextthink = 0;

return Viper;
}

//======================================================
qboolean Launch_Airstrike(edict_t *ent, int cmd) {
edict_t *Viper;
vec3_t start={0,0,0},
world_up={0,0,0},
endpt={0,0,0},
targetdir={0,0,0};
trace_t tr;

Viper=G_Spawn_Viper(ent);
gi.linkentity(Viper); // Must be here for some reason..

switch (cmd) {
case 4: Viper->airstrike_type=RAILSTRIKE; break;
case 3: Viper->airstrike_type=BFG_NUKE; break;
case 2: Viper->airstrike_type=CLUSTER_BOMBS; break;
case 1:
default:Viper->airstrike_type=ROCKET_BOMBS;
} // end switch

VectorClear(Viper->airstrike_start);
VectorCopy(ent->s.origin, start); // start at ent's position.
start[2] += ent->viewheight;
VectorSet(world_up, 0, 0, 1);
VectorMA(start, MAX_WORLD_HEIGHT, world_up, endpt);
tr=gi.trace(start, NULL, NULL, endpt, ent, MASK_SHOT|MASK_LIQUID);
if (tr.surface && !(tr.surface->flags & SURF_SKY)) {
G_FreeEdict(Viper);
return false;} // No direct path to ent->s.origin.

VectorSubtract(tr.endpos, start, targetdir);
VectorNormalize(targetdir);
VectorAdd(endpt,targetdir,start);

//-----------------------------------------------
// Direct path to target so go ahead with strike!
//-----------------------------------------------

VectorCopy(tr.endpos, Viper->airstrike_start);

// Warn of impending doom..
switch (cmd) {
case 4: gi.centerprintf(ent, "InComing RailStrike!!\n"); break;
case 3: gi.centerprintf(ent, "InComing BFG NUKE!!\n"); break;
case 2: gi.centerprintf(ent, "InComing Cluster Bombs!!\n"); break;
case 1:
default:gi.centerprintf(ent, "InComing Rocket Bombs!!\n");
} // end switch

Viper->think = Viper_Think;
Viper->nextthink =level.time + 5.0; // 5 Seconds 'till KABOOM!

return true;
}

//======================================================
void Launcher_Think(edict_t *Launcher) {
int strike_type;
edict_t *ent;

ent=G_GetRandomEnt();
while (!G_EntInGame(ent))
ent=G_GetRandomEnt();

// Randomly select: CLUSTER, ROCKET, BFG_NUKE, RAILSTRIKE
strike_type=(((int)(random()*10+0.5))%4)+1;

if (!Launch_Airstrike(ent, strike_type)) // Couldn't be launched?
Launcher->nextthink=level.time + 5.0; // Try again in 5 seconds.
else
Launcher->nextthink=level.time + 60.0; // Wait for 60 secs..
}

//=========================================================
void CreateLauncher(void) {
edict_t *Launcher;

Launcher=G_Spawn();

Launcher->movetype= MOVETYPE_NONE;
Launcher->classname="Launcher";
Launcher->takedamage=DAMAGE_NO;
Launcher->solid = SOLID_NOT;
Launcher->clipmask = MASK_ALL;
Launcher->model = "";
Launcher->s.modelindex = 0;
Launcher->s.modelindex2 = 0;
Launcher->s.frame = 0;
Launcher->s.effects = 0;
Launcher->s.renderfx = 0;
VectorClear(Launcher->s.origin);
VectorClear(Launcher->velocity);

Launcher->use = NULL;
Launcher->die = NULL;
Launcher->touch = NULL;

Launcher->think = Launcher_Think;
Launcher->nextthink =level.time + 10;

gi.linkentity(Launcher);

launcher_spawned=true; // only spawn once!
}

------------ End here --------------------

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

Okay, now let's get the AutoLauncher spawned!

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

Inside of PutClientInServer() add this single
line to the very bottom of the function:

if (!launcher_spawned) CreateLauncher();

This will spawn the AutoLauncher when the first
player connects.. That's it for the AutoLauncher.

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

Next, let's setup the player's ability to call
the airstrike themselves..


========================================================
============== G_CMDS.C MODIFICATINOS ==================
========================================================

Add the following function to g_cmds.c at a point above
the ClientCommand() function..

//======================================================
void Cmd_Airstrike_f(edict_t *ent) {
int index;
vec3_t start={0,0,0}, forward={0,0,0},
world_up={0,0,0}, end={0,0,0};
trace_t tr;

// Deduct proper ammo amounts?
if (!((int)dmflags->value & DF_INFINITE_AMMO))
switch (ent->airstrike_type) {
case CLUSTER_BOMBS:
index = ITEM_INDEX(FindItem("grenades"));
if (ent->client->pers.inventory[index] >= 10)
ent->client->pers.inventory[index] -= 10;
else {
gi.cprintf(ent, PRINT_HIGH, "Cluster strike requires 10 Grenades!!\n");
return; }
break;
case ROCKET_BOMBS:
index = ITEM_INDEX(FindItem("rockets"));
if (ent->client->pers.inventory[index] >= 6)
ent->client->pers.inventory[index] -= 6;
else {
gi.cprintf(ent, PRINT_HIGH, "Rocket strike requires 6 Rockets!!\n");
return; }
break;
case BFG_NUKE:
index = ITEM_INDEX(FindItem("cells"));
if (ent->client->pers.inventory[index] >= 50)
ent->client->pers.inventory[index] -= 50;
else {
gi.cprintf(ent, PRINT_HIGH, "BFG Nuke strike requires 50 PowerCells!!\n");
return; }
break;
case RAILSTRIKE:
index = ITEM_INDEX(FindItem("cells"));
if (ent->client->pers.inventory[index] >= 50)
ent->client->pers.inventory[index] -= 50;
else {
gi.cprintf(ent, PRINT_HIGH, "RailStrike requires 50 PowerCells!!\n");
return; }
break;
} // end switch

// Zero out the airstrike positioning vector.
VectorClear(ent->airstrike_start);
------------- INSERT PART II HERE ------------

philip
profile | email

posted 09-17-98 11:34 PM CT (US)
----------- Start Part II Here -----------

// cancel airstrike if it's already been called
if (ent->airstrike_called) {
ent->airstrike_called=false;
gi.cprintf(ent, PRINT_HIGH, "Airstrike has been called off!!\n");
gi.sound(ent, CHAN_ITEM, PILOT1_SOUND, 0.4, ATTN_NORM, 0);
return; }

// see if we're pointed at the sky
VectorCopy(ENTS_S_ORIGIN, start);
start[2] += ENTS_VIEW_HEIGHT;
AngleVectors(ENTS_V_ANGLE, forward, NULL, NULL);
VectorMA(start, MAX_WORLD_HEIGHT, forward, end);
tr=gi.trace(start, NULL, NULL, end, ent, MASK_SHOT|MASK_LIQUID);

// We hit something but it wasn't sky, see if there is sky above it!
if (tr.surface && !(tr.surface->flags & SURF_SKY)) {
VectorCopy(tr.endpos,start);
VectorSet(world_up, 0, 0, 1);
VectorMA(start, MAX_WORLD_HEIGHT, world_up, end);
tr=gi.trace(start, NULL, NULL, end, ent, MASK_SHOT|MASK_LIQUID);
if (tr.surface && !(tr.surface->flags & SURF_SKY)) {
gi.cprintf(ent, PRINT_HIGH, "No direct airstrike path to target!\n");
gi.sound(ent, CHAN_ITEM, PILOT1_SOUND, 0.4, ATTN_NORM, 0);
return; }
// Not pointing at sky and sky above so proceed with strike!
ent->airstrike_called=true;
ent->airstrike_time=level.time+10; // Time to Airstrike
Get_Target_Position(ent, tr.endpos);
gi.cprintf(ent, PRINT_HIGH, "Target Locked! ETA 10 secs.\n");
gi.sound(ent, CHAN_ITEM, PILOT2_SOUND, 0.8, ATTN_NORM, 0);
} // endif
else
gi.cprintf(ent, PRINT_HIGH, "Target not acquired!! Retarget...\n");
}

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

Add into ClientCommand() (near the bottom of g_cmds.c) the
following lines of code:

else if (Q_stricmp(cmd, "airstrike1") == 0) {
ent->airstrike_type=ROCKET_BOMBS;
Cmd_Airstrike_f(ent); }
else if (Q_stricmp(cmd, "airstrike2") == 0) {
ent->airstrike_type=CLUSTER_BOMBS;
Cmd_Airstrike_f(ent); }
else if (Q_stricmp(cmd, "airstrike3") == 0) {
ent->airstrike_type=BFG_NUKE;
Cmd_Airstrike_f(ent); }
else if (Q_stricmp(cmd, "airstrike4") == 0) {
ent->airstrike_type=RAILSTRIKE;
Cmd_Airstrike_f(ent); }

----- add right before this line below ------
else
Cmd_Say_f(ent, false, true);


This will add the ability to read the player's input commands..


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

Near the end of player_die() in p_client.c put the following:

NOTE: Victim here is either self or ent depending on how your
code reads. I have mine set up so that I know who the
victim was in the player_die function (for easy reading).

// AirStrikes - Dead can't call strikes
victim->airstrike_called=false;


This will turn off the airstrike if the originator has
since died while the airstrike is in progress.

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

At the bottom of ClientThink() put the following

// Airstrike Called AND Time to launch AirStrike
if (ent->airstrike_called && (level.time > ent->airstrike_time))
spawn_aircraft(ent);


This will start the airstrike after the player has
initiated one..

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

At the bottom of g_weapon.c add ALL the following code
from start to end as shown below:

------------------ Start Here----------------

//======================================================
void Bombs_Touch(edict_t *rocket, edict_t *target, cplane_t *plane, csurface_t *surf) {
vec3_t origin={0,0,0};

if (surf && (surf->flags & SURF_SKY)) return;

if (rocket->owner->client)
PlayerNoise(rocket->owner, rocket->s.origin, PNOISE_IMPACT);

// calculate position for the explosion entity
VectorMA(rocket->s.origin, -0.02, rocket->velocity, origin);

T_Damage(target, rocket, rocket->owner, rocket->velocity, rocket->s.origin,
plane->normal, rocket->dmg, 0, 0, MOD_ROCKET_BOMBS);

G_Spawn_Explosion(TE_ROCKET_EXPLOSION, origin, rocket->s.origin);

T_RadiusDamage(rocket, rocket->owner, rocket->radius_dmg, target, rocket->dmg_radius, MOD_R_SPLASH);

G_FreeEdict(rocket);
}

//======================================================
void drop_rocket_bomb(edict_t *shooter, vec3_t start, vec3_t dir, int damage,int speed){
edict_t *bomb=NULL;

bomb=G_Spawn();
VectorCopy(start, bomb->s.origin);
VectorCopy(dir, bomb->movedir);
vectoangles(dir, bomb->s.angles);
VectorScale(dir, speed, bomb->velocity);
bomb->movetype=MOVETYPE_FLY;
bomb->clipmask=MASK_SHOT;
bomb->solid=SOLID_BBOX;
bomb->s.effects |= EF_ROTATE;
VectorClear(bomb->mins);
VectorClear(bomb->maxs);
bomb->s.modelindex=gi.modelindex(ROCKET_MODEL);
// bomb->s.modelindex=gi.modelindex(BOMB_MODEL);
bomb->owner=shooter;
bomb->touch=Bombs_Touch;
bomb->nextthink=level.time+8000/speed;
bomb->think=G_FreeEdict;
bomb->dmg=damage;
bomb->radius_dmg=250;
bomb->dmg_radius=200;
bomb->s.sound=ROCKET_FLY_SOUND;
bomb->classname="rocket";

gi.linkentity(bomb);
}

//======================================================
void drop_clusterbomb(edict_t *shooter, vec3_t start, vec3_t aimdir) {
int damage=125;
float timer=7+crandom()*2.7;
float damage_radius=165;
edict_t *grenade=NULL;
vec3_t dir={0,0,0}, forward={0,0,0},
right={0,0,0}, up={0,0,0};

vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, up);

grenade=G_Spawn();
VectorCopy(start, grenade->s.origin);
VectorScale(aimdir, (int)(581+(crandom()*17.9)+(crandom()*89.3)), grenade->velocity);
VectorMA(grenade->velocity, (float)(83+(crandom()*13.7)), up, grenade->velocity);
VectorMA(grenade->velocity, (float)(57+(crandom()*19.1)), right, grenade->velocity);
VectorSet(grenade->avelocity, 277+crandom()*123.6, 333+crandom()*175.2, 313+crandom()*211.8);
grenade->movetype=MOVETYPE_BOUNCE;
grenade->clipmask=MASK_SHOT;
grenade->solid=SOLID_BBOX;
grenade->s.effects |= EF_GRENADE;
VectorClear(grenade->mins);
VectorClear(grenade->maxs);
grenade->s.modelindex=gi.modelindex(GRENADE_MODEL);
grenade->owner=shooter;

grenade->dmg=damage;
grenade->spawnflags=4; // Must be set for Obits!
grenade->dmg_radius=damage_radius;
grenade->classname="grenade";

grenade->touch=Grenade_Touch;
grenade->think=Grenade_Explode;
grenade->nextthink=level.time+timer;

gi.linkentity(grenade);
}

//======================================================
void BFG_Nuke_Touch(edict_t *bfg, edict_t *target, cplane_t *plane, csurface_t *surf) {

if (surf && (surf->flags & SURF_SKY)) return;

if (bfg->owner->client)
PlayerNoise(bfg->owner, bfg->s.origin, PNOISE_IMPACT);

gi.sound(bfg, CHAN_VOICE, BFG_EXPLODE_SOUND,1, ATTN_NORM, 0);
bfg->solid=SOLID_NOT;
bfg->touch=NULL;
VectorMA(bfg->s.origin, -1*0.1, bfg->velocity, bfg->s.origin);
VectorClear(bfg->velocity);
bfg->s.modelindex=gi.modelindex("sprites/s_bfg3.sp2");
bfg->s.frame=0;
bfg->s.sound=0;
bfg->s.effects &= ~EF_ANIM_ALLFAST;
bfg->think=bfg_explode;
bfg->nextthink=level.time + 0.1;
bfg->enemy=target;

T_Damage(target, bfg, bfg->owner, bfg->velocity, bfg->s.origin, plane->normal, 200, 0, 0, MOD_BFG_NUKE);

G_Spawn_Explosion(TE_BFG_BIGEXPLOSION, bfg->s.origin, bfg->s.origin);

T_RadiusDamage(bfg, bfg->owner, 100, target, 400, MOD_BFG_BLAST);
}

//======================================================
void Fire_BFG_Nuke(edict_t *shooter, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius){
edict_t *bfg=NULL; // new bfg entity

bfg=G_Spawn();
VectorCopy(start, bfg->s.origin);
VectorCopy(dir, bfg->movedir);
vectoangles(dir, bfg->s.angles);
VectorScale(dir, speed, bfg->velocity);
bfg->movetype=MOVETYPE_FLYMISSILE;
bfg->clipmask=MASK_SHOT;
bfg->solid=SOLID_BBOX;
bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
VectorClear(bfg->mins);
VectorClear(bfg->maxs);
bfg->s.modelindex=gi.modelindex("sprites/s_bfg1.sp2");
bfg->owner=shooter;

bfg->radius_dmg=damage;
bfg->dmg_radius=damage_radius;
bfg->classname="bfg blast";
bfg->s.sound=BFG_LAUNCH_SOUND;

bfg->touch=BFG_Nuke_Touch;
bfg->think=G_FreeEdict;
bfg->nextthink=level.time+8000/speed;

gi.linkentity(bfg);
}

//======================================================
void Fire_RailStrike_Bolt(edict_t *shooter, vec3_t start, vec3_t aimdir, int damage, int kick) {
vec3_t from={0,0,0}, end={0,0,0};
trace_t tr;
edict_t *ignore;
int mask;

VectorMA(start, MAX_WORLD_HEIGHT, aimdir, end);
VectorCopy(start, from);
mask=MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;

ignore=shooter;

while (ignore) {
tr=gi.trace(from, NULL, NULL, end, ignore, mask);
if (tr.contents & MASK_LIQUID)
mask &= ~MASK_LIQUID;
else {
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client!=NULL))
ignore=tr.ent;
else
ignore=NULL;
if (tr.ent->takedamage==DAMAGE_YES)
T_Damage(tr.ent, shooter, shooter, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILSTRIKE);
} // end else
VectorCopy(tr.endpos, from);
} // endif

G_Spawn_Trails(TE_RAILTRAIL, start, tr.endpos, shooter->s.origin);

// send muzzle flash
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(shooter-g_edicts);
gi.WriteByte(MZ_RAILGUN);
gi.multicast(tr.endpos, MULTICAST_PHS);

PlayerNoise(shooter, start, PNOISE_WEAPON);

G_Spawn_Sparks(TE_SHIELD_SPARKS, tr.endpos, tr.plane.normal, tr.endpos);

T_RadiusDamage(tr.ent, shooter, 100, NULL, damage, MOD_SPLASH);
}

------------------ End Here----------------

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

Okay, almost done. Just some misc. stuff here and there.

Inside of grenade_explode() modify it to add this after
these two lines..

points=grenade->dmg - 0.5 * VectorLength(v);
VectorSubtract(grenade->enemy->s.origin, grenade->s.origin, dir);

//-----after the above lines, make the code read like this...

if (grenade->spawnflags & 4) //<=== new line
mod=MOD_CLUSTER_BOMBS; //<=== new line
else //<=== new line
if (grenade->spawnflags & 1)
mod=MOD_HANDGRENADE;
else
mod=MOD_GRENADE;


Okay, this will add the right obituaries to the cluster bomb
victims.

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

Okay, let's move on to the obituaries page...

Inside of ClientObituaries() right after these lines:

case MOD_BFG_BLAST:
message="should have used a smaller gun";
break;

------- add this right here -------------

// ADD: airstrike obituaries for self..
case MOD_CLUSTER_BOMBS:
if (IsFemale(victim))
message="caught her own Cluster Bombs.";
else
message="caught his own Cluster Bombs.";
break;
case MOD_ROCKET_BOMBS:
if (IsFemale(victim))
message="caught her own Rocket Bombs.";
else
message="caught his own Rocket Bombs.";
break;
case MOD_BFG_NUKE:
if (IsFemale(victim))
message="caught her own BFG Nuke.";
else
message="caught his own BFG Nuke.";
break;
case MOD_RAILSTRIKE:
message="got struck by RAILSTRIKE";
break;
// ADD airstrike obituaries
-------- stop here ---------------


Okay, in the same function but further down, do this:

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

------ Add this right here --------------

// ADD airstrike obituaries victim
case MOD_CLUSTER_BOMBS:
message="was shredded by";
message2="'s Cluster bombs";
break;
case MOD_ROCKET_BOMBS:
message="swallowed";
message2="'s Rocket bombs";
break;
case MOD_BFG_NUKE:
message="was obliterated by";
message2="'s Nuke airstrike";
break;
case MOD_RAILSTRIKE:
message="was toasted by";
message2="'s RailStrike";
break;
// ADD airstrike obituaries

--------- Stop here ---------------

These lines will add the right obituaries to
the victims of the airstrikes!!

============================================
On more thing in the ClientObituary() to do!

At the bottom of that function, make the code
read like this:

if (message) {
if (attacker!=NULL && attacker->client!=NULL) { // <== new line here
gi.bprintf(PRINT_MEDIUM,"%s %s %s%s\n",
target->client->pers.netname, message,
attacker->client->pers.netname, message2);
if (ff)
attacker->client->resp.score--;
else
attacker->client->resp.score++;
return;
} // endif


IMPORTANT!!

This will stop the program from crashing if the Viper entity
is G_FreeEdict()'d before the execution of the gi.bprintf()
which wants to reference the 'attacker->client' which no
longer exists!! CRASH!!! So, if for some reason the attacker
(VIPER) entity is no longer around, this printf just gets
skipped!!

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

At the bottom of g_spawn.c add the following function from
the start to end..

----------------- Start Here -------------------

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

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

------------------ End Here -------------------

These are some helper routines which I put together to localize
the spawning of various TE_ temp entity types so often used
in the weapon firing code.


============================================================
================= AUTOEXEC.CFG STUFF =======================
============================================================
Inside of your autoexec.cfg file be sure to include this:

Bind 4 keys to the following as in the example below:

bind F3 "airstrike1" // Rocket Bombs
bind F4 "airstrike2" // Cluster Bombs
bind F5 "airstrike3" // BFG Nuke
bind F5 "airstrike4" // RAILSTRIKE

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

Wow! You made it!! Okay, compile this baby and blast'em all
straight to HELL!! But... Watch out over your head because
the STROGG will AirStrike you if you're out in the open too
long!!

Have fun and kill all those bastards!!

Please email or post any bugs!!

regards,
Philip
aka Maj.Bitch