Ok I've sorted out my flamethrower's issues (BTW it's from RtCW, not WolfET

). With some of the code from the tutorial I linked to above, here's how to add a functional translocator to normal Q3 (bear in mind that I'm trying to adapt this from my mod which is quite different in places, and this still needs modifying to work a little better). Add the stuff in green:
Adding a Translocator the Way it's Meant to Be Done
bg_public.h:
Add an entity flag definition under the persEnum_t struct for the missile bounce:
} persEnum_t;
// entityState_t->eFlags
#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD
#ifdef MISSIONPACK
#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound
#endif
#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes
#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite
#define EF_PLAYER_EVENT 0x00000010
#define EF_BOUNCE 0x00000010 // for missiles
#define EF_BOUNCE_HALF 0x00000020 // for missiles
#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite
#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items)
#define EF_FIRING 0x00000100 // for lightning gun
#define EF_KAMIKAZE 0x00000200
#define EF_MOVER_STOP 0x00000400 // will push otherwise
#define EF_AWARD_CAP 0x00000800 // draw the capture sprite
#define EF_TALK 0x00001000 // draw a talk balloon
#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite
#define EF_VOTED 0x00004000 // already cast a vote
#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite
#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite
#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite
#define EF_AWARD_DENIED 0x00040000 // denied
#define EF_TEAMVOTED 0x00080000 // already cast a team vote
#define EF_BOUNCE_TRANS 0x00100000
// NOTE: may not have more than 16
typedef enum {
Search for the weapon_t struct. Add
WP_TRANSLOCATOR, before
WP_NUM_WEAPONS:
typedef enum {
WP_NONE,
WP_GAUNTLET,
WP_MACHINEGUN,
WP_SHOTGUN,
WP_GRENADE_LAUNCHER,
WP_ROCKET_LAUNCHER,
WP_LIGHTNING,
WP_RAILGUN,
WP_PLASMAGUN,
WP_BFG,
WP_GRAPPLING_HOOK,
#ifdef MISSIONPACK
WP_NAILGUN,
WP_PROX_LAUNCHER,
WP_CHAINGUN,
#endif
WP_TRANSLOCATOR,
WP_NUM_WEAPONS
} weapon_t;
bg_misc.c:
Create an entity definition. Preferably right at the end of the item list, right above the 'end of list marker':
{
"item_bluecube",
"sound/misc/am_pkup.wav",
{ "models/powerups/orb/b_orb.md3",
NULL, NULL, NULL },
/* icon */ "icons/iconh_borb",
/* pickup */ "Blue Cube",
0,
IT_TEAM,
0,
/* precache */ "",
/* sounds */ ""
},
#endif
/*QUAKED weapon_translocator (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
{
"weapon_translocator",
"sound/misc/w_pkup.wav",
{ "models/weapons2/grenadel/grenadel.md3",
NULL, NULL, NULL},
/* icon */ "icons/iconw_trans",
/* pickup */ "Translocator",
0,
IT_WEAPON,
WP_TRANSLOCATOR,
/* precache */ "",
/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav"
},
// end of list marker
{NULL}
};
g_local.h:
At the end of the gentity_s struct, add the following variables which will tell the server where to teleport the player:
struct gentity_s {
...
vec3_t teleloc;
int istelepoint;
};
typedef enum {
CON_DISCONNECTED,
CON_CONNECTING,
CON_CONNECTED
} clientConnected_t;
Then scroll down to where it does declarations for g_missile.c and add G_ExplodeMissile under G_RunMissile:
//
// g_missile.c
//
void G_RunMissile( gentity_t *ent );
void G_ExplodeMissile( gentity_t *ent );
Directly after that should be the entity missile firing declarations. Add fire_translocator to that:
gentity_t *fire_blaster (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_bfg (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_grapple (gentity_t *self, vec3_t start, vec3_t dir);
#ifdef MISSIONPACK
gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up );
gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t aimdir );
#endif
gentity_t *fire_translocator (gentity_t *self, vec3_t start, vec3_t aimdir);
g_missile.c:
Right at the top should be G_BounceMissile. Add:
/*
================
G_BounceMissile
================
*/
void G_BounceMissile( gentity_t *ent, trace_t *trace ) {
vec3_t velocity;
float dot;
int hitTime;
// reflect the velocity on the trace plane
hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
dot = DotProduct( velocity, trace->plane.normal );
VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
if ( ent->s.eFlags & EF_BOUNCE_HALF ) {
VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
// check for stop
if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) {
G_SetOrigin( ent, trace->endpos );
return;
}
} else if ( ent->s.eFlags & EF_BOUNCE_TRANS ) {
VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
G_SetOrigin( ent, trace->endpos );
ent->parent->istelepoint = 1;
VectorCopy(ent->r.currentOrigin, ent->parent->teleloc);
ent->parent->teleloc[2] += 6;
}
VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
ent->s.pos.trTime = level.time;
}
Look for the G_MissileImpact function, where it says check for bounce, add the entity flag we defined earlier into the if statement:
// check for bounce
if ( !other->takedamage &&
( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF | EF_BOUNCE_TRANS ) ) ) {
G_BounceMissile( ent, trace );
G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
return;
Further down the file, add this after the fire_grenade entity function:
/*
=================
fire_translocator
=================
*/
gentity_t *fire_translocator (gentity_t *self, vec3_t start, vec3_t dir) {
gentity_t *bolt;
VectorNormalize (dir);
bolt = G_Spawn();
bolt->classname = "trans";
bolt->nextthink = level.time + 30000;
bolt->think = G_ExplodeMissile;
bolt->s.eType = ET_MISSILE;
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
bolt->s.weapon = WP_TRANSLOCATOR;
bolt->s.eFlags = EF_BOUNCE_TRANS;
bolt->r.ownerNum = self->s.number;
bolt->parent = self;
bolt->damage = 0;
bolt->splashDamage = 0;
bolt->splashRadius = 150;
bolt->methodOfDeath = MOD_GRAPPLE;
bolt->splashMethodOfDeath = MOD_GRAPPLE;
bolt->clipmask = MASK_SHOT;
bolt->s.pos.trType = TR_GRAVITY;
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
VectorCopy( start, bolt->s.pos.trBase );
VectorScale( dir, 1000, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, bolt->r.currentOrigin);
return bolt;
}
//=============================================================================
g_weapon.c:
Now we need to tell the game what to do when you fire the weapon. Go to the end of all the other weapon functions and add:
#endif
//======================================================================
/*
======================================================================
TRANSLOCATOR
======================================================================
*/
void Weapon_Translocator_Fire (gentity_t *ent) {
gentity_t *m;
gentity_t *trans = NULL;
while ((trans = G_Find (trans, FOFS(classname), "trans")) != NULL) {
if(trans->r.ownerNum == ent->s.clientNum) { //make sure its ours
ent->istelepoint = 0; // client cannot teleport
VectorClear( ent->teleloc ); // clear the teleport location
trans->think = G_ExplodeMissile;
trans->nextthink = level.time;
}
}
// extra vertical velocity
forward[2] += 0.2f;
VectorNormalize( forward );
m = fire_translocator (ent, muzzle, forward);
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
}
//======================================================================
/*
===============
LogAccuracyHit
===============
*/
Scoll down to FireWeapon. We want to exclude firing the translocator from contributing to one's overall accuracy:
#endif
// track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_TRANSLOCATOR && ent->s.weapon != WP_GAUNTLET ) {
#ifdef MISSIONPACK
if( ent->s.weapon == WP_NAILGUN ) {
Further down that function we want to tell the game to use the weapon function we added above when we're using the translocator:
// fire the specific weapon
...
case WP_GRAPPLING_HOOK:
Weapon_GrapplingHook_Fire( ent );
break;
#ifdef MISSIONPACK
case WP_NAILGUN:
Weapon_Nailgun_Fire( ent );
break;
case WP_PROX_LAUNCHER:
weapon_proxlauncher_fire( ent );
break;
case WP_CHAINGUN:
Bullet_Fire( ent, CHAINGUN_SPREAD, MACHINEGUN_DAMAGE );
break;
#endif
case WP_TRANSLOCATOR:
Weapon_Translocator_Fire( ent );
break;
default:
// FIXME G_Error( "Bad ent->s.weapon" );
break;
}
}
...
bg_pmove.c:
Under PM_Weapon, we want to add an entry to the pm->ps->weapon switch which sets the firing rate of the translocator:
// fire weapon
PM_AddEvent( EV_FIRE_WEAPON );
switch( pm->ps->weapon ) {
default:
case WP_GAUNTLET:
addTime = 400;
break;
case WP_LIGHTNING:
addTime = 50;
break;
case WP_SHOTGUN:
addTime = 1000;
break;
case WP_MACHINEGUN:
addTime = 100;
break;
case WP_GRENADE_LAUNCHER:
addTime = 800;
break;
case WP_ROCKET_LAUNCHER:
addTime = 800;
break;
case WP_PLASMAGUN:
addTime = 100;
break;
case WP_RAILGUN:
addTime = 1500;
break;
case WP_BFG:
addTime = 200;
break;
case WP_GRAPPLING_HOOK:
addTime = 400;
break;
#ifdef MISSIONPACK
case WP_NAILGUN:
addTime = 1000;
break;
case WP_PROX_LAUNCHER:
addTime = 800;
break;
case WP_CHAINGUN:
addTime = 30;
break;
#endif
case WP_TRANSLOCATOR:
addTime = 1000;
break;
}
g_local.h:
Now let's add a cvar to allow players to spawn with the weapon, and then callvote functionality (you can apply this method to any other weapon too). Add this after g_proxMineTimeout:
extern vmCvar_t g_enableBreath;
extern vmCvar_t g_singlePlayer;
extern vmCvar_t g_proxMineTimeout;
extern vmCvar_t g_translocator;
g_main.c:
vmCvar_t g_enableDust;
vmCvar_t g_enableBreath;
vmCvar_t g_proxMineTimeout;
#endif
vmCvar_t g_translocator;
{ &g_enableDust, "g_enableDust", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
{ &g_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
{ &g_proxMineTimeout, "g_proxMineTimeout", "20000", 0, 0, qfalse },
#endif
{ &g_translocator, "g_translocator", "0", 0, 0, qfalse},
{ &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
{ &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
g_cmds.c:
In Cmd_CallVote_f:
...
} else if ( !Q_stricmp( arg1, "fraglimit" ) ) {
} else if ( !Q_stricmp( arg1, "g_translocator" ) ) {
} else {
if ( g_votelimit.integer ) {
voteInvalid( ent );
} else {
trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" );
trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map <mapname>, g_gametype <n>, kick <player>, clientkick <clientnum>, g_doWarmup, timelimit <time>, fraglimit <frags>, g_translocator [0/1].\n\"" ); //trans
}
Staying in g_cmds.c, we will now add a command to teleport the player to the location of the missile. First add this to the top of the file, under the #include entries:
#include "g_local.h"
#include "../../ui/menudef.h" // for the voice chats
/*
=================
Cmd_UseItem_f
//trans
=================
*/
void Cmd_UseItem_f (gentity_t *ent) {
if ( ent->istelepoint == 1 ) {
VectorCopy( ent->teleloc, ent->client->ps.origin );
} else {
G_Printf( S_COLOR_RED "No teleportation node\n" );
}
}
Now at the end of the file, Add:
else if (Q_stricmp (cmd, "stats") == 0)
Cmd_Stats_f( ent );
else if (Q_stricmp (cmd, "useitem") == 0)
Cmd_UseItem_f( ent );
Now to teleport the player to where you shot the missile, bind/type in console 'useitem'. I named it that as you could make it replace the
+button2 button command while you're holding the translocator, thus cutting down the amount of binds you need.
g_client.c:
In the ClientSpawn function, lower down where it lists some weapons, we want to add an entry for spawning players to get the translocator when the cvar is set to 1, and then set infinite ammo for it even when a player does not have the weapon:
client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
if ( g_gametype.integer == GT_TEAM ) {
client->ps.ammo[WP_MACHINEGUN] = 50;
} else {
client->ps.ammo[WP_MACHINEGUN] = 100;
}
if ( g_translocator.integer == 1 ) { //trans
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRANSLOCATOR );
}
client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
client->ps.ammo[WP_GAUNTLET] = -1;
client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
client->ps.ammo[WP_TRANSLOCATOR] = -1;
Now for some bot support (well they still won't work after you do this as you'll need to edit the botfiles/* that's in the game's main pk3 in order to get them to actually work, but for now I'll leave that for you to figure out.
inv.h:
Model indices need to be listed in the same order as the item list in bg_misc.c, so with that in mind, add the translocator to the end of that:
#define MODELINDEX_NEUTRALFLAG 52
#define MODELINDEX_REDCUBE 53
#define MODELINDEX_BLUECUBE 54
#define MODELINDEX_TRANSLOCATOR 55
#define WEAPONINDEX_GRAPPLING_HOOK 10
#define WEAPONINDEX_NAILGUN 11
#define WEAPONINDEX_PROXLAUNCHER 12
#define WEAPONINDEX_CHAINGUN 13
#define WEAPONINDEX_TRANSLOCATOR 14
Further up the file...
#define INVENTORY_NEUTRALFLAG 50
#define INVENTORY_REDCUBE 51
#define INVENTORY_BLUECUBE 52
#define INVENTORY_TRANSLOCATOR 53
ai_dmq3.c:
In BotUpdateInventory:
bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0;
bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0;
#ifdef MISSIONPACK
bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;;
bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;;
bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;;
#endif
bs->inventory[INVENTORY_TRANSLOCATOR] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_TRANSLOCATOR)) != 0;
//ammo
bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN];
bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN];
I hope I didn't miss something. Goddamnit that took me longer to type than to code.
