Adding Random Airstrikes to your mod


 

Posted by Maj.Bitch (12.145.208.*) at 10:24 AM, 3/5/2001:


This is a repost of one of my earlier tutorials. I've tested everything out and it all works. Here is how it works.. After all the items are put into the map by SpawnEntities() then an autolauncher entity is created which exists solely for activating random airstrikes. When the autolauncher is active, it scans thru all the players in the game and tests which, if any, have an origin with sky directly above because it can't bomb indoor locations from the sky. However, be aware that some indoor maps have buildings which have skylights and such which are open to the sky so, if the Viper targets a player's indoor position (because sky happens to exist directly above that location) it will drop its payload thru that opening and blast everything inside the building around that location!

Once it finds a bombable position, then it will conduct an airstrike onto that location. The autolauncher does some computations to find the farthest point over the horizon from that target position to start the Viper Craft from. We want the farthest over the horizon location so the Viper flies across the sky for the longest period of time (thus giving players the best chance of shooting the Viper down and get the 3-Frag award!).

Once the horizon location is found, then a random type of airstrike is selected (bomb, nuke, railstrike, laserstrike, (create your own)). After the strike type is selected, then over the horizon will appear one of those Viper Craft (from single-player). It will fly to a point directly above the selected origin and stop for 2 seconds, then release its payload and zoom off into the sky.

The Viper can be killed at anytime but be aware that it has a 500 health (adjustable) so it is hard to kill. If you do kill it then it explodes into a fireball in the sky and chunks of debris rain down out of the fireball. These chunks will explode on impact with a player (causing direct damage) or the ground (causing radius damage) so run for cover!

Nobody gets the frags for the kills the Viper makes so nobody is at an advantage.

Great addition for those maps with outdoor locations with sky above. Really adds a new dimension to the gameplay! Check it out!

First, open your g_local.h file and add these new obituary flags to the list of existing MOD_* flags like so:

<C&NBSP;CODE>
#define MOD_HIT             32
#define MOD_TARGET_BLASTER  33

// ----- Add these new flags right here ------------
#define MOD_BOMBSTRIKE      34 // Bomb strike
#define MOD_RAILSTRIKE      35 // BFG Nuke Airstrike
#define MOD_LASERSTRIKE     36 // Laser Airstrike
#define MOD_NUKESTRIKE      37 // Railgun Airstrike
//---------------------------------------------------
</C&NBSP;CODE>

Next, add these new messages to your ClientObituary() function in your p_client.c file:

