CALLING STROGG AIRSHIP FOR AIRSTRIKE

 

 

philip
profile | email

posted 09-03-98 8:47 PM CT (US)
==================
Title: CALLING STROGG AIRSHIP FOR AIRSTRIKE
Difficulty: Moderate to Difficult
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net // please report any bugs(??)
Date: 9-2-98
Note: Please give credit where credit is due.

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

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.


========================================================
========================================================
========================================================
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 DMFLAGS_IS_INFINITE_AMMO ((int)dmflags->value & DF_INFINITE_AMMO)

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

#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 STROGG_SHIP_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 rockflysound gi.soundindex("weapons/rockfly.wav")

// Type of airstrike desired
#define ROCKET_BOMBS 0x00000001 // airstrike1
#define CLUSTER_BOMBS 0x00000002 // airstrike2
#define BFG_NUKE 0x00000003 // airstrike3

// Airstrike Global Substitutions.
#define ENTS_TIME_TO_AIRSTRIKE ent->client->airstrike_time
#define ENT_CALLED_AIRSTRIKE ent->client->airstrike_called
#define ENTS_AIRSTRIKE_TYPE ent->client->airstrike_type
#define ENTS_AIRSTRIKE_START ent->client->airstrike_start
#define ENTS_AIRSTRIKE_TARGETDIR ent->client->airstrike_targetdir

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

========================================================
========================================================
========================================================
Next, add the following variables to g_client struct.

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

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

Add the following to g_cmds.c

Add this external function declaration at some point
above the cmd_airstrike_f() function.

void Get_Target_Position(edict_t *ent, vec3_t endpos);

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

// Which airstrike type was called?
if (Q_stricmp(cmd, "airstrike1") == 0)
ENTS_AIRSTRIKE_TYPE=ROCKET_BOMBS;
else if (Q_stricmp(cmd, "airstrike2") == 0)
ENTS_AIRSTRIKE_TYPE=CLUSTER_BOMBS;
else if (Q_stricmp(cmd, "airstrike3") == 0)
ENTS_AIRSTRIKE_TYPE=BFG_NUKE;

// Deduct proper ammo amounts?
if (!DMFLAGS_IS_INFINITE_AMMO) {
index=ENTS_AMMO_INDEX;
switch (ENTS_AIRSTRIKE_TYPE) {
case CLUSTER_BOMBS:
if (ITEM_IN_ENTS_INVENTORY >= 10)
ITEM_IN_ENTS_INVENTORY -= 10;
else {
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 10 Grenades!!\n");
return; }
break;
case ROCKET_BOMBS:
if (ITEM_IN_ENTS_INVENTORY >= 6)
ITEM_IN_ENTS_INVENTORY -= 6;
else {
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 6 Rockets!!\n");
return; }
break;
case BFG_NUKE:
if (ITEM_IN_ENTS_INVENTORY >= 50)
ITEM_IN_ENTS_INVENTORY -= 50;
else {
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 50 PowerCells!!\n");
return; }
break;
} // switch
} // if

// Zero out the airstrike positioning vectors.
VectorCopy(start, ENTS_AIRSTRIKE_START);
VectorCopy(start, ENTS_AIRSTRIKE_TARGETDIR);

// cancel airstrike if it's already been called
if (ENT_CALLED_AIRSTRIKE) {
ENT_CALLED_AIRSTRIKE=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|CONTENTS_SLIME|CONTENTS_LAVA);

// 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|CONTENTS_SLIME|CONTENTS_LAVA);
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_CALLED_AIRSTRIKE=true;
ENTS_TIME_TO_AIRSTRIKE=PRESENT_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);
} // if
else
gi.cprintf(ent, PRINT_HIGH, "Target not acquired!! Retarget...\n");
}

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

Add into ClientCommand() (g_cmds.c) the following:

else if ((Q_stricmp(cmd, "airstrike1") == 0)
||(Q_stricmp(cmd, "airstrike2") == 0)
||(Q_stricmp(cmd, "airstrike3") == 0))
Cmd_Airstrike_f(ent,cmd); // AirStrikes

========================================================
============ P_CLIENT.C MODIFICATIONS ==================
========================================================
At the top of p_client.c put the following extern definition:

void spawn_aircraft(edict_t *ent);

============
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->client->airstrike_called=false;

============
Near the bottom of ClientThink() in p_client.c put the following

// Airstrike Called AND Time to launch AirStrike
if (ENT_CALLED_AIRSTRIKE && (PRESENT_TIME > ENTS_TIME_TO_AIRSTRIKE))
spawn_aircraft(ent);


========================================================
================ P_WEAPON.C MODIFICATIONS ==============
========================================================

At the top of p_weapon.c add in the following extern declarations:

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


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

At the bottom of p_weapon.c paste in the following code:

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

VectorCopy(ENTS_AIRSTRIKE_START, start);
VectorCopy(ENTS_AIRSTRIKE_TARGETDIR, targetdir);

