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