|
posted 11-03-98 6:15 PM CT (US)
Title: The Baton Weapon
Difficulty: Moderate
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net
Date: 11-03-98
Note: Please give credit where credit is due.
======================================================
This is another
take-off of my two previous tutorials on entity
activation and targeting.
I put this tutorial
together because I wanted to make another
new weapon which has a lot of neat features. This new weapon
is called the Baton (because it looks like those things which
cheerleaders twirl around at football games and in parades).
The baton's model is
standard issue Q2 (called the minelight) so
nothing new is needed client-side.
The features of the
Baton are as follows:
After paying the
cost of obtaining a baton, the player can pull
out one of their batons from their backpack and toss this baby
at anything they want to. The baton can be thrown by the player
with such acceleration (and it is coated with sticky stuff)
such that it embeds itself into the first world surface that it
comes in contact with (providing that it doesn't hit a player or
the sky first). If a player gets in the path of the tossed baton
then a giant fireball erupts on impact with that unlucky bastard
(with the normal damage distribution stuff of course)
And, if the baton is
tossed at the sky, it just disappears.
Upon impact with a
surface, the baton's fuse immediately gets
lit with the associated green-sparks. The fuse burns for 10
seconds (with bursts of sparks showering from the baton's fuse
each second). After the 10 second fuse burns all the way down,
the baton's targeting system becomes active and for the next
30 seconds the baton sits at its impact point half buried in
a wall/ceiling somewhere targeting all visible players within
a 1000 unit radius. Anybody (you can exempt the owner) who comes
within the zone-of-death of the baton, gets a laserbeam blast sent
straight through their skull! After the baton's 30 seconds of
targeting have expired, the baton auto self-destructs into a big
fireball (with bits of debris thrown here and there) inflicting
damage on anybody within the blast radius..
Since the cost of
tossing out a single baton is 1 frag, those
players who have alot of frags can throw out multiple batons
up on the walls around a certain area and let the batons kill
anybody and everybody that dares approach!! As they rack up
more frags, they can toss out more and more batons!! Cool!!
However, the baton's
can be killed! And, since the baton's fuse
burns for a full 10 seconds before its targeting mechanism gets
activated, it just sits helplessly imbedded into a wall somewhere
waiting to go active. Take advantage and disable the baton now!
If you disable one of the batons (by causing it to explode) you
get awarded 2 frags for disabling one of these very deadly
weapons. So, if the fuse is still burning (sparks are flying out
of the baton) then that is your opportunity to pick up a couple of
quick frags.
But, be warned of
several things: although the health of the baton is
only a mere 30 units, the size of the baton is quite small and
narrow so you'll have to hit it just about dead-center for it to
explode. Also, you are going to have to fire at it from a distance
because if you are standing right up close to the baton trying to
kill it when it goes off, it'll take you out with it! And, if you
are anywhere near the baton when it goes active, you're dead meat!!
Lastly, all batons
belonging to a single player will auto self destruct
when the baton's owner dies. So, you can explode all their batons
by killing the baton's owner.
GREAT for defending
a large area!! Toss out batons on strategic locations
and let the batons do all the work and rack up those frags for you!!!
Okay, let's get
started..
============================================================
We need to add a new variable to your gclient_s struct. So,
at the bottom of that struct, add this:
int baton; // 1=ON,
0=OFF
This will be used to
toggle the baton ON/OFF
============================================================
We need to add
another MOD_* obituary flag. So, inside of
your g_local.h add the MOD_BATON flag as the next integer
in the numerical sequence as shown by example:
#define
MOD_CLUSTER_BOMBS 0x00000024 // Cluster Airstrike
#define MOD_ZYLON_GAS 0x00000025 // Gas Grenade
#define MOD_RAILSTRIKE 0x00000026 // BFG Nuke Airstrike
#define MOD_BFG_NUKE 0x00000027 // Railgun Airstrike
#define MOD_DECOY 0x00000028 // Decoy Blast
#define MOD_LASERDRONE 0x00000029 // LaserDrone Weapon
#define MOD_BATON 0x00000030 // NEW FLAG HERE
#define MOD_FRIENDLY_FIRE 0x10000000 // DON'T TOUCH THIS ONE!!
============================================================
Okay, let's make
some obituaries for your gameplay. Find your
ClientObituary() function in your p_client.c file and add the
following:
---------- FIND
THESE LINES HERE -------------
case
MOD_TARGET_BLASTER:
message="got blasted";
break;
------------ ADD THESE LINES HERE ------------
case MOD_BATON:
message="was killed by a Baton";
break;
Further down in this
same function:
-------------- FIND
THESE LINES -----------
case MOD_G_SPLASH:
if (IsFemale(victim))
message="tripped on her own grenade";
else
message="tripped on his own grenade";
break;
----------- ADD
THESE LINES HERE -------------
case MOD_BATON:
if (IsFemale(victim))
message="got killed by her Baton";
else
message="got killed by his Baton";
break;
Still further down
in this same function:
---------- FIND
THESE LINES HERE -------------
case MOD_TELEFRAG:
message="tried to invade";
message2="'s personal space";
break;
--------- ADD THESE
LINES HERE ---------------
case MOD_BATON:
message="was killed by";
message2="'s Baton";
break;
Remember: You should
feel free to change these client
obituaries to whatever you think is more appropriate for
your particular mod..
============================================================
Okay, go into
InitClientPersistant() function in your p_client.c
file and add this line near the bottom:
client->baton=0;
// default to OFF.
This will set the
default to OFF each time the player enters
the game..
============================================================
Also in your
p_client.c file, go into Player_Die() function
and add this line too:
self->client->baton=0;
// Turn OFF.
This will turn off
all the player's Batons when he dies..
============================================================
Open up your
g_cmds.c file and go near the bottom of your
ClientCommand() function and add the following as shown
by example..
---------- IF-ELSE
STATEMENTS --------
else if (Q_stricmp(cmd, "decoy") == 0 )
SP_Decoy(ent);
else if (Q_stricmp(cmd, "drone") == 0 )
Cmd_LaserDrone_f(ent);
else if (Q_stricmp(cmd, "teleport") == 0)
Cmd_Teleport_f(ent);
----------- ADD THESE LINES ------
else if (Q_stricmp(cmd, "baton") == 0)
Cmd_Baton_f(ent);
else if .....
This will fire a
Baton with a single stroke of the player's
aliased key (providing they can pay the cost of each baton!).
============================================================
Also, near the top
of you g_cmds.c file add this forward
declaration:
void
Cmd_Baton_f(edict_t *ent);
============================================================
Lets open you g_weapons.c file and add these helper routines
and the baton's source code somewhere near the top of the file.
NOTE: I've used
these helper routines in many of my previous
tutorials so you may already have them someplace in your source.
If you already do have them, then you don't need them anymore.
Your compiler should warn you that you've already got these
functions previously defined..
Okay, add the
following text to the top of your g_weapons.c file.
------------------
START HERE ------------------------
// Some forward
declarations...
void ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin);
qboolean visible(edict_t *self, edict_t *other);
//======================================================
// True if Ent is valid, has client, and edict_t inuse.
//======================================================
qboolean G_EntExists(edict_t *ent) {
return ((ent) && (ent->client) && (ent->inuse));
}
//======================================================
// True if ent is not DEAD or DEAD or DEAD (and BURIED!)
//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean buried=true;
qboolean b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b3=ent->health > 0;
return (b3||b2||b1)&&(buried);
}
//======================================================
// True if ent is not DEAD and not just did a Respawn.
//======================================================
qboolean G_ClientInGame(edict_t *ent) {
if (!G_EntExists(ent)) return false;
if (!G_ClientNotDead(ent)) return false;
return (ent->client->respawn_time + 5.0 < level.time);
}
//======================================================
//======================================================
//======================================================
/*
Spawns a trail of (type) from {start} to {end} and Broadcasts to all
in Potentially Visible Set from vector (origin)
TE_BFG_LASER -
Spawns a green laser
TE_BUBBLETRAIL - Spawns a trail of bubbles
TE_PLASMATRAIL - NOT IMPLEMENTED IN ENGINE
TE_RAILTRAIL - Spawns a blue spiral trail filled with white smoke
*/
//======================================================
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);
}
//======================================================
//======================================================
//======================================================
/*
Spawns sparks of (type) from {start} in direction of {movdir} and
Broadcasts to all in Potentially Visible Set from vector (origin)
TE_BLASTER - Spawns
a blaster sparks
TE_BLOOD - Spawns a spurt of red blood
TE_BULLET_SPARKS - Same as TE_SPARKS, with a bullet puff and richochet sound
TE_GREENBLOOD - NOT IMPLEMENTED - Spawns a spurt of green blood
TE_GUNSHOT - Spawns a grey splash of particles, with a bullet puff
TE_SCREEN_SPARKS - Spawns a large green/white splash of sparks
TE_SHIELD_SPARKS - Spawns a large blue/violet splash of sparks
TE_SHOTGUN - Spawns a small grey splash of spark particles, with a bullet
puff
TE_SPARKS - Spawns a red/gold splash of spark particles
*/
//======================================================
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);
}
//======================================================
//======================================================
//======================================================
/*
Spawns a (type) explosion at (start} and Broadcasts to all Potentially
Visible Sets from {origin}
TE_BFG_BIGEXPLOSION
- Spawns a BFG particle explosion
TE_BFG_EXPLOSION - Spawns a BFG explosion sprite
TE_BOSSTPORT - Spawns a mushroom-cloud particle effect
TE_EXPLOSION1 - Spawns a mid-air-style explosion
TE_EXPLOSION2 - Spawns a nuclear-style explosion
TE_GRENADE_EXPLOSION - Spawns a grenade explosion
TE_GRENADE_EXPLOSION_WATER - Spawns an underwater grenade explosion
TE_ROCKET_EXPLOSION - Spawns a rocket explosion
TE_ROCKET_EXPLOSION_WATER - Spawns an underwater rocket explosion
Note: The last four
EXPLOSION entries overlap to some degree.
TE_GRENADE_EXPLOSION is the same as TE_EXPLOSION2,
TE_ROCKET_EXPLOSION is the same as TE_EXPLOSION1,
and both of the EXPLOSION_WATER entries are the same, visually.
*/
//======================================================
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);
}
//======================================================
//============== BATON WEAPON ROUTINES =================
//======================================================
//======================================================
// True if start and end are within radius distance.
//======================================================
qboolean G_Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
for (j=0; j < 3; j++)
eorg[j]=abs(start[j]-end[j]);
return (VectorLength(eorg) < rad);
}
//======================================================
void Baton_Explode(edict_t *baton) {
// Immediately Flag
as OFF
baton->owner->client->baton=0;
// Blow up the Baton
in a big fireball.
G_Spawn_Explosion(TE_EXPLOSION2, baton->s.origin, baton->s.origin);
// Send small debris
flying all around..
ThrowDebris(baton, DEBRIS2_MODEL, 1.50, baton->s.origin);
ThrowDebris(baton, DEBRIS1_MODEL, 2.50, baton->s.origin);
ThrowDebris(baton, DEBRIS1_MODEL, 3.50, baton->s.origin);
ThrowDebris(baton, DEBRIS1_MODEL, 3.00, baton->s.origin);
ThrowDebris(baton, DEBRIS2_MODEL, 1.30, baton->s.origin);
// Assign any Radius
Damage frags to the Baton's owner..
T_RadiusDamage(baton, baton->owner, baton->dmg, NULL,
baton->dmg_radius, MOD_SPLASH);
G_FreeEdict(baton);
// Free the baton entity.
}
//========================================================
// True if Baton's timer has expired (or owner has died!)
//========================================================
qboolean Baton_Expired(edict_t *baton) {
if ((baton->delay
< level.time)
|| (baton->owner->client->baton==0)) {
Baton_Explode(baton);
return true; }
else
return false;
}
//==================================================================
// Fires beam at all visible players within 1000 unit radius
//==================================================================
void Baton_Think(edict_t *baton) {
edict_t *ent=NULL;
trace_t tr;
vec3_t zvec=(0,0,0);
vec3_t start,end;
int i;
if
(Baton_Expired(baton)) return; // Done!
baton->viewheight
= 16; // required for visible().
// Find ALL visible
ents within 1000 unit radius...
for(i=0;i < game.maxclients;i++) {
ent=g_edicts+i+1;
if (!G_ClientInGame(ent)) continue;
if (ent==baton) continue;
// if (ent==baton->owner) continue; // Exempt Baton's owner??
if (!ent->takedamage) continue;
if (!visible(baton,ent)) continue;
if (!G_Within_Radius(baton->s.origin, ent->s.origin, 1000)) continue;
// Okay, Fire at this ent!!
VectorCopy(baton->s.origin, start);
VectorCopy(ent->s.origin, end);
end[2]+=8;
tr=gi.trace(start, ent->mins, ent->maxs, end, NULL, MASK_SHOT);
G_Spawn_Trails(TE_BFG_LASER, start, tr.endpos, tr.endpos); // FIRE!!
if (G_EntExists(tr.ent) && (tr.ent->takedamage))
T_Damage(tr.ent, baton->owner, baton->owner, zvec, start, zvec,
baton->dmg, 1, 0, MOD_BATON);
} // end for
baton->nextthink
= level.time + 0.1; // Re-Target in 1/10 second..
}
//==================================================================
// Burn 10 sec fuse of green sparks before targeting visible ents..
//==================================================================
void Baton_Fuse(edict_t *baton) {
vec3_t zvec=(0,0,0);
if
(Baton_Expired(baton)) return; // Done!
// Has the 10 sec
fuse burned out yet?
if (baton->delay - level.time < 0) {
baton->delay = level.time + 30.0; // 30 sec to self destruct.
baton->think = Baton_Think; // Activate Laser Scan!
baton->nextthink = level.time + 0.1; // Think on next frame.
return; }
// Shower of fuse
sparks...
G_Spawn_Sparks(TE_SCREEN_SPARKS, baton->s.origin, zvec,
baton->s.origin);
baton->nextthink
= level.time + 1.0; // Burn fuse every 1 sec..
}
//==========================================================
void Baton_Touch(edict_t *baton, edict_t *other, cplane_t *plane, csurface_t
*surf){
// Hit Sky? Turn
Baton OFF and exit quietly..
if (surf && surf->flags & SURF_SKY) {
baton->owner->client->baton=0;
G_FreeEdict(baton);
return; }
// Hit a Player?
then Detonate!
if (G_EntExists(other)) {
Baton_Explode(baton);
return; }
//
// Hit a World Surface!
//
baton->takedamage
= DAMAGE_YES; // Can be killed now!
// Play a sound at
point of Baton's impact..
gi.sound(baton, CHAN_VOICE, gi.soundindex("misc/menu1.wav"), 1,
ATTN_IDLE, 0);
// Stop baton's
forward motion.
VectorClear(baton->avelocity);
VectorClear(baton->velocity);
baton->delay =
level.time + 10.0; // Set 10 sec fuse timer.
baton->touch =
NULL; // Don't touch this!
baton->think =
Baton_Fuse;
// Ignite the
Baton's fuse on next frame..
baton->nextthink = level.time + 0.1;
}
//==========================================================
void Fire_Baton(edict_t *ent) {
edict_t *baton=NULL;
vec3_t offset={0,0,0}, start={0,0,0};
vec3_t forward={0,0,0}, right={0,0,0};
// Derive point of
Baton's origin
AngleVectors(ENTS_V_ANGLE, forward, right, NULL);
VectorSet(offset, 8, 8, ENTS_VIEW_HEIGHT-8); // Off-Handed Launch
P_ProjectSource_Reverse(ent->client, ENTS_S_ORIGIN, offset, forward,
right, start);
baton = G_Spawn();
baton->classname = "Baton";
baton->owner = ent;
baton->takedamage = DAMAGE_NO; // No Damage until Touch().
baton->clipmask = MASK_SHOT; // Ability to be hit by weapon's fire.
baton->health = 30; // Hard to target but easy to Kill
baton->max_health=30;
VectorCopy(start, baton->s.origin);
VectorCopy(forward, baton->movedir);
vectoangles(forward, baton->s.angles);
VectorScale(forward, 800, baton->velocity); // Baton's toss speed = 800..
baton->movetype = MOVETYPE_FLY;
baton->solid = SOLID_BBOX;
baton->s.effects = 0;
VectorSet(baton->mins, -5, -5, -15); // size of bbox for touch
VectorSet(baton->maxs, 5, 5, 15); // size of bbox for touch
baton->dmg = 80; // Damage amount..
baton->dmg_radius = 300; // Explosion Damage radius
baton->s.modelindex =
gi.modelindex("models/objects/minelite/light2/tris.md2");
baton->s.modelindex2 = 0;
VectorSet(baton->avelocity,0,0,-1600); // Add some spin to baton flight..
baton->touch =
Baton_Touch; // Function to call upon impact
baton->think =
NULL; // Touch will ignite fuse timer
baton->nextthink = 0;
ent->client->baton=1;
// Flag baton as ON!
gi.linkentity(baton);
}
//======================================================
// Pay the Piper to launch the Baton
//======================================================
void Cmd_Baton_f(edict_t *ent) {
// Don't allow
dead/respawning players to launch Baton!
if (!G_ClientInGame(ent)) return;
// If not enough
frags then notify ent..
if (ent->client->resp.score < 1)
gi.centerprintf(ent, "Each Baton costs 1 frag!");
else {
// Deduct frags from ent's Score
ent->client->resp.score -= 1;
// And, fire off the Baton..
Fire_Baton(ent); }
}
------------------
END HERE ------------------------
NOTE: In the
Baton_Think() function, if you want to exempt
the baton's owner from getting included in the baton's search
and destroy targeting routine then UNCOMMENT THE CODE that I
put in there for you. Also, those of you which have teamplay
mods will have to tailor that one line for your particular
setup..
============================================================
Lastly..
Be sure to add the
following to your Autoexec.cfg file as
shown by example..
bind b
"baton"
That's it!! Now get
out there and KILL ALL THOSE BASTARDS!
Have Fun!!
philip
|