switch (strike_type) {
case CLUSTER_BOMBS:
for (i=1;i<=10;i++)
drop_clusterbomb(ent, start, targetdir);
break;
case ROCKET_BOMBS:
drop_rocket_bomb(ent, start, targetdir, 400, 250);
drop_rocket_bomb(ent, start, targetdir, 500, 450);
drop_rocket_bomb(ent, start, targetdir, 600, 150);
drop_rocket_bomb(ent, start, targetdir, 700, 210);
drop_rocket_bomb(ent, start, targetdir, 800, 430);
drop_rocket_bomb(ent, start, targetdir, 800, 330);
break;
case BFG_NUKE:
fire_bfg(ent, start, targetdir, 200, 400, 1000);
break;
} // switch

// Clear out the airstrike positioning vectors.
VectorCopy(zvec, ENTS_AIRSTRIKE_START);
VectorCopy(zvec, ENTS_AIRSTRIKE_TARGETDIR);

ENT_CALLED_AIRSTRIKE=false;
}

//=====================================================
void craft_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(ENTS_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(STROGG_SHIP_MODEL);
craft->owner=ent;
craft->takedamage=DAMAGE_YES;
craft->touch=craft_touch;
craft->nextthink=PRESENT_TIME+30;
craft->think=G_FreeEdict;
gi.linkentity(craft);

launch_airstrike_salvo(ent,ENTS_AIRSTRIKE_TYPE);

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

//======================================================
//============ Airstrike Targeting Routine =============
//======================================================
void Get_Target_Position(edict_t *ent, vec3_t endpos) {
vec3_t zvec={0,0,0}, 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(ent->s.origin, start);
start[2] += ENTS_VIEW_HEIGHT;
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
VectorMA(start, MAX_WORLD_HEIGHT, forward, endpt);
tr=gi.trace(start, NULL, NULL, endpt, ent, MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA);

// 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|CONTENTS_SLIME|CONTENTS_LAVA);

// Do we have a clear line of fire?
if (gi.pointcontents(start) == CONTENTS_SOLID || tr_2.fraction < 1.0) {
// Clear out the airstrike positioning vectors.
ENT_CALLED_AIRSTRIKE=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; }

// Clear path to target - prepare for Airstrike.
VectorCopy(start, ENTS_AIRSTRIKE_START);
VectorCopy(targetdir, ENTS_AIRSTRIKE_TARGETDIR);
}


========================================================
============= G_WEAPON.C MODIFICATIONS =================
========================================================

At the bottom of g_weapon.c add the following code:

//======================================================
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);
// Alternate bomb model if you'd like something different..
// bomb->s.modelindex=gi.modelindex(BOMB_MODEL);
bomb->owner=shooter;
bomb->touch=rocket_touch;
bomb->nextthink=PRESENT_TIME+8000/speed;
bomb->think=G_FreeEdict;
bomb->dmg=damage;
bomb->radius_dmg=250;
bomb->dmg_radius=200;
bomb->s.sound=rockflysound;
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->touch=Grenade_Touch;
grenade->nextthink=PRESENT_TIME+timer;
grenade->think=Grenade_Explode;
grenade->dmg=damage;
grenade->dmg_radius=damage_radius;
grenade->classname="grenade";

gi.linkentity(grenade);
}

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

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

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

Have fun and kill all those bastards!!

regards,
Philip
aka Maj.Bitch

Veregon
profile | email

posted 09-06-98 3:24 PM CT (US)
Incase noone noticied, the airstrike takes ammo out of whatever weapon you are currently using. To fix that, change the Cmd_Airstrike_f as such:

// Deduct proper ammo amounts?
if (!DMFLAGS_IS_INFINITE_AMMO)
{
switch (ENTS_AIRSTRIKE_TYPE)
{
case CLUSTER_BOMBS:
index = ITEM_INDEX(FindItem("grenades"));
if (ent->client->pers.inventory[index] >= 10)
ITEM_IN_ENTS_INVENTORY -= 10;
else
{
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 10 Grenades!!\n");
return;
}
break;

case ROCKET_BOMBS:
index = ITEM_INDEX(FindItem("rockets"));
if (ent->client->pers.inventory[index] >= 6)
ITEM_IN_ENTS_INVENTORY -= 6;
else
{
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 6 Rockets!!\n");
return;
}
break;
case BFG_NUKE:
index = ITEM_INDEX(FindItem("cells"));
if (ent->client->pers.inventory[index] >= 50 )
ITEM_IN_ENTS_INVENTORY -= 50;
else
{
gi.cprintf(ent, PRINT_HIGH, "Airstrike requires 50 PowerCells!!\n");
return;
}
break;
} // switch
} // if

That should do it. Also, has anyone noticied that the BFG Airstrike crashes if you don't have the BFG selected when the BFG Ball hits something? I'm looking into this now also.