What the hell is a
"Telefrag gun"?
We
will be modifying the railgun so when you shoot another player, instead of
killing them, you will teleport to where they are standing, hence telefrag them.
This makes the railgun a lot more fun to use, and creates one hell of an anti
camper / anti sniper feature.
The Telefrag gun is included as one of the 10 styles of Instagib in Human Debris mod OSK:Arena
Note: I am not the worlds greatest programmer by a long shot, so don't go crazy if you see a bit of bad programming in this tutorial. I would appreciate any suggested improvements or corrections you can offer. The code has been tested extensively and works fine.
In doing this tutorial I assume you know how to compile the source, and have played with the code before.
Okay, on with the tutorial. This is a server side modification.
Files to be
modified
g_local.h
g_misc.c
g_weapon.c
Outline
This is a reasonably simple operation. First we will have to create a
new TelefragPlayer function, which is a slightly modified version of the
TeleportPlayer function in g_misc.c. Then we will change the DO-WHILE in the
weapon_railgun_fire function in g_weapon.c to call our new TelefragPlayer
function. We will change the Railgun so that the projectile it fires will not
pass through multiple players.
g_local.h
Go down to about halfway and find:
//
// g_misc.c
//
void TeleportPlayer(
gentity_t *player, vec3_t origin, vec3_t angles );
Now add this line:
void TelefragPlayer( gentity_t
*player, vec3_t origin ); // same as above without the
angles
g_misc.c
Go down about 1/4 of the way until you find the following:
void TeleportPlayer(
gentity_t *player, vec3_t origin, vec3_t angles ) {
gentity_t
*tent;
// use temp events
at source and destination to prevent the effect
// from getting dropped by
a second player event
if ( player->client->sess.sessionTeam !=
TEAM_SPECTATOR ) {
tent = G_TempEntity(
player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
tent->s.clientNum = player->s.clientNum;
tent =
G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum =
player->s.clientNum;
}
// unlink to make
sure it can't possibly interfere with G_KillBox
trap_UnlinkEntity
(player);
VectorCopy (
origin, player->client->ps.origin );
player->client->ps.origin[2] += 1;
// spit the player
out
AngleVectors( angles, player->client->ps.velocity, NULL,
NULL );
VectorScale( player->client->ps.velocity, 400,
player->client->ps.velocity );
player->client->ps.pm_time = 160; // hold
time
player->client->ps.pm_flags |=
PMF_TIME_KNOCKBACK;
// toggle the
teleport bit so the client knows to not lerp
player->client->ps.eFlags ^=
EF_TELEPORT_BIT;
// set
angles
SetClientViewAngle( player, angles );
// kill anything
at the destination
if ( player->client->sess.sessionTeam !=
TEAM_SPECTATOR ) {
G_KillBox (player);
}
// save results of
pmove
BG_PlayerStateToEntityState( &player->client->ps,
&player->s, qtrue );
// use the precise
origin for linking
VectorCopy( player->client->ps.origin,
player->r.currentOrigin );
if (
player->client->sess.sessionTeam != TEAM_SPECTATOR )
{
trap_LinkEntity (player);
}
}
This is the TeleportPlayer function. This
function is called when a player uses the Self Teleport Item, and when a player
goes through a teleporter. We will be using this as a base for our new
TelefragPlayer() function.
After the end of the above code, put the following:
void TelefragPlayer(
gentity_t *player, vec3_t origin ) { // removed angles
gentity_t
*tent;
// use temp events
at source and destination to prevent the effect
// from getting dropped by
a second player event
if ( player->client->sess.sessionTeam !=
TEAM_SPECTATOR ) {
tent = G_TempEntity(
player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
tent->s.clientNum = player->s.clientNum;
tent =
G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum =
player->s.clientNum;
}
// unlink to make
sure it can't possibly interfere with G_KillBox
trap_UnlinkEntity
(player);
VectorCopy (
origin, player->client->ps.origin );
player->client->ps.origin[2] += 1;
//
Zygote
// Remove angles and "spit-out"
/*
// spit the
player out
AngleVectors( angles, player->client->ps.velocity, NULL,
NULL );
VectorScale( player->client->ps.velocity, 400,
player->client->ps.velocity );
player->client->ps.pm_time = 160; // hold
time
player->client->ps.pm_flags |=
PMF_TIME_KNOCKBACK;
*/
// toggle the teleport bit
so the client knows to not lerp
player->client->ps.eFlags ^=
EF_TELEPORT_BIT;
//
Zygote
// Remove angles
/*
// set angles
SetClientViewAngle( player,
angles );
*/
// kill anything at the destination
if (
player->client->sess.sessionTeam != TEAM_SPECTATOR )
{
G_KillBox (player);
}
// save results of
pmove
BG_PlayerStateToEntityState( &player->client->ps,
&player->s, qtrue );
// use the precise
origin for linking
VectorCopy( player->client->ps.origin,
player->r.currentOrigin );
if (
player->client->sess.sessionTeam != TEAM_SPECTATOR )
{
trap_LinkEntity (player);
}
}
You should notice, that it is an exact copy
of TeleportPlayer.
All that
has been done is block comment (/*
*/) all code that changes the players angles after
the teleport. The reasons is we want the player to be looking in exactly the
same place before and after the teleport.
You will also notice we have commented the "spit-out"
code. What the "spit-out" code did was scale the players velocity forward in the
direction they are looking, and hold it for 160ms. This we commented because
after test playing, the "spit-out" becomes increasingly annoying.
g_weapon.c
Go down to about halfway, and find the following code:
/*
=================
weapon_railgun_fire
=================
*/
#define MAX_RAIL_HITS 4
void
weapon_railgun_fire (gentity_t *ent) {
This marks the start of the
weapon_railgun_fire function. This code is called when a player fires the
railgun.
I have broken this
section up into 2 parts:
Stop the projectile when it hits a
player
The railgun
projectile can go through up to four players on one shot. We want to change it
so the projectile will stop after hitting one player.
Find the following line:
#define MAX_RAIL_HITS
4
Change it to:
#define MAX_RAIL_HITS
1
Adding the Teleport to the
Telefrag
Find the
following line near the top of the function:
damage = 100 *
s_quadFactor;
Change it to:
damage = 1000 *
s_quadFactor;
This
just makes the railgun's projectile hurt a lot more!
Find the following code inside the
DO-WHILE:
if ( traceEnt->takedamage )
{
if( LogAccuracyHit( traceEnt, ent ) )
{
hits++;
}
G_Damage (traceEnt, ent, ent, forward, trace.endpos,
damage, 0, MOD_RAILGUN);
}
What this code does is, when the projectile
hits something that can be damaged, increment AccuracyHits and damage what was
hit.
We want to change this
bit of code to the following:
if (
traceEnt->takedamage ) {
if( LogAccuracyHit( traceEnt, ent ) )
{
hits++;
}
// make sure you are a client and you are alive and not a
spectator
if ((traceEnt->client) &&
(traceEnt->client->ps.pm_type != PM_DEAD) &&
(traceEnt->client->sess.sessionTeam != TEAM_SPECTATOR))
{
if ( OnSameTeam (traceEnt, ent) ) { // if the
attacker was on the same team
if (
!g_friendlyFire.integer ) { // if TF_NO_FRIENDLY_FIRE is
set
G_Damage (traceEnt, ent, ent, forward,
trace.endpos, damage, 0, MOD_RAILGUN);
break; // get out of here!
}
}
// Damage then teleport (kill box causes a strange
sound!?!?)
G_Damage (traceEnt, ent, ent, forward, trace.endpos,
damage, 0, MOD_RAILGUN);
TelefragPlayer(ent,
traceEnt->r.currentOrigin); // teleport player
break;
}
// For Doors, Spectators,
Deadbodies...
G_Damage (traceEnt, ent, ent, forward,
trace.endpos, damage, 0, MOD_RAILGUN);
}
Analysis:
You can see that we are doing a lot of checking on
the entity that the projectile hits. You could rewrite the checking a few
different ways if you want, and make it more stream lined.
Is the entity a client, alive, and not a
spectator?
if ((traceEnt->client) &&
(traceEnt->client->ps.pm_type != PM_DEAD) &&
(traceEnt->client->sess.sessionTeam != TEAM_SPECTATOR))
{
Is the entity on the same
team?
if ( OnSameTeam (traceEnt, ent) ) {
The entity is on the same team and Friendly
Fire is ON (can damage team mates), Damage it normally, and break out of the
DO-WHILE
if ( !g_friendlyFire.integer ) { // if TF_NO_FRIENDLY_FIRE
is set
G_Damage (traceEnt, ent, ent, forward,
trace.endpos, damage, 0, MOD_RAILGUN);
break; // get out of here!
}
}
The entity is not on the same team. Damage
it, THEN teleport to where the entity was standing, and break out of the
DO-WHILE
G_Damage (traceEnt, ent, ent, forward, trace.endpos,
damage, 0, MOD_RAILGUN);
TelefragPlayer(ent,
traceEnt->r.currentOrigin); // teleport player
break;
}
The reason why we don't let TelefragPlayer dish out the damage with its
call to G_KillBox, is because it causes some sort of strange error noise. As
well as that, there is a chance the server could crash if two players try and
teleport to the one place at the exact same time. This method
(Damage-then-Teleport) is cleaner, and looks exactly the same in
game.
The entity is not alive or is not a client,
or is a spectator, Damage it, and break out of the DO-WHILE
// For Doors,
Spectators, Deadbodies...
G_Damage (traceEnt, ent, ent, forward,
trace.endpos, damage, 0, MOD_RAILGUN);
The reason why we have to do all of that
checking is because when you damage an entity normally with G_Damage(), it does
all of the checking for you. Our new TelefragPlayer() does not do any checking,
so we have to do it all in advance.
Another way to handle this would be to add all the checking into the
TelefragPlayer() function.
Thats
It!
That is all
there is to it.
Compile the
code. Now load up your favorite level with a railgun in it (mine is q3tourney3),
and take it for a test toast!