<C&NBSP;CODE>
void ClientObituary(edict_t *self,edict_t *inflictor,edict_t *attacker) {
qboolean ff=false;
int mod=meansOfDeath;
char *message='\0';
char *message2='\0';

  switch (mod) {
//----- airstrike obituary messages --------
    case MOD_BOMBSTRIKE:
      message="was bombed by a Viper";
      break;
    case MOD_RAILSTRIKE:
      message="was railed by a Viper";
      break;
    case MOD_LASERSTRIKE:
      message="got toasted by a Viper";
      break;
    case MOD_NUKESTRIKE:
      message="was nuked by a Viper";
      break;
//----------------------------------------
    case MOD_SUICIDE:
      message="suicides";
      break;
</C&NBSP;CODE>

Feel free to change the obituary messages to what is appropriate for your particular mod.

Next, add this to the bottom of your SpawnEntities() function in your g_spawn.c file:
<C&NBSP;CODE>
  G_FindTeams();

// ADD THESE LINES HERE - 
  // Skip these maps because they are mainly indoor maps!
  if (!strcmp(level.mapname,"q2dm2")) return; // Tokay Towers
  if (!strcmp(level.mapname,"q2dm3")) return; // FragPipe
  if (!strcmp(level.mapname,"q2dm6")) return; // Slimy Place
  if (!strcmp(level.mapname,"q2dm8")) return; // Warehouse

  Init_AutoLauncher();
}
</C&NBSP;CODE>

I put this code in as an example of how NOT to initialize the autolauncer on maps with little or no sky above. However, Tokay's Towers DOES have small sections with sky above so feel free to delete that one..

Be sure to prototype Init_AutoLauncher() function either above SpawnEntities() or in your files.h file like so:
<C&NBSP;CODE>
void Init_AutoLauncher(void); 
</C&NBSP;CODE>

Lastly, create a new file called g_airstrike.c and paste in all the following:

<C&NBSP;CODE>
#include "g_local.h"

//======================================================
//============ RANDOM AIRSTRIKE ROUTINES ===============
//======================================================

//============================================================
//================ DEBRIS HANDLING ROUTINES ==================
//============================================================

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

//============================================================
void Chunk_Touch(edict_t *chunk,edict_t *other,cplane_t *plane,csurface_t *surf) {

  G_Spawn_Explosion(TE_GRENADE_EXPLOSION,chunk->s.origin);

  T_Damage(other,chunk,world,zvec,chunk->s.origin,zvec,chunk->dmg,0,0,MOD_HIT);

  T_RadiusDamage(chunk,world,chunk->radius_dmg,NULL,chunk->dmg_radius,MOD_SPLASH);

  G_FreeEdict(chunk);
}

//============================================================
void CreateDebrisChunk(edict_t *craft,char *modelname,float speed) {

  //==============================
  // Spawn single chunk of debris
  //==============================

  edict_t *chunk=G_Spawn();
  chunk->owner=world; // world ownership

  gi.setmodel(chunk,modelname);

  VectorCopy(craft->s.origin,chunk->s.origin); // debris starts at fireball origin

  // Makes each chunk fly out of fireball in different directions
  VectorSet(chunk->velocity,100*crandom(),100*crandom(),100+100*crandom());
  VectorScale(chunk->velocity,speed,chunk->velocity);

  chunk->solid=SOLID_BBOX;
  VectorSet(chunk->mins,-8,-8,-8);
  VectorSet(chunk->maxs,+8,+8,+8);

  chunk->movetype=MOVETYPE_BOUNCE; // fall and bounce on impact
  chunk->takedamage=DAMAGE_NO;     // debris not damageable

  chunk->dmg=30;         // do 30 units damage on impact
  chunk->radius_dmg=30;  // do 30 units radius damage
  chunk->dmg_radius=120; // apply radius damage to all within 120 radius

  chunk->touch=Chunk_Touch; // Explode upon impact

  gi.linkentity(chunk);
}

//======================================================
//=========== VIPER CRAFT PAYLOAD FUNCTIONS ============
//======================================================

//======================================================
void fire_laserstrike(edict_t *craft) {
  for (int i=1; i<=game.maxclients; i++) {
    if (!G_ClientInGame(&g_edicts[i])) continue; // skip dead or spectating players
    if (!G_ClearPath(craft->s.origin,g_edicts[i].s.origin)) continue;
    G_Spawn_Trails(TE_BFG_LASER,craft->s.origin,g_edicts[i].s.origin);
    T_Damage(&g_edicts[i],craft,world,zvec,craft->s.origin,zvec,50,1,0,MOD_LASERSTRIKE); }
}

//======================================================
void fire_railstrike(edict_t *craft) {
  for (int i=1; i<=game.maxclients; i++) {
    if (!G_ClientInGame(&g_edicts[i])) continue; // skip dead or spectating players
    if (!G_ClearPath(craft->s.origin,g_edicts[i].s.origin)) continue;
    G_Spawn_Trails(TE_RAILTRAIL,craft->s.origin,g_edicts[i].s.origin);
    T_Damage(&g_edicts[i],craft,world,zvec,craft->s.origin,zvec,50,1,0,MOD_RAILSTRIKE); }
}

//======================================================
void bomb_touch(edict_t *rocket,edict_t *other,cplane_t *plane,csurface_t *surface) {
  G_Spawn_Explosion(TE_ROCKET_EXPLOSION,rocket->s.origin);
  T_Damage(other,rocket,world,rocket->velocity,rocket->s.origin,plane->normal,rocket->dmg,0,0,MOD_BOMBSTRIKE);
  T_RadiusDamage(rocket,world,rocket->radius_dmg,rocket->owner,rocket->dmg_radius,MOD_BOMBSTRIKE);
  G_FreeEdict(rocket);
}

//======================================================
void drop_bomb(edict_t *craft) {
  edict_t *bomb=G_Spawn();
  bomb->owner=craft;
  bomb->s.modelindex=gi.modelindex("models/objects/bomb/tris.md2"); // bomb model
  bomb->movetype=MOVETYPE_BOUNCE;
  bomb->clipmask=MASK_SHOT;
  bomb->solid=SOLID_BBOX;
  bomb->s.effects |= EF_ROTATE;
  VectorCopy(craft->s.origin,bomb->s.origin);
  VectorCopy(tv(0,0,-1),bomb->movedir);
  vectoangles(tv(0,0,-1),bomb->s.angles);
  VectorClear(bomb->velocity);
  bomb->velocity[2]=-400;
  bomb->dmg=80+(int)(random()*100);
  bomb->radius_dmg=120;
  bomb->dmg_radius=300;
  bomb->touch=bomb_touch; // explode on impact
  gi.linkentity(bomb);
}

//======================================================
// These bfg funcs are duplicates of standard Q2 bfg
// funcs because we want to use our own obituary flag.
// Otherwise, we could've easily used fire_bfg()..
//======================================================

void bfg_explode(edict_t *);

//======================================================
void bfg_touch2(edict_t *bfg,edict_t *other,cplane_t *plane,csurface_t *surf) {

  if (!plane || !bfg || (other==bfg->owner)) return;

  T_Damage(other,bfg,bfg->owner,bfg->velocity,bfg->s.origin,plane->normal,200,0,0,MOD_NUKESTRIKE); // own obit flag!

  T_RadiusDamage(bfg,bfg->owner,200,other,100,MOD_NUKESTRIKE); // own obit flag!

  gi.sound(bfg,2,gi.soundindex("weapons/bfg__x1b.wav"),1,1,0);
  bfg->s.modelindex=gi.modelindex("sprites/s_bfg3.sp2");
  bfg->solid=SOLID_NOT;
  bfg->touch=NULL;
  VectorMA(bfg->s.origin,-0.1,bfg->velocity,bfg->s.origin);
  VectorClear(bfg->velocity);
  bfg->s.frame=0;
  bfg->s.sound=0;
  bfg->s.effects &= ~EF_ANIM_ALLFAST;
  bfg->enemy=other;

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

  bfg->think=bfg_explode;
  bfg->nextthink=level.time + FRAMETIME;
}

//======================================================
void bfg_think2(edict_t *bfg) {
edict_t *ignore=NULL;
edict_t *ent=NULL;
vec3_t point,dir,start,end;
trace_t tr;

  while ((ent=findradius(ent,bfg->s.origin,256))) {
    if (!G_ClientInGame(ent)) continue;
    if ((ent==bfg) || (ent==bfg->owner)) continue;
    VectorMA(ent->absmin,0.5,ent->size,point);
    VectorSubtract(point,bfg->s.origin,dir);
    VectorNormalize(dir);
    ignore=bfg;
    VectorCopy(bfg->s.origin,start);
    VectorMA(start,2048,dir,end);
    while (1) {
      tr=gi.trace(start,NULL,NULL,end,ignore,(CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER));
      if (!tr.ent) break;
      if (!(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent!=bfg->owner))
        T_Damage(tr.ent,bfg,bfg->owner,dir,tr.endpos,zvec,5,1,DAMAGE_ENERGY,MOD_NUKESTRIKE); // own obit flag!
      if (!(tr.ent->svflags & SVF_MONSTER) && !tr.ent->client) {
        G_Spawn_Splash(TE_LASER_SPARKS,4,bfg->s.skinnum,tr.endpos,tr.plane.normal);
        break; }
      ignore=tr.ent;
      VectorCopy(tr.endpos,start); }
      G_Spawn_Trails(TE_BFG_LASER,bfg->s.origin,tr.endpos); }

  bfg->nextthink=level.time + FRAMETIME;
}

//======================================================
void fire_bfg2(edict_t *craft,vec3_t start,vec3_t dir,int damage,int speed,float radius) {

  edict_t *bfg=G_Spawn();
  bfg->owner=craft;
  bfg->classname="bfg blast";
  bfg->s.modelindex=gi.modelindex("sprites/s_bfg1.sp2");
  bfg->s.sound=gi.soundindex("weapons/bfg__l1a.wav");
  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->radius_dmg=damage;
  bfg->dmg_radius=radius;
  bfg->teammaster=bfg;
  bfg->teamchain=NULL;

  bfg->touch=bfg_touch2; // own touch func
  bfg->think=bfg_think2; // own think func

  bfg->nextthink=level.time + FRAMETIME;

  gi.linkentity(bfg);
}

//======================================================
void fire_bfgnuke(edict_t *craft) {
  // Fire BFG Fireball straight downward
  fire_bfg2(craft,craft->s.origin,tv(0,0,-1),200,400,1000);
}

//=====================================================
//============= VIPER AIRCRAFT ROUTINES ===============
//=====================================================

void ViperCraft_Think(edict_t *);

//=====================================================
// Die function is called from Killed()
//=====================================================
void ViperCraft_Die(edict_t *craft,edict_t *inflictor,edict_t *attacker,int damage,vec3_t point) {

  // Craft was killed by somebody!!
  G_Spawn_Explosion(TE_EXPLOSION1,craft->s.origin); // Explode into fireball

  // Rain down chunks of debris!!
  CreateDebrisChunk(craft,DEBRIS2_MODEL,3.75);
  CreateDebrisChunk(craft,DEBRIS3_MODEL,2.50);
  CreateDebrisChunk(craft,DEBRIS2_MODEL,4.60);
  CreateDebrisChunk(craft,DEBRIS3_MODEL,1.50);
  CreateDebrisChunk(craft,DEBRIS2_MODEL,2.30);

  G_FreeEdict(craft); // Make craft disappear

  // Award 3 frags to attacker for downing Craft-if player still alive
  if (G_ClientInGame(attacker)) {
    gi_centerprintf(attacker,"3 Frags for killing Viper!\n");
    attacker->client->resp.score += 3; }
}

//=====================================================
void ViperCraft_Touch(edict_t *craft,edict_t *other,cplane_t *plane,csurface_t *surface) {
  if (other && !stricmp(other->classname,"worldspawn"))
    G_FreeEdict(craft); // Hit world/sky so make Craft vanish!
}

//======================================================
void ViperCraft_Flyaway(edict_t *craft) {
  VectorScale(craft->movedir,300,craft->velocity); // zoom away along movedir
}

//======================================================
void drop_payload(edict_t *craft) {

  // Select randomized strike type
  switch ((int)(rand()%3)) {
    case 0: drop_bomb(craft); break;       // drop a bomb
    case 1: fire_bfgnuke(craft); break;    // fire BFG Fireball
    case 2: fire_railstrike(craft); break; // fire railslugs
    default:fire_laserstrike(craft); }     // fire laserbeam

  craft->think=ViperCraft_Flyaway; // Craft zooms off
  craft->nextthink=level.time + 1.0;
}

//======================================================
void ViperCraft_Think(edict_t *craft) {

  if (VectorSubtractLen(craft->s.origin,craft->pos2)<10) {
    VectorClear(craft->velocity); // Stop forward movement
    craft->think=drop_payload;    // Drop payload in 2 secs
    craft->nextthink=level.time + 2.0;
    return; }

  craft->think=ViperCraft_Think;
  craft->nextthink=level.time + FRAMETIME;
}

//======================================================
void Spawn_ViperCraft(edict_t *autolauncher) {
vec3_t forward;
vec3_t angle,vdir;

  edict_t *craft=G_Spawn();
  craft->owner=autolauncher;
  craft->s.modelindex=gi.modelindex("models/ships/viper/tris.md2");
  craft->s.sound=gi.soundindex("world/flyby1.wav");

  craft->health=500; // Make Craft harder to kill (Titanium Hull!) - ADJUSTABLE
  craft->max_health=500;
  craft->takedamage=DAMAGE_AIM;
  craft->clipmask=MASK_SHOT;
  craft->movetype=MOVETYPE_FLYMISSILE;
  craft->solid=SOLID_BBOX;
  VectorSet(craft->mins,-30,-30,-30);
  VectorSet(craft->maxs,+30,+30,+30);

  VectorCopy(autolauncher->pos1,craft->pos1); // Start of Viper ship
  VectorCopy(craft->pos1,craft->s.origin);
  VectorCopy(autolauncher->pos2,craft->pos2); // Targeted location
  VectorSubtract(craft->pos2,craft->pos1,vdir);
  vectoangles(vdir,angle);
  AngleVectors(angle,forward,NULL,NULL);
  VectorCopy(forward,craft->movedir);      // Craft flies in forward direction
  vectoangles(forward,craft->s.angles);    // Turn craft facing direction
  VectorScale(forward,100,craft->velocity);// This velocity is pretty fast..

  craft->touch=ViperCraft_Touch; // called when Touched

  craft->die=ViperCraft_Die; // called when Killed

  craft->think=ViperCraft_Think;
  craft->nextthink=level.time + FRAMETIME;

  gi.linkentity(craft);
}

//======================================================
// torigin is 10 units below sky (directly above target)
//======================================================
void GetHorizonVector(vec3_t torigin,vec3_t result) {
vec3_t end,start1,start2;

  // trace north.
  VectorMA(torigin,8192,tv(0,1,0),end);
  trace_t trN=gi.trace(torigin,NULL,NULL,end,NULL,MASK_ALL);

  // trace south.
  VectorMA(torigin,8192,tv(0,-1,0),end);
  trace_t trS=gi.trace(torigin,NULL,NULL,end,NULL,MASK_ALL);

  // trace east.
  VectorMA(torigin,8192,tv(1,0,0),end);
  trace_t trE=gi.trace(torigin,NULL,NULL,end,NULL,MASK_ALL);

  // trace west.
  VectorMA(torigin,8192,tv(-1,0,0),end);
  trace_t trW=gi.trace(torigin,NULL,NULL,end,NULL,MASK_ALL);

  //------------------------------------------------------
  // Now we want to find the farthest horizon from start
  // so, Viper can be visible in sky for the longest time.
  //------------------------------------------------------

  //--------------------------------------
  // Get longest vector in N/S direction
  //--------------------------------------
  if (VectorSubtractLen(trN.endpos,torigin)>VectorSubtractLen(trS.endpos,torigin))
    VectorCopy(trN.endpos,start1);
  else
    VectorCopy(trS.endpos,start1);

  //--------------------------------------
  // Get longest vector in E/W direction
  //--------------------------------------
  if (VectorSubtractLen(trE.endpos,torigin)>VectorSubtractLen(trW.endpos,torigin))
    VectorCopy(trE.endpos,start2);
  else
    VectorCopy(trW.endpos,start2);

  //--------------------------------------
  // Now,which is longest of these two?
  //--------------------------------------

  // Finally! the start vector!
  if (VectorSubtractLen(start1,torigin)>VectorSubtractLen(start2,torigin))
    VectorCopy(start1,result);
  else
    VectorCopy(start2,result);
}

//======================================================
qboolean CheckPosition(edict_t *autolauncher,edict_t *ent) {

  trace_t tr=gi.trace(ent->s.origin,ent->mins,ent->maxs,tv(ent->s.origin[0],ent->s.origin[1],8192),ent,MASK_ALL);
  if (!tr.surface || !(tr.surface->flags & SURF_SKY)) return false;

  // Payload drop will occur at this location above target
  // NOTE: pos2 is used to store target origin
  VectorCopy(tr.endpos,autolauncher->pos2);
  autolauncher->pos2[2] -= 10; // lower a bit

  // Get Viper craft's starting location over horizon
  // NOTE: pos1 is used to store craft start..
  GetHorizonVector(autolauncher->pos2,autolauncher->pos1);

  return true;
}

//======================================================
qboolean PositionCanBeTargeted(edict_t *autolauncher) {

  // Find first origin with sky directly above
  for (int j=1; j<=game.maxclients; j++)
    if (G_ClientInGame(&g_edicts[j]))
      if (CheckPosition(autolauncher,&g_edicts[j]))
        return true;

  return false; // everybody indoors!
}

//======================================================
void Launcher_Think(edict_t *autolauncher) {

  if (!PositionCanBeTargeted(autolauncher))
    autolauncher->nextthink = level.time + 10.0; // try again in 10 secs.
  else {
    //=========================================
    // Okay!  Got valid origin with sky above!
    //=========================================
    // Set Viper airstrike in motion
    Spawn_ViperCraft(autolauncher);
    // Reset autolauncher timer for next random strike
    autolauncher->nextthink = level.time + 60 + (60*(rand()%2)); } // [1 min to 1+2=3 min]
}

//======================================================
void Init_AutoLauncher(void) {

  edict_t *autolauncher=G_Spawn();
  autolauncher->owner=world;
  autolauncher->svflags=SVF_NOCLIENT;

  autolauncher->think = Launcher_Think;
  autolauncher->nextthink = level.time + 60.0; // Start in 1 minute-ADJUSTABLE

  gi.linkentity(autolauncher);
}
</C&NBSP;CODE>

Save your new g_airstrike.c file and be sure to add it to your project!

Feel free to play around with the various timers to make them longer or shorter...

Also, you can have it only do one particular type of airstrike by commenting out the other types. My preference is just to have the Viper fire a BFG fireball straight downward! But.. the other payloads are pretty cool too!

That's it! At the start of each map, the autolauncher entity will be created! It will handle all the airstriking for that level. You don't have to do anything else EXCEPT keep looking up when fraggin in those outdoor areas!!!

Have fun!

Maj.Bitch