mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-23 04:33:12 +00:00
5801 lines
150 KiB
C
5801 lines
150 KiB
C
//#include "g_local.h"
|
|
#include "b_local.h"
|
|
#include "w_saber.h"
|
|
#include "ai_main.h"
|
|
#include "../ghoul2/G2.h"
|
|
|
|
#define METROID_JUMP 1
|
|
|
|
//NEEDED FOR MIND-TRICK on NPCS=========================================================
|
|
extern void NPC_PlayConfusionSound( gentity_t *self );
|
|
extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
|
|
extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
|
|
//NEEDED FOR MIND-TRICK on NPCS=========================================================
|
|
extern void Jedi_Decloak( gentity_t *self );
|
|
|
|
extern vmCvar_t g_saberRestrictForce;
|
|
|
|
#include "../namespace_begin.h"
|
|
extern qboolean BG_FullBodyTauntAnim( int anim );
|
|
#include "../namespace_end.h"
|
|
|
|
extern bot_state_t *botstates[MAX_CLIENTS];
|
|
|
|
int speedLoopSound = 0;
|
|
|
|
int rageLoopSound = 0;
|
|
|
|
int protectLoopSound = 0;
|
|
|
|
int absorbLoopSound = 0;
|
|
|
|
int seeLoopSound = 0;
|
|
|
|
int ysalamiriLoopSound = 0;
|
|
|
|
#define FORCE_VELOCITY_DAMAGE 0
|
|
|
|
int ForceShootDrain( gentity_t *self );
|
|
|
|
gentity_t *G_PreDefSound(vec3_t org, int pdSound)
|
|
{
|
|
gentity_t *te;
|
|
|
|
te = G_TempEntity( org, EV_PREDEFSOUND );
|
|
te->s.eventParm = pdSound;
|
|
VectorCopy(org, te->s.origin);
|
|
|
|
return te;
|
|
}
|
|
|
|
const int forcePowerMinRank[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] = //0 == neutral
|
|
{
|
|
{
|
|
999,//FP_HEAL,//instant
|
|
999,//FP_LEVITATION,//hold/duration
|
|
999,//FP_SPEED,//duration
|
|
999,//FP_PUSH,//hold/duration
|
|
999,//FP_PULL,//hold/duration
|
|
999,//FP_TELEPATHY,//instant
|
|
999,//FP_GRIP,//hold/duration
|
|
999,//FP_LIGHTNING,//hold/duration
|
|
999,//FP_RAGE,//duration
|
|
999,//FP_PROTECT,//duration
|
|
999,//FP_ABSORB,//duration
|
|
999,//FP_TEAM_HEAL,//instant
|
|
999,//FP_TEAM_FORCE,//instant
|
|
999,//FP_DRAIN,//hold/duration
|
|
999,//FP_SEE,//duration
|
|
999,//FP_SABER_OFFENSE,
|
|
999,//FP_SABER_DEFENSE,
|
|
999//FP_SABERTHROW,
|
|
//NUM_FORCE_POWERS
|
|
},
|
|
{
|
|
10,//FP_HEAL,//instant
|
|
0,//FP_LEVITATION,//hold/duration
|
|
0,//FP_SPEED,//duration
|
|
0,//FP_PUSH,//hold/duration
|
|
0,//FP_PULL,//hold/duration
|
|
10,//FP_TELEPATHY,//instant
|
|
15,//FP_GRIP,//hold/duration
|
|
10,//FP_LIGHTNING,//hold/duration
|
|
15,//FP_RAGE,//duration
|
|
15,//FP_PROTECT,//duration
|
|
15,//FP_ABSORB,//duration
|
|
10,//FP_TEAM_HEAL,//instant
|
|
10,//FP_TEAM_FORCE,//instant
|
|
10,//FP_DRAIN,//hold/duration
|
|
5,//FP_SEE,//duration
|
|
0,//FP_SABER_OFFENSE,
|
|
0,//FP_SABER_DEFENSE,
|
|
0//FP_SABERTHROW,
|
|
//NUM_FORCE_POWERS
|
|
},
|
|
{
|
|
10,//FP_HEAL,//instant
|
|
0,//FP_LEVITATION,//hold/duration
|
|
0,//FP_SPEED,//duration
|
|
0,//FP_PUSH,//hold/duration
|
|
0,//FP_PULL,//hold/duration
|
|
10,//FP_TELEPATHY,//instant
|
|
15,//FP_GRIP,//hold/duration
|
|
10,//FP_LIGHTNING,//hold/duration
|
|
15,//FP_RAGE,//duration
|
|
15,//FP_PROTECT,//duration
|
|
15,//FP_ABSORB,//duration
|
|
10,//FP_TEAM_HEAL,//instant
|
|
10,//FP_TEAM_FORCE,//instant
|
|
10,//FP_DRAIN,//hold/duration
|
|
5,//FP_SEE,//duration
|
|
5,//FP_SABER_OFFENSE,
|
|
5,//FP_SABER_DEFENSE,
|
|
5//FP_SABERTHROW,
|
|
//NUM_FORCE_POWERS
|
|
},
|
|
{
|
|
10,//FP_HEAL,//instant
|
|
0,//FP_LEVITATION,//hold/duration
|
|
0,//FP_SPEED,//duration
|
|
0,//FP_PUSH,//hold/duration
|
|
0,//FP_PULL,//hold/duration
|
|
10,//FP_TELEPATHY,//instant
|
|
15,//FP_GRIP,//hold/duration
|
|
10,//FP_LIGHTNING,//hold/duration
|
|
15,//FP_RAGE,//duration
|
|
15,//FP_PROTECT,//duration
|
|
15,//FP_ABSORB,//duration
|
|
10,//FP_TEAM_HEAL,//instant
|
|
10,//FP_TEAM_FORCE,//instant
|
|
10,//FP_DRAIN,//hold/duration
|
|
5,//FP_SEE,//duration
|
|
10,//FP_SABER_OFFENSE,
|
|
10,//FP_SABER_DEFENSE,
|
|
10//FP_SABERTHROW,
|
|
//NUM_FORCE_POWERS
|
|
}
|
|
};
|
|
|
|
const int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
|
|
{
|
|
0,//none
|
|
5000,
|
|
10000,
|
|
15000
|
|
};
|
|
|
|
void WP_InitForcePowers( gentity_t *ent )
|
|
{
|
|
int i;
|
|
int i_r;
|
|
int maxRank = g_maxForceRank.integer;
|
|
qboolean warnClient = qfalse;
|
|
qboolean warnClientLimit = qfalse;
|
|
char userinfo[MAX_INFO_STRING];
|
|
char forcePowers[256];
|
|
char readBuf[256];
|
|
int lastFPKnown = -1;
|
|
qboolean didEvent = qfalse;
|
|
|
|
if (!maxRank)
|
|
{ //if server has no max rank, default to max (50)
|
|
maxRank = FORCE_MASTERY_JEDI_MASTER;
|
|
}
|
|
else if (maxRank >= NUM_FORCE_MASTERY_LEVELS)
|
|
{//ack, prevent user from being dumb
|
|
maxRank = FORCE_MASTERY_JEDI_MASTER;
|
|
trap_Cvar_Set( "g_maxForceRank", va("%i", maxRank) );
|
|
}
|
|
|
|
/*
|
|
if (g_forcePowerDisable.integer)
|
|
{
|
|
maxRank = FORCE_MASTERY_UNINITIATED;
|
|
}
|
|
*/
|
|
//rww - don't do this
|
|
|
|
if ( !ent || !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->client->ps.fd.saberAnimLevel = ent->client->sess.saberLevel;
|
|
|
|
if (ent->client->ps.fd.saberAnimLevel < FORCE_LEVEL_1 ||
|
|
ent->client->ps.fd.saberAnimLevel > FORCE_LEVEL_3)
|
|
{
|
|
ent->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
|
|
}
|
|
|
|
if (!speedLoopSound)
|
|
{ //so that the client configstring is already modified with this when we need it
|
|
speedLoopSound = G_SoundIndex("sound/weapons/force/speedloop.wav");
|
|
}
|
|
|
|
if (!rageLoopSound)
|
|
{
|
|
rageLoopSound = G_SoundIndex("sound/weapons/force/rageloop.wav");
|
|
}
|
|
|
|
if (!absorbLoopSound)
|
|
{
|
|
absorbLoopSound = G_SoundIndex("sound/weapons/force/absorbloop.wav");
|
|
}
|
|
|
|
if (!protectLoopSound)
|
|
{
|
|
protectLoopSound = G_SoundIndex("sound/weapons/force/protectloop.wav");
|
|
}
|
|
|
|
if (!seeLoopSound)
|
|
{
|
|
seeLoopSound = G_SoundIndex("sound/weapons/force/seeloop.wav");
|
|
}
|
|
|
|
if (!ysalamiriLoopSound)
|
|
{
|
|
ysalamiriLoopSound = G_SoundIndex("sound/player/nullifyloop.wav");
|
|
}
|
|
|
|
if (ent->s.eType == ET_NPC)
|
|
{ //just stop here then.
|
|
return;
|
|
}
|
|
|
|
i = 0;
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[i] = 0;
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
i++;
|
|
}
|
|
|
|
ent->client->ps.fd.forcePowerSelected = -1;
|
|
|
|
ent->client->ps.fd.forceSide = 0;
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
ent->client->siegeClass != -1)
|
|
{ //Then use the powers for this class, and skip all this nonsense.
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[i] = bgSiegeClasses[ent->client->siegeClass].forcePowerLevels[i];
|
|
|
|
if (!ent->client->ps.fd.forcePowerLevel[i])
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!ent->client->sess.setForce)
|
|
{
|
|
//bring up the class selection menu
|
|
trap_SendServerCommand(ent-g_entities, "scl");
|
|
}
|
|
ent->client->sess.setForce = qtrue;
|
|
|
|
return;
|
|
}
|
|
|
|
if (ent->s.eType == ET_NPC && ent->s.number >= MAX_CLIENTS)
|
|
{ //rwwFIXMEFIXME: Temp
|
|
strcpy(userinfo, "forcepowers\\7-1-333003000313003120");
|
|
}
|
|
else
|
|
{
|
|
trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) );
|
|
}
|
|
|
|
Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
|
|
|
|
if ( (ent->r.svFlags & SVF_BOT) && botstates[ent->s.number] )
|
|
{ //if it's a bot just copy the info directly from its personality
|
|
Com_sprintf(forcePowers, sizeof(forcePowers), "%s\0", botstates[ent->s.number]->forceinfo);
|
|
}
|
|
|
|
//rww - parse through the string manually and eat out all the appropriate data
|
|
i = 0;
|
|
|
|
if (g_forceBasedTeams.integer)
|
|
{
|
|
if (ent->client->sess.sessionTeam == TEAM_RED)
|
|
{
|
|
warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), FORCE_DARKSIDE, g_gametype.integer, g_forcePowerDisable.integer));
|
|
}
|
|
else if (ent->client->sess.sessionTeam == TEAM_BLUE)
|
|
{
|
|
warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), FORCE_LIGHTSIDE, g_gametype.integer, g_forcePowerDisable.integer));
|
|
}
|
|
else
|
|
{
|
|
warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), 0, g_gametype.integer, g_forcePowerDisable.integer));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), 0, g_gametype.integer, g_forcePowerDisable.integer));
|
|
}
|
|
|
|
i_r = 0;
|
|
while (forcePowers[i] && forcePowers[i] != '-')
|
|
{
|
|
readBuf[i_r] = forcePowers[i];
|
|
i_r++;
|
|
i++;
|
|
}
|
|
readBuf[i_r] = 0;
|
|
//THE RANK
|
|
ent->client->ps.fd.forceRank = atoi(readBuf);
|
|
i++;
|
|
|
|
i_r = 0;
|
|
while (forcePowers[i] && forcePowers[i] != '-')
|
|
{
|
|
readBuf[i_r] = forcePowers[i];
|
|
i_r++;
|
|
i++;
|
|
}
|
|
readBuf[i_r] = 0;
|
|
//THE SIDE
|
|
ent->client->ps.fd.forceSide = atoi(readBuf);
|
|
i++;
|
|
|
|
|
|
if ( g_gametype.integer != GT_SIEGE && (ent->r.svFlags & SVF_BOT) && botstates[ent->s.number] )
|
|
{ //hmm..I'm going to cheat here.
|
|
int oldI = i;
|
|
i_r = 0;
|
|
while (forcePowers[i] && forcePowers[i] != '\n' &&
|
|
i_r < NUM_FORCE_POWERS)
|
|
{
|
|
if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
|
|
{
|
|
if (i_r == FP_ABSORB)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
if (botstates[ent->s.number]->settings.skill >= 4)
|
|
{ //cheat and give them more stuff
|
|
if (i_r == FP_HEAL)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
else if (i_r == FP_PROTECT)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
}
|
|
}
|
|
else if (ent->client->ps.fd.forceSide == FORCE_DARKSIDE)
|
|
{
|
|
if (botstates[ent->s.number]->settings.skill >= 4)
|
|
{
|
|
if (i_r == FP_GRIP)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
else if (i_r == FP_LIGHTNING)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
else if (i_r == FP_RAGE)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
else if (i_r == FP_DRAIN)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i_r == FP_PUSH)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
else if (i_r == FP_PULL)
|
|
{
|
|
forcePowers[i] = '3';
|
|
}
|
|
|
|
i++;
|
|
i_r++;
|
|
}
|
|
i = oldI;
|
|
}
|
|
|
|
i_r = 0;
|
|
while (forcePowers[i] && forcePowers[i] != '\n' &&
|
|
i_r < NUM_FORCE_POWERS)
|
|
{
|
|
readBuf[0] = forcePowers[i];
|
|
readBuf[1] = 0;
|
|
|
|
ent->client->ps.fd.forcePowerLevel[i_r] = atoi(readBuf);
|
|
if (ent->client->ps.fd.forcePowerLevel[i_r])
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown |= (1 << i_r);
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i_r);
|
|
}
|
|
i++;
|
|
i_r++;
|
|
}
|
|
//THE POWERS
|
|
|
|
if (ent->s.eType != ET_NPC)
|
|
{
|
|
if (HasSetSaberOnly())
|
|
{
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FREE_SABER );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = 1;
|
|
}
|
|
else
|
|
{
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FREE_SABER );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = 0;
|
|
}
|
|
|
|
if (g_forcePowerDisable.integer)
|
|
{
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FORCE_DISABLE );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = 1;
|
|
}
|
|
else
|
|
{
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FORCE_DISABLE );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = 0;
|
|
}
|
|
}
|
|
|
|
//rww - It seems we currently want to always do this, even if the player isn't exceeding the max
|
|
//rank, so..
|
|
// if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
|
|
// { //totally messes duel up to force someone into spec mode, and besides, each "round" is
|
|
//counted as a full restart
|
|
// ent->client->sess.setForce = qtrue;
|
|
// }
|
|
|
|
if (ent->s.eType == ET_NPC)
|
|
{
|
|
ent->client->sess.setForce = qtrue;
|
|
}
|
|
else if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
if (!ent->client->sess.setForce)
|
|
{
|
|
ent->client->sess.setForce = qtrue;
|
|
//bring up the class selection menu
|
|
trap_SendServerCommand(ent-g_entities, "scl");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (warnClient || !ent->client->sess.setForce)
|
|
{ //the client's rank is too high for the server and has been autocapped, so tell them
|
|
if (g_gametype.integer != GT_HOLOCRON && g_gametype.integer != GT_JEDIMASTER )
|
|
{
|
|
#ifdef EVENT_FORCE_RANK
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_GIVE_NEW_RANK );
|
|
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.trickedentindex = ent->s.number;
|
|
te->s.eventParm = maxRank;
|
|
te->s.bolt1 = 0;
|
|
#endif
|
|
didEvent = qtrue;
|
|
|
|
// if (!(ent->r.svFlags & SVF_BOT) && g_gametype.integer != GT_DUEL && g_gametype.integer != GT_POWERDUEL && ent->s.eType != ET_NPC)
|
|
if (!(ent->r.svFlags & SVF_BOT) && ent->s.eType != ET_NPC)
|
|
{
|
|
if (!g_teamAutoJoin.integer)
|
|
{
|
|
//Make them a spectator so they can set their powerups up without being bothered.
|
|
ent->client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
ent->client->sess.spectatorState = SPECTATOR_FREE;
|
|
ent->client->sess.spectatorClient = 0;
|
|
|
|
ent->client->pers.teamState.state = TEAM_BEGIN;
|
|
trap_SendServerCommand(ent-g_entities, "spc"); // Fire up the profile menu
|
|
}
|
|
}
|
|
|
|
#ifdef EVENT_FORCE_RANK
|
|
te->s.bolt2 = ent->client->sess.sessionTeam;
|
|
#else
|
|
//Event isn't very reliable, I made it a string. This way I can send it to just one
|
|
//client also, as opposed to making a broadcast event.
|
|
trap_SendServerCommand(ent->s.number, va("nfr %i %i %i", maxRank, 1, ent->client->sess.sessionTeam));
|
|
//Arg1 is new max rank, arg2 is non-0 if force menu should be shown, arg3 is the current team
|
|
#endif
|
|
}
|
|
ent->client->sess.setForce = qtrue;
|
|
}
|
|
|
|
if (!didEvent )
|
|
{
|
|
#ifdef EVENT_FORCE_RANK
|
|
gentity_t *te = G_TempEntity( vec3_origin, EV_GIVE_NEW_RANK );
|
|
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.trickedentindex = ent->s.number;
|
|
te->s.eventParm = maxRank;
|
|
te->s.bolt1 = 1;
|
|
te->s.bolt2 = ent->client->sess.sessionTeam;
|
|
#else
|
|
trap_SendServerCommand(ent->s.number, va("nfr %i %i %i", maxRank, 0, ent->client->sess.sessionTeam));
|
|
#endif
|
|
}
|
|
|
|
if (warnClientLimit)
|
|
{ //the server has one or more force powers disabled and the client is using them in his config
|
|
//trap_SendServerCommand(ent-g_entities, va("print \"The server has one or more force powers that you have chosen disabled.\nYou will not be able to use the disable force power(s) while playing on this server.\n\""));
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if ((ent->client->ps.fd.forcePowersKnown & (1 << i)) &&
|
|
!ent->client->ps.fd.forcePowerLevel[i])
|
|
{ //err..
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
}
|
|
else
|
|
{
|
|
if (i != FP_LEVITATION && i != FP_SABER_OFFENSE && i != FP_SABER_DEFENSE && i != FP_SABERTHROW)
|
|
{
|
|
lastFPKnown = i;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (ent->client->ps.fd.forcePowersKnown & ent->client->sess.selectedFP)
|
|
{
|
|
ent->client->ps.fd.forcePowerSelected = ent->client->sess.selectedFP;
|
|
}
|
|
|
|
if (!(ent->client->ps.fd.forcePowersKnown & (1 << ent->client->ps.fd.forcePowerSelected)))
|
|
{
|
|
if (lastFPKnown != -1)
|
|
{
|
|
ent->client->ps.fd.forcePowerSelected = lastFPKnown;
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.fd.forcePowerSelected = 0;
|
|
}
|
|
}
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerBaseLevel[i] = ent->client->ps.fd.forcePowerLevel[i];
|
|
i++;
|
|
}
|
|
ent->client->ps.fd.forceUsingAdded = 0;
|
|
}
|
|
|
|
void WP_SpawnInitForcePowers( gentity_t *ent )
|
|
{
|
|
int i = 0;
|
|
|
|
ent->client->ps.saberAttackChainCount = 0;
|
|
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (ent->client->ps.fd.forcePowersActive & (1 << i))
|
|
{
|
|
WP_ForcePowerStop(ent, i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
ent->client->ps.fd.forceDeactivateAll = 0;
|
|
|
|
ent->client->ps.fd.forcePower = ent->client->ps.fd.forcePowerMax = FORCE_POWER_MAX;
|
|
ent->client->ps.fd.forcePowerRegenDebounceTime = 0;
|
|
ent->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE;
|
|
ent->client->ps.fd.forceMindtrickTargetIndex = 0;
|
|
ent->client->ps.fd.forceMindtrickTargetIndex2 = 0;
|
|
ent->client->ps.fd.forceMindtrickTargetIndex3 = 0;
|
|
ent->client->ps.fd.forceMindtrickTargetIndex4 = 0;
|
|
|
|
ent->client->ps.holocronBits = 0;
|
|
|
|
i = 0;
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.holocronsCarried[i] = 0;
|
|
i++;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_HOLOCRON)
|
|
{
|
|
i = 0;
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0;
|
|
i++;
|
|
}
|
|
|
|
if (HasSetSaberOnly())
|
|
{
|
|
if (ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_1)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
|
|
}
|
|
if (ent->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
|
|
}
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerDebounce[i] = 0;
|
|
ent->client->ps.fd.forcePowerDuration[i] = 0;
|
|
|
|
i++;
|
|
}
|
|
|
|
ent->client->ps.fd.forcePowerRegenDebounceTime = 0;
|
|
ent->client->ps.fd.forceJumpZStart = 0;
|
|
ent->client->ps.fd.forceJumpCharge = 0;
|
|
ent->client->ps.fd.forceJumpSound = 0;
|
|
ent->client->ps.fd.forceGripDamageDebounceTime = 0;
|
|
ent->client->ps.fd.forceGripBeingGripped = 0;
|
|
ent->client->ps.fd.forceGripCripple = 0;
|
|
ent->client->ps.fd.forceGripUseTime = 0;
|
|
ent->client->ps.fd.forceGripSoundTime = 0;
|
|
ent->client->ps.fd.forceGripStarted = 0;
|
|
ent->client->ps.fd.forceHealTime = 0;
|
|
ent->client->ps.fd.forceHealAmount = 0;
|
|
ent->client->ps.fd.forceRageRecoveryTime = 0;
|
|
ent->client->ps.fd.forceDrainEntNum = ENTITYNUM_NONE;
|
|
ent->client->ps.fd.forceDrainTime = 0;
|
|
|
|
i = 0;
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if ((ent->client->ps.fd.forcePowersKnown & (1 << i)) &&
|
|
!ent->client->ps.fd.forcePowerLevel[i])
|
|
{ //make sure all known powers are cleared if we have level 0 in them
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
ent->client->siegeClass != -1)
|
|
{ //Then use the powers for this class.
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
ent->client->ps.fd.forcePowerLevel[i] = bgSiegeClasses[ent->client->siegeClass].forcePowerLevels[i];
|
|
|
|
if (!ent->client->ps.fd.forcePowerLevel[i])
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "../namespace_begin.h"
|
|
extern qboolean BG_InKnockDown( int anim ); //bg_pmove.c
|
|
#include "../namespace_end.h"
|
|
|
|
int ForcePowerUsableOn(gentity_t *attacker, gentity_t *other, forcePowers_t forcePower)
|
|
{
|
|
if (other && other->client && BG_HasYsalamiri(g_gametype.integer, &other->client->ps))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (attacker && attacker->client && !BG_CanUseFPNow(g_gametype.integer, &attacker->client->ps, level.time, forcePower))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//Dueling fighters cannot use force powers on others, with the exception of force push when locked with each other
|
|
if (attacker && attacker->client && attacker->client->ps.duelInProgress)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (other && other->client && other->client->ps.duelInProgress)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (forcePower == FP_GRIP)
|
|
{
|
|
if (other && other->client &&
|
|
(other->client->ps.fd.forcePowersActive & (1<<FP_ABSORB)))
|
|
{ //don't allow gripping to begin with if they are absorbing
|
|
//play sound indicating that attack was absorbed
|
|
if (other->client->forcePowerSoundDebounce < level.time)
|
|
{
|
|
gentity_t *abSound = G_PreDefSound(other->client->ps.origin, PDSOUND_ABSORBHIT);
|
|
abSound->s.trickedentindex = other->s.number;
|
|
other->client->forcePowerSoundDebounce = level.time + 400;
|
|
}
|
|
return 0;
|
|
}
|
|
else if (other && other->client &&
|
|
other->client->ps.weapon == WP_SABER &&
|
|
BG_SaberInSpecial(other->client->ps.saberMove))
|
|
{ //don't grip person while they are in a special or some really bad things can happen.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (other && other->client &&
|
|
(forcePower == FP_PUSH ||
|
|
forcePower == FP_PULL))
|
|
{
|
|
if (BG_InKnockDown(other->client->ps.legsAnim))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (other && other->client && other->s.eType == ET_NPC &&
|
|
other->s.NPC_class == CLASS_VEHICLE)
|
|
{ //can't use the force on vehicles.. except lightning
|
|
if (forcePower == FP_LIGHTNING)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (other && other->client && other->s.eType == ET_NPC &&
|
|
g_gametype.integer == GT_SIEGE)
|
|
{ //can't use powers at all on npc's normally in siege...
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
|
|
{
|
|
int drain = overrideAmt ? overrideAmt :
|
|
forcePowerNeeded[self->client->ps.fd.forcePowerLevel[forcePower]][forcePower];
|
|
|
|
if (self->client->ps.fd.forcePowersActive & (1 << forcePower))
|
|
{ //we're probably going to deactivate it..
|
|
return qtrue;
|
|
}
|
|
if ( forcePower == FP_LEVITATION )
|
|
{
|
|
return qtrue;
|
|
}
|
|
if ( !drain )
|
|
{
|
|
return qtrue;
|
|
}
|
|
if ((forcePower == FP_DRAIN || forcePower == FP_LIGHTNING) &&
|
|
self->client->ps.fd.forcePower >= 25)
|
|
{ //it's ok then, drain/lightning are actually duration
|
|
return qtrue;
|
|
}
|
|
if ( self->client->ps.fd.forcePower < drain )
|
|
{
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
qboolean WP_ForcePowerInUse( gentity_t *self, forcePowers_t forcePower )
|
|
{
|
|
if ( (self->client->ps.fd.forcePowersActive & ( 1 << forcePower )) )
|
|
{//already using this power
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower )
|
|
{
|
|
if (BG_HasYsalamiri(g_gametype.integer, &self->client->ps))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (self->health <= 0 || self->client->ps.stats[STAT_HEALTH] <= 0 ||
|
|
(self->client->ps.eFlags & EF_DEAD))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (self->client->ps.pm_flags & PMF_FOLLOW)
|
|
{ //specs can't use powers through people
|
|
return qfalse;
|
|
}
|
|
if (self->client->sess.sessionTeam == TEAM_SPECTATOR)
|
|
{
|
|
return qfalse;
|
|
}
|
|
if (self->client->tempSpectate >= level.time)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (!BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, forcePower))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( !(self->client->ps.fd.forcePowersKnown & ( 1 << forcePower )) )
|
|
{//don't know this power
|
|
return qfalse;
|
|
}
|
|
|
|
if ( (self->client->ps.fd.forcePowersActive & ( 1 << forcePower )) )
|
|
{//already using this power
|
|
if (forcePower != FP_LEVITATION)
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if (forcePower == FP_LEVITATION && self->client->fjDidJump)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (!self->client->ps.fd.forcePowerLevel[forcePower])
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( g_debugMelee.integer )
|
|
{
|
|
if ( (self->client->ps.pm_flags&PMF_STUCK_TO_WALL) )
|
|
{//no offensive force powers when stuck to wall
|
|
switch ( forcePower )
|
|
{
|
|
case FP_GRIP:
|
|
case FP_LIGHTNING:
|
|
case FP_DRAIN:
|
|
case FP_SABER_OFFENSE:
|
|
case FP_SABER_DEFENSE:
|
|
case FP_SABERTHROW:
|
|
return qfalse;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !self->client->ps.saberHolstered )
|
|
{
|
|
if ( (self->client->saber[0].saberFlags&SFL_TWO_HANDED) )
|
|
{
|
|
if ( g_saberRestrictForce.integer )
|
|
{
|
|
switch ( forcePower )
|
|
{
|
|
case FP_PUSH:
|
|
case FP_PULL:
|
|
case FP_TELEPATHY:
|
|
case FP_GRIP:
|
|
case FP_LIGHTNING:
|
|
case FP_DRAIN:
|
|
return qfalse;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (self->client->saber[0].saberFlags&SFL_TWO_HANDED)
|
|
|| (self->client->saber[0].model && self->client->saber[0].model[0]) )
|
|
{//this saber requires the use of two hands OR our other hand is using an active saber too
|
|
if ( (self->client->saber[0].forceRestrictions&(1<<forcePower)) )
|
|
{//this power is verboten when using this saber
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if ( self->client->saber[0].model
|
|
&& self->client->saber[0].model[0] )
|
|
{//both sabers on
|
|
if ( g_saberRestrictForce.integer )
|
|
{
|
|
switch ( forcePower )
|
|
{
|
|
case FP_PUSH:
|
|
case FP_PULL:
|
|
case FP_TELEPATHY:
|
|
case FP_GRIP:
|
|
case FP_LIGHTNING:
|
|
case FP_DRAIN:
|
|
return qfalse;
|
|
break;
|
|
}
|
|
}
|
|
if ( (self->client->saber[1].forceRestrictions&(1<<forcePower)) )
|
|
{//this power is verboten when using this saber
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
return WP_ForcePowerAvailable( self, forcePower, 0 ); // OVERRIDEFIXME
|
|
}
|
|
|
|
int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent)
|
|
{
|
|
int getLevel = 0;
|
|
int addTot = 0;
|
|
gentity_t *abSound;
|
|
|
|
if (atPower != FP_LIGHTNING &&
|
|
atPower != FP_DRAIN &&
|
|
atPower != FP_GRIP &&
|
|
atPower != FP_PUSH &&
|
|
atPower != FP_PULL)
|
|
{ //Only these powers can be absorbed
|
|
return -1;
|
|
}
|
|
|
|
if (!atdAbsLevel)
|
|
{ //looks like attacker doesn't have any absorb power
|
|
return -1;
|
|
}
|
|
|
|
if (!(attacked->client->ps.fd.forcePowersActive & (1 << FP_ABSORB)))
|
|
{ //absorb is not active
|
|
return -1;
|
|
}
|
|
|
|
//Subtract absorb power level from the offensive force power
|
|
getLevel = atPowerLevel;
|
|
getLevel -= atdAbsLevel;
|
|
|
|
if (getLevel < 0)
|
|
{
|
|
getLevel = 0;
|
|
}
|
|
|
|
//let the attacker absorb an amount of force used in this attack based on his level of absorb
|
|
addTot = (atForceSpent/3)*attacked->client->ps.fd.forcePowerLevel[FP_ABSORB];
|
|
|
|
if (addTot < 1 && atForceSpent >= 1)
|
|
{
|
|
addTot = 1;
|
|
}
|
|
attacked->client->ps.fd.forcePower += addTot;
|
|
if (attacked->client->ps.fd.forcePower > 100)
|
|
{
|
|
attacked->client->ps.fd.forcePower = 100;
|
|
}
|
|
|
|
//play sound indicating that attack was absorbed
|
|
if (attacked->client->forcePowerSoundDebounce < level.time)
|
|
{
|
|
abSound = G_PreDefSound(attacked->client->ps.origin, PDSOUND_ABSORBHIT);
|
|
abSound->s.trickedentindex = attacked->s.number;
|
|
|
|
attacked->client->forcePowerSoundDebounce = level.time + 400;
|
|
}
|
|
|
|
return getLevel;
|
|
}
|
|
|
|
void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
|
|
{ //called on a regular interval to regenerate force power.
|
|
if ( !self->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( overrideAmt )
|
|
{ //custom regen amount
|
|
self->client->ps.fd.forcePower += overrideAmt;
|
|
}
|
|
else
|
|
{ //otherwise, just 1
|
|
self->client->ps.fd.forcePower++;
|
|
}
|
|
|
|
if ( self->client->ps.fd.forcePower > self->client->ps.fd.forcePowerMax )
|
|
{ //cap it off at the max (default 100)
|
|
self->client->ps.fd.forcePower = self->client->ps.fd.forcePowerMax;
|
|
}
|
|
}
|
|
|
|
void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
|
|
{ //activate the given force power
|
|
int duration = 0;
|
|
qboolean hearable = qfalse;
|
|
float hearDist = 0;
|
|
|
|
if (!WP_ForcePowerAvailable( self, forcePower, overrideAmt ))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( BG_FullBodyTauntAnim( self->client->ps.legsAnim ) )
|
|
{//stop taunt
|
|
self->client->ps.legsTimer = 0;
|
|
}
|
|
if ( BG_FullBodyTauntAnim( self->client->ps.torsoAnim ) )
|
|
{//stop taunt
|
|
self->client->ps.torsoTimer = 0;
|
|
}
|
|
//hearable and hearDist are merely for the benefit of bots, and not related to if a sound is actually played.
|
|
//If duration is set, the force power will assume to be timer-based.
|
|
switch( (int)forcePower )
|
|
{
|
|
case FP_HEAL:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_LEVITATION:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_SPEED:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_1)
|
|
{
|
|
duration = 10000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_2)
|
|
{
|
|
duration = 15000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_3)
|
|
{
|
|
duration = 20000;
|
|
}
|
|
else //shouldn't get here
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (overrideAmt)
|
|
{
|
|
duration = overrideAmt;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_PUSH:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
break;
|
|
case FP_PULL:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
break;
|
|
case FP_TELEPATHY:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_1)
|
|
{
|
|
duration = 20000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_2)
|
|
{
|
|
duration = 25000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_3)
|
|
{
|
|
duration = 30000;
|
|
}
|
|
else //shouldn't get here
|
|
{
|
|
break;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_GRIP:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
self->client->ps.powerups[PW_DISINT_4] = level.time + 60000;
|
|
break;
|
|
case FP_LIGHTNING:
|
|
hearable = qtrue;
|
|
hearDist = 512;
|
|
duration = overrideAmt;
|
|
overrideAmt = 0;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_LIGHTNING];
|
|
break;
|
|
case FP_RAGE:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1)
|
|
{
|
|
duration = 8000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2)
|
|
{
|
|
duration = 14000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3)
|
|
{
|
|
duration = 20000;
|
|
}
|
|
else //shouldn't get here
|
|
{
|
|
break;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_PROTECT:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
duration = 20000;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_ABSORB:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
duration = 20000;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_TEAM_HEAL:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_TEAM_FORCE:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_DRAIN:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
duration = overrideAmt;
|
|
overrideAmt = 0;
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
//self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN];
|
|
break;
|
|
case FP_SEE:
|
|
hearable = qtrue;
|
|
hearDist = 256;
|
|
if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1)
|
|
{
|
|
duration = 10000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2)
|
|
{
|
|
duration = 20000;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3)
|
|
{
|
|
duration = 30000;
|
|
}
|
|
else //shouldn't get here
|
|
{
|
|
break;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowersActive |= ( 1 << forcePower );
|
|
break;
|
|
case FP_SABER_OFFENSE:
|
|
break;
|
|
case FP_SABER_DEFENSE:
|
|
break;
|
|
case FP_SABERTHROW:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( duration )
|
|
{
|
|
self->client->ps.fd.forcePowerDuration[forcePower] = level.time + duration;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerDuration[forcePower] = 0;
|
|
}
|
|
|
|
if (hearable)
|
|
{
|
|
self->client->ps.otherSoundLen = hearDist;
|
|
self->client->ps.otherSoundTime = level.time + 100;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowerDebounce[forcePower] = 0;
|
|
|
|
if ((int)forcePower == FP_SPEED && overrideAmt)
|
|
{
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, overrideAmt*0.025 );
|
|
}
|
|
else if ((int)forcePower != FP_GRIP && (int)forcePower != FP_DRAIN)
|
|
{ //grip and drain drain as damage is done
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, overrideAmt );
|
|
}
|
|
}
|
|
|
|
void ForceHeal( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_HEAL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self->health >= self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_3)
|
|
{
|
|
self->health += 25; //This was 50, but that angered the Balance God.
|
|
|
|
if (self->health > self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
self->health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 );
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_2)
|
|
{
|
|
self->health += 10;
|
|
|
|
if (self->health > self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
self->health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 );
|
|
}
|
|
else
|
|
{
|
|
self->health += 5;
|
|
|
|
if (self->health > self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
self->health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 );
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
WP_ForcePowerStart( self, FP_HEAL, 0 );
|
|
}
|
|
*/
|
|
//NOTE: Decided to make all levels instant.
|
|
|
|
G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/weapons/force/heal.wav") );
|
|
}
|
|
|
|
void WP_AddToClientBitflags(gentity_t *ent, int entNum)
|
|
{
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (entNum > 47)
|
|
{
|
|
ent->s.trickedentindex4 |= (1 << (entNum-48));
|
|
}
|
|
else if (entNum > 31)
|
|
{
|
|
ent->s.trickedentindex3 |= (1 << (entNum-32));
|
|
}
|
|
else if (entNum > 15)
|
|
{
|
|
ent->s.trickedentindex2 |= (1 << (entNum-16));
|
|
}
|
|
else
|
|
{
|
|
ent->s.trickedentindex |= (1 << entNum);
|
|
}
|
|
}
|
|
|
|
void ForceTeamHeal( gentity_t *self )
|
|
{
|
|
float radius = 256;
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
vec3_t a;
|
|
int numpl = 0;
|
|
int pl[MAX_CLIENTS];
|
|
int healthadd = 0;
|
|
gentity_t *te = NULL;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_TEAM_HEAL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerDebounce[FP_TEAM_HEAL] >= level.time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL] == FORCE_LEVEL_2)
|
|
{
|
|
radius *= 1.5;
|
|
}
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL] == FORCE_LEVEL_3)
|
|
{
|
|
radius *= 2;
|
|
}
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->client && self != ent && OnSameTeam(self, ent) && ent->client->ps.stats[STAT_HEALTH] < ent->client->ps.stats[STAT_MAX_HEALTH] && ent->client->ps.stats[STAT_HEALTH] > 0 && ForcePowerUsableOn(self, ent, FP_TEAM_HEAL) &&
|
|
trap_InPVS(self->client->ps.origin, ent->client->ps.origin))
|
|
{
|
|
VectorSubtract(self->client->ps.origin, ent->client->ps.origin, a);
|
|
|
|
if (VectorLength(a) <= radius)
|
|
{
|
|
pl[numpl] = i;
|
|
numpl++;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (numpl < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numpl == 1)
|
|
{
|
|
healthadd = 50;
|
|
}
|
|
else if (numpl == 2)
|
|
{
|
|
healthadd = 33;
|
|
}
|
|
else
|
|
{
|
|
healthadd = 25;
|
|
}
|
|
|
|
self->client->ps.fd.forcePowerDebounce[FP_TEAM_HEAL] = level.time + 2000;
|
|
i = 0;
|
|
|
|
while (i < numpl)
|
|
{
|
|
if (g_entities[pl[i]].client->ps.stats[STAT_HEALTH] > 0 &&
|
|
g_entities[pl[i]].health > 0)
|
|
{
|
|
g_entities[pl[i]].client->ps.stats[STAT_HEALTH] += healthadd;
|
|
if (g_entities[pl[i]].client->ps.stats[STAT_HEALTH] > g_entities[pl[i]].client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
g_entities[pl[i]].client->ps.stats[STAT_HEALTH] = g_entities[pl[i]].client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
|
|
g_entities[pl[i]].health = g_entities[pl[i]].client->ps.stats[STAT_HEALTH];
|
|
|
|
//At this point we know we got one, so add him into the collective event client bitflag
|
|
if (!te)
|
|
{
|
|
te = G_TempEntity( self->client->ps.origin, EV_TEAM_POWER);
|
|
te->s.eventParm = 1; //eventParm 1 is heal, eventParm 2 is force regen
|
|
|
|
//since we had an extra check above, do the drain now because we got at least one guy
|
|
BG_ForcePowerDrain( &self->client->ps, FP_TEAM_HEAL, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL] );
|
|
}
|
|
|
|
WP_AddToClientBitflags(te, pl[i]);
|
|
//Now cramming it all into one event.. doing this many g_sound events at once was a Bad Thing.
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void ForceTeamForceReplenish( gentity_t *self )
|
|
{
|
|
float radius = 256;
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
vec3_t a;
|
|
int numpl = 0;
|
|
int pl[MAX_CLIENTS];
|
|
int poweradd = 0;
|
|
gentity_t *te = NULL;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_TEAM_FORCE ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerDebounce[FP_TEAM_FORCE] >= level.time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE] == FORCE_LEVEL_2)
|
|
{
|
|
radius *= 1.5;
|
|
}
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE] == FORCE_LEVEL_3)
|
|
{
|
|
radius *= 2;
|
|
}
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->client && self != ent && OnSameTeam(self, ent) && ent->client->ps.fd.forcePower < 100 && ForcePowerUsableOn(self, ent, FP_TEAM_FORCE) &&
|
|
trap_InPVS(self->client->ps.origin, ent->client->ps.origin))
|
|
{
|
|
VectorSubtract(self->client->ps.origin, ent->client->ps.origin, a);
|
|
|
|
if (VectorLength(a) <= radius)
|
|
{
|
|
pl[numpl] = i;
|
|
numpl++;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (numpl < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numpl == 1)
|
|
{
|
|
poweradd = 50;
|
|
}
|
|
else if (numpl == 2)
|
|
{
|
|
poweradd = 33;
|
|
}
|
|
else
|
|
{
|
|
poweradd = 25;
|
|
}
|
|
self->client->ps.fd.forcePowerDebounce[FP_TEAM_FORCE] = level.time + 2000;
|
|
|
|
BG_ForcePowerDrain( &self->client->ps, FP_TEAM_FORCE, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE] );
|
|
|
|
i = 0;
|
|
|
|
while (i < numpl)
|
|
{
|
|
g_entities[pl[i]].client->ps.fd.forcePower += poweradd;
|
|
if (g_entities[pl[i]].client->ps.fd.forcePower > 100)
|
|
{
|
|
g_entities[pl[i]].client->ps.fd.forcePower = 100;
|
|
}
|
|
|
|
//At this point we know we got one, so add him into the collective event client bitflag
|
|
if (!te)
|
|
{
|
|
te = G_TempEntity( self->client->ps.origin, EV_TEAM_POWER);
|
|
te->s.eventParm = 2; //eventParm 1 is heal, eventParm 2 is force regen
|
|
}
|
|
|
|
WP_AddToClientBitflags(te, pl[i]);
|
|
//Now cramming it all into one event.. doing this many g_sound events at once was a Bad Thing.
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void ForceGrip( gentity_t *self )
|
|
{
|
|
trace_t tr;
|
|
vec3_t tfrom, tto, fwd;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceGripUseTime > level.time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_GRIP ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy(self->client->ps.origin, tfrom);
|
|
tfrom[2] += self->client->ps.viewheight;
|
|
AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL);
|
|
tto[0] = tfrom[0] + fwd[0]*MAX_GRIP_DISTANCE;
|
|
tto[1] = tfrom[1] + fwd[1]*MAX_GRIP_DISTANCE;
|
|
tto[2] = tfrom[2] + fwd[2]*MAX_GRIP_DISTANCE;
|
|
|
|
trap_Trace(&tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID);
|
|
|
|
if ( tr.fraction != 1.0 &&
|
|
tr.entityNum != ENTITYNUM_NONE &&
|
|
g_entities[tr.entityNum].client &&
|
|
!g_entities[tr.entityNum].client->ps.fd.forceGripCripple &&
|
|
g_entities[tr.entityNum].client->ps.fd.forceGripBeingGripped < level.time &&
|
|
ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_GRIP) &&
|
|
(g_friendlyFire.integer || !OnSameTeam(self, &g_entities[tr.entityNum])) ) //don't grip someone who's still crippled
|
|
{
|
|
if (g_entities[tr.entityNum].s.number < MAX_CLIENTS && g_entities[tr.entityNum].client->ps.m_iVehicleNum)
|
|
{ //a player on a vehicle
|
|
gentity_t *vehEnt = &g_entities[g_entities[tr.entityNum].client->ps.m_iVehicleNum];
|
|
if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
|
|
{
|
|
if (vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER ||
|
|
vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
|
|
{ //push the guy off
|
|
vehEnt->m_pVehicle->m_pVehicleInfo->Eject(vehEnt->m_pVehicle, (bgEntity_t *)&g_entities[tr.entityNum], qfalse);
|
|
}
|
|
}
|
|
}
|
|
self->client->ps.fd.forceGripEntityNum = tr.entityNum;
|
|
g_entities[tr.entityNum].client->ps.fd.forceGripStarted = level.time;
|
|
self->client->ps.fd.forceGripDamageDebounceTime = 0;
|
|
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD;
|
|
self->client->ps.forceHandExtendTime = level.time + 5000;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ForceSpeed( gentity_t *self, int forceDuration )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_SPEED)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_SPEED );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_SPEED ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self->client->holdingObjectiveItem >= MAX_CLIENTS
|
|
&& self->client->holdingObjectiveItem < ENTITYNUM_WORLD )
|
|
{//holding Siege item
|
|
if ( g_entities[self->client->holdingObjectiveItem].genericValue15 )
|
|
{//disables force powers
|
|
return;
|
|
}
|
|
}
|
|
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
WP_ForcePowerStart( self, FP_SPEED, forceDuration );
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/speed.wav") );
|
|
G_Sound( self, TRACK_CHANNEL_2, speedLoopSound );
|
|
}
|
|
|
|
void ForceSeeing( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_SEE)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_SEE );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_SEE ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
WP_ForcePowerStart( self, FP_SEE, 0 );
|
|
|
|
G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/see.wav") );
|
|
G_Sound( self, TRACK_CHANNEL_5, seeLoopSound );
|
|
}
|
|
|
|
void ForceProtect( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_PROTECT );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_PROTECT ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure to turn off Force Rage and Force Absorb.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_RAGE );
|
|
}
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_ABSORB );
|
|
}
|
|
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
WP_ForcePowerStart( self, FP_PROTECT, 0 );
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_PROTECT);
|
|
G_Sound( self, TRACK_CHANNEL_3, protectLoopSound );
|
|
}
|
|
|
|
void ForceAbsorb( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_ABSORB );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_ABSORB ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure to turn off Force Rage and Force Protection.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_RAGE );
|
|
}
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_PROTECT );
|
|
}
|
|
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
WP_ForcePowerStart( self, FP_ABSORB, 0 );
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_ABSORB);
|
|
G_Sound( self, TRACK_CHANNEL_3, absorbLoopSound );
|
|
}
|
|
|
|
void ForceRage( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_RAGE );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_RAGE ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceRageRecoveryTime >= level.time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->health < 10)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure to turn off Force Protection and Force Absorb.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_PROTECT );
|
|
}
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_ABSORB );
|
|
}
|
|
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
WP_ForcePowerStart( self, FP_RAGE, 0 );
|
|
|
|
G_Sound( self, TRACK_CHANNEL_4, G_SoundIndex("sound/weapons/force/rage.wav") );
|
|
G_Sound( self, TRACK_CHANNEL_3, rageLoopSound );
|
|
}
|
|
|
|
void ForceLightning( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->client->ps.fd.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING ) )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->client->ps.fd.forcePowerDebounce[FP_LIGHTNING] > level.time )
|
|
{//stops it while using it and also after using it, up to 3 second delay
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Shoot lightning from hand
|
|
//using grip anim now, to extend the burst time
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD;
|
|
self->client->ps.forceHandExtendTime = level.time + 20000;
|
|
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/lightning") );
|
|
|
|
WP_ForcePowerStart( self, FP_LIGHTNING, 500 );
|
|
}
|
|
|
|
void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint )
|
|
{
|
|
self->client->dangerTime = level.time;
|
|
self->client->ps.eFlags &= ~EF_INVULNERABLE;
|
|
self->client->invulnerableTimer = 0;
|
|
|
|
if ( traceEnt && traceEnt->takedamage )
|
|
{
|
|
if (!traceEnt->client && traceEnt->s.eType == ET_NPC)
|
|
{ //g2animent
|
|
if (traceEnt->s.genericenemyindex < level.time)
|
|
{
|
|
traceEnt->s.genericenemyindex = level.time + 2000;
|
|
}
|
|
}
|
|
if ( traceEnt->client )
|
|
{//an enemy or object
|
|
if (traceEnt->client->noLightningTime >= level.time)
|
|
{ //give them power and don't hurt them.
|
|
traceEnt->client->ps.fd.forcePower++;
|
|
if (traceEnt->client->ps.fd.forcePower > 100)
|
|
{
|
|
traceEnt->client->ps.fd.forcePower = 100;
|
|
}
|
|
return;
|
|
}
|
|
if (ForcePowerUsableOn(self, traceEnt, FP_LIGHTNING))
|
|
{
|
|
int dmg = Q_irand(1,2); //Q_irand( 1, 3 );
|
|
|
|
int modPowerLevel = -1;
|
|
|
|
if (traceEnt->client)
|
|
{
|
|
modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.fd.forcePowerLevel[FP_LIGHTNING], 1);
|
|
}
|
|
|
|
if (modPowerLevel != -1)
|
|
{
|
|
if (!modPowerLevel)
|
|
{
|
|
dmg = 0;
|
|
traceEnt->client->noLightningTime = level.time + 400;
|
|
}
|
|
else if (modPowerLevel == 1)
|
|
{
|
|
dmg = 1;
|
|
traceEnt->client->noLightningTime = level.time + 300;
|
|
}
|
|
else if (modPowerLevel == 2)
|
|
{
|
|
dmg = 1;
|
|
traceEnt->client->noLightningTime = level.time + 100;
|
|
}
|
|
}
|
|
|
|
if ( self->client->ps.weapon == WP_MELEE
|
|
&& self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
|
|
{//2-handed lightning
|
|
//jackin' 'em up, Palpatine-style
|
|
dmg *= 2;
|
|
}
|
|
|
|
if (dmg)
|
|
{
|
|
//rww - Shields can now absorb lightning too.
|
|
G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK );
|
|
}
|
|
if ( traceEnt->client )
|
|
{
|
|
if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( va("sound/weapons/force/lightninghit%i", Q_irand(1, 3) )) );
|
|
}
|
|
|
|
if (traceEnt->client->ps.electrifyTime < (level.time + 400))
|
|
{ //only update every 400ms to reduce bandwidth usage (as it is passing a 32-bit time value)
|
|
traceEnt->client->ps.electrifyTime = level.time + 800;
|
|
}
|
|
if ( traceEnt->client->ps.powerups[PW_CLOAKED] )
|
|
{//disable cloak temporarily
|
|
Jedi_Decloak( traceEnt );
|
|
traceEnt->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ForceShootLightning( gentity_t *self )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end, forward;
|
|
gentity_t *traceEnt;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
|
|
VectorNormalize( forward );
|
|
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
|
|
{//arc
|
|
vec3_t center, mins, maxs, dir, ent_org, size, v;
|
|
float radius = FORCE_LIGHTNING_RADIUS, dot, dist;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int iEntityList[MAX_GENTITIES];
|
|
int e, numListedEntities, i;
|
|
|
|
VectorCopy( self->client->ps.origin, center );
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = center[i] - radius;
|
|
maxs[i] = center[i] + radius;
|
|
}
|
|
numListedEntities = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES );
|
|
|
|
i = 0;
|
|
while (i < numListedEntities)
|
|
{
|
|
entityList[i] = &g_entities[iEntityList[i]];
|
|
|
|
i++;
|
|
}
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
traceEnt = entityList[e];
|
|
|
|
if ( !traceEnt )
|
|
continue;
|
|
if ( traceEnt == self )
|
|
continue;
|
|
if ( traceEnt->r.ownerNum == self->s.number && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
|
|
continue;
|
|
if ( !traceEnt->inuse )
|
|
continue;
|
|
if ( !traceEnt->takedamage )
|
|
continue;
|
|
if ( traceEnt->health <= 0 )//no torturing corpses
|
|
continue;
|
|
if ( !g_friendlyFire.integer && OnSameTeam(self, traceEnt))
|
|
continue;
|
|
//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( center[i] < traceEnt->r.absmin[i] )
|
|
{
|
|
v[i] = traceEnt->r.absmin[i] - center[i];
|
|
} else if ( center[i] > traceEnt->r.absmax[i] )
|
|
{
|
|
v[i] = center[i] - traceEnt->r.absmax[i];
|
|
} else
|
|
{
|
|
v[i] = 0;
|
|
}
|
|
}
|
|
|
|
VectorSubtract( traceEnt->r.absmax, traceEnt->r.absmin, size );
|
|
VectorMA( traceEnt->r.absmin, 0.5, size, ent_org );
|
|
|
|
//see if they're in front of me
|
|
//must be within the forward cone
|
|
VectorSubtract( ent_org, center, dir );
|
|
VectorNormalize( dir );
|
|
if ( (dot = DotProduct( dir, forward )) < 0.5 )
|
|
continue;
|
|
|
|
//must be close enough
|
|
dist = VectorLength( v );
|
|
if ( dist >= radius )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//in PVS?
|
|
if ( !traceEnt->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) )
|
|
{//must be in PVS
|
|
continue;
|
|
}
|
|
|
|
//Now check and see if we can actually hit it
|
|
trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT );
|
|
if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
|
|
{//must have clear LOS
|
|
continue;
|
|
}
|
|
|
|
// ok, we are within the radius, add us to the incoming list
|
|
ForceLightningDamage( self, traceEnt, dir, ent_org );
|
|
}
|
|
}
|
|
else
|
|
{//trace-line
|
|
VectorMA( self->client->ps.origin, 2048, forward, end );
|
|
|
|
trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
|
|
if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
ForceLightningDamage( self, traceEnt, forward, tr.endpos );
|
|
}
|
|
}
|
|
|
|
void ForceDrain( gentity_t *self )
|
|
{
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self->client->ps.fd.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN ) )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->client->ps.fd.forcePowerDebounce[FP_DRAIN] > level.time )
|
|
{//stops it while using it and also after using it, up to 3 second delay
|
|
return;
|
|
}
|
|
|
|
// self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
// self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD;
|
|
self->client->ps.forceHandExtendTime = level.time + 20000;
|
|
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/drain.wav") );
|
|
|
|
WP_ForcePowerStart( self, FP_DRAIN, 500 );
|
|
}
|
|
|
|
void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint )
|
|
{
|
|
gentity_t *tent;
|
|
|
|
self->client->dangerTime = level.time;
|
|
self->client->ps.eFlags &= ~EF_INVULNERABLE;
|
|
self->client->invulnerableTimer = 0;
|
|
|
|
if ( traceEnt && traceEnt->takedamage )
|
|
{
|
|
if ( traceEnt->client && (!OnSameTeam(self, traceEnt) || g_friendlyFire.integer) && self->client->ps.fd.forceDrainTime < level.time && traceEnt->client->ps.fd.forcePower )
|
|
{//an enemy or object
|
|
if (!traceEnt->client && traceEnt->s.eType == ET_NPC)
|
|
{ //g2animent
|
|
if (traceEnt->s.genericenemyindex < level.time)
|
|
{
|
|
traceEnt->s.genericenemyindex = level.time + 2000;
|
|
}
|
|
}
|
|
if (ForcePowerUsableOn(self, traceEnt, FP_DRAIN))
|
|
{
|
|
int modPowerLevel = -1;
|
|
int dmg = 0; //Q_irand( 1, 3 );
|
|
if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1)
|
|
{
|
|
dmg = 2; //because it's one-shot
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_2)
|
|
{
|
|
dmg = 3;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_3)
|
|
{
|
|
dmg = 4;
|
|
}
|
|
|
|
if (traceEnt->client)
|
|
{
|
|
modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.fd.forcePowerLevel[FP_DRAIN], 1);
|
|
}
|
|
|
|
if (modPowerLevel != -1)
|
|
{
|
|
if (!modPowerLevel)
|
|
{
|
|
dmg = 0;
|
|
}
|
|
else if (modPowerLevel == 1)
|
|
{
|
|
dmg = 1;
|
|
}
|
|
else if (modPowerLevel == 2)
|
|
{
|
|
dmg = 2;
|
|
}
|
|
}
|
|
//G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK );
|
|
|
|
if (dmg)
|
|
{
|
|
traceEnt->client->ps.fd.forcePower -= (dmg);
|
|
}
|
|
if (traceEnt->client->ps.fd.forcePower < 0)
|
|
{
|
|
traceEnt->client->ps.fd.forcePower = 0;
|
|
}
|
|
|
|
if (self->client->ps.stats[STAT_HEALTH] < self->client->ps.stats[STAT_MAX_HEALTH] &&
|
|
self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0)
|
|
{
|
|
self->health += dmg;
|
|
if (self->health > self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
self->health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
self->client->ps.stats[STAT_HEALTH] = self->health;
|
|
}
|
|
|
|
traceEnt->client->ps.fd.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away
|
|
|
|
//Drain the standard amount since we just drained someone else
|
|
|
|
/*
|
|
if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1)
|
|
{
|
|
BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 0 );
|
|
}
|
|
else
|
|
{
|
|
BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_DRAIN]][FP_DRAIN]/5 );
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forceDrainTime = level.time + 100;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forceDrainTime = level.time + 20;
|
|
}
|
|
|
|
if ( traceEnt->client )
|
|
{
|
|
if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
//G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( "sound/weapons/force/lightninghit.wav" ) );
|
|
}
|
|
// traceEnt->s.powerups |= ( 1 << PW_DISINT_1 );
|
|
|
|
// traceEnt->client->ps.powerups[PW_DISINT_1] = level.time + 500;
|
|
}
|
|
*/
|
|
|
|
if (traceEnt->client->forcePowerSoundDebounce < level.time)
|
|
{
|
|
tent = G_TempEntity( impactPoint, EV_FORCE_DRAINED);
|
|
tent->s.eventParm = DirToByte(dir);
|
|
tent->s.owner = traceEnt->s.number;
|
|
|
|
traceEnt->client->forcePowerSoundDebounce = level.time + 400;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int ForceShootDrain( gentity_t *self )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end, forward;
|
|
gentity_t *traceEnt;
|
|
int gotOneOrMore = 0;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
|
|
VectorNormalize( forward );
|
|
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
|
|
{//arc
|
|
vec3_t center, mins, maxs, dir, ent_org, size, v;
|
|
float radius = MAX_DRAIN_DISTANCE, dot, dist;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int iEntityList[MAX_GENTITIES];
|
|
int e, numListedEntities, i;
|
|
|
|
VectorCopy( self->client->ps.origin, center );
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = center[i] - radius;
|
|
maxs[i] = center[i] + radius;
|
|
}
|
|
numListedEntities = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES );
|
|
|
|
i = 0;
|
|
while (i < numListedEntities)
|
|
{
|
|
entityList[i] = &g_entities[iEntityList[i]];
|
|
|
|
i++;
|
|
}
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
traceEnt = entityList[e];
|
|
|
|
if ( !traceEnt )
|
|
continue;
|
|
if ( traceEnt == self )
|
|
continue;
|
|
if ( !traceEnt->inuse )
|
|
continue;
|
|
if ( !traceEnt->takedamage )
|
|
continue;
|
|
if ( traceEnt->health <= 0 )//no torturing corpses
|
|
continue;
|
|
if ( !traceEnt->client )
|
|
continue;
|
|
if ( !traceEnt->client->ps.fd.forcePower )
|
|
continue;
|
|
if (OnSameTeam(self, traceEnt) && !g_friendlyFire.integer)
|
|
continue;
|
|
//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( center[i] < traceEnt->r.absmin[i] )
|
|
{
|
|
v[i] = traceEnt->r.absmin[i] - center[i];
|
|
} else if ( center[i] > traceEnt->r.absmax[i] )
|
|
{
|
|
v[i] = center[i] - traceEnt->r.absmax[i];
|
|
} else
|
|
{
|
|
v[i] = 0;
|
|
}
|
|
}
|
|
|
|
VectorSubtract( traceEnt->r.absmax, traceEnt->r.absmin, size );
|
|
VectorMA( traceEnt->r.absmin, 0.5, size, ent_org );
|
|
|
|
//see if they're in front of me
|
|
//must be within the forward cone
|
|
VectorSubtract( ent_org, center, dir );
|
|
VectorNormalize( dir );
|
|
if ( (dot = DotProduct( dir, forward )) < 0.5 )
|
|
continue;
|
|
|
|
//must be close enough
|
|
dist = VectorLength( v );
|
|
if ( dist >= radius )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//in PVS?
|
|
if ( !traceEnt->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) )
|
|
{//must be in PVS
|
|
continue;
|
|
}
|
|
|
|
//Now check and see if we can actually hit it
|
|
trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT );
|
|
if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
|
|
{//must have clear LOS
|
|
continue;
|
|
}
|
|
|
|
// ok, we are within the radius, add us to the incoming list
|
|
ForceDrainDamage( self, traceEnt, dir, ent_org );
|
|
gotOneOrMore = 1;
|
|
}
|
|
}
|
|
else
|
|
{//trace-line
|
|
VectorMA( self->client->ps.origin, 2048, forward, end );
|
|
|
|
trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
|
|
if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid || !g_entities[tr.entityNum].client || !g_entities[tr.entityNum].inuse )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
ForceDrainDamage( self, traceEnt, forward, tr.endpos );
|
|
gotOneOrMore = 1;
|
|
}
|
|
|
|
self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN] + FORCE_LEVEL_3;
|
|
|
|
BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 5 ); //used to be 1, but this did, too, anger the God of Balance.
|
|
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + 500;
|
|
|
|
return gotOneOrMore;
|
|
}
|
|
|
|
void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
|
|
{ //I guess this is unused now. Was used for the "charge" jump type.
|
|
float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!self->client->ps.fd.forceJumpCharge && self->client->ps.groundEntityNum == ENTITYNUM_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePower < forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION])
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
return;
|
|
}
|
|
|
|
if (!self->client->ps.fd.forceJumpCharge)
|
|
{
|
|
self->client->ps.fd.forceJumpAddTime = 0;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceJumpAddTime >= level.time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//need to play sound
|
|
if ( !self->client->ps.fd.forceJumpCharge )
|
|
{
|
|
G_Sound( self, TRACK_CHANNEL_1, G_SoundIndex("sound/weapons/force/jumpbuild.wav") );
|
|
}
|
|
|
|
//Increment
|
|
if (self->client->ps.fd.forceJumpAddTime < level.time)
|
|
{
|
|
self->client->ps.fd.forceJumpCharge += forceJumpChargeInterval*50;
|
|
self->client->ps.fd.forceJumpAddTime = level.time + 500;
|
|
}
|
|
|
|
//clamp to max strength for current level
|
|
if ( self->client->ps.fd.forceJumpCharge > forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]] )
|
|
{
|
|
self->client->ps.fd.forceJumpCharge = forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]];
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
}
|
|
|
|
//clamp to max available force power
|
|
if ( self->client->ps.fd.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION] > self->client->ps.fd.forcePower )
|
|
{//can't use more than you have
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
self->client->ps.fd.forceJumpCharge = self->client->ps.fd.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
|
|
}
|
|
|
|
//G_Printf("%f\n", self->client->ps.fd.forceJumpCharge);
|
|
}
|
|
|
|
int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
|
|
{
|
|
float pushFwd = 0, pushRt = 0;
|
|
vec3_t view, forward, right;
|
|
VectorCopy( self->client->ps.viewangles, view );
|
|
view[0] = 0;
|
|
AngleVectors( view, forward, right, NULL );
|
|
if ( ucmd->forwardmove && ucmd->rightmove )
|
|
{
|
|
if ( ucmd->forwardmove > 0 )
|
|
{
|
|
pushFwd = 50;
|
|
}
|
|
else
|
|
{
|
|
pushFwd = -50;
|
|
}
|
|
if ( ucmd->rightmove > 0 )
|
|
{
|
|
pushRt = 50;
|
|
}
|
|
else
|
|
{
|
|
pushRt = -50;
|
|
}
|
|
}
|
|
else if ( ucmd->forwardmove || ucmd->rightmove )
|
|
{
|
|
if ( ucmd->forwardmove > 0 )
|
|
{
|
|
pushFwd = 100;
|
|
}
|
|
else if ( ucmd->forwardmove < 0 )
|
|
{
|
|
pushFwd = -100;
|
|
}
|
|
else if ( ucmd->rightmove > 0 )
|
|
{
|
|
pushRt = 100;
|
|
}
|
|
else if ( ucmd->rightmove < 0 )
|
|
{
|
|
pushRt = -100;
|
|
}
|
|
}
|
|
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP);
|
|
|
|
if (self->client->ps.fd.forceJumpCharge < JUMP_VELOCITY+40)
|
|
{ //give him at least a tiny boost from just a tap
|
|
self->client->ps.fd.forceJumpCharge = JUMP_VELOCITY+400;
|
|
}
|
|
|
|
if (self->client->ps.velocity[2] < -30)
|
|
{ //so that we can get a good boost when force jumping in a fall
|
|
self->client->ps.velocity[2] = -30;
|
|
}
|
|
|
|
VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
|
|
VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
|
|
jumpVel[2] += self->client->ps.fd.forceJumpCharge;
|
|
if ( pushFwd > 0 && self->client->ps.fd.forceJumpCharge > 200 )
|
|
{
|
|
return FJ_FORWARD;
|
|
}
|
|
else if ( pushFwd < 0 && self->client->ps.fd.forceJumpCharge > 200 )
|
|
{
|
|
return FJ_BACKWARD;
|
|
}
|
|
else if ( pushRt > 0 && self->client->ps.fd.forceJumpCharge > 200 )
|
|
{
|
|
return FJ_RIGHT;
|
|
}
|
|
else if ( pushRt < 0 && self->client->ps.fd.forceJumpCharge > 200 )
|
|
{
|
|
return FJ_LEFT;
|
|
}
|
|
else
|
|
{
|
|
return FJ_UP;
|
|
}
|
|
}
|
|
|
|
void ForceJump( gentity_t *self, usercmd_t *ucmd )
|
|
{
|
|
float forceJumpChargeInterval;
|
|
vec3_t jumpVel;
|
|
|
|
if ( self->client->ps.fd.forcePowerDuration[FP_LEVITATION] > level.time )
|
|
{
|
|
return;
|
|
}
|
|
if ( !WP_ForcePowerUsable( self, FP_LEVITATION ) )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->s.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->client->fjDidJump = qtrue;
|
|
|
|
forceJumpChargeInterval = forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
|
|
|
|
WP_GetVelocityForForceJump( self, jumpVel, ucmd );
|
|
|
|
//FIXME: sound effect
|
|
self->client->ps.fd.forceJumpZStart = self->client->ps.origin[2];//remember this for when we land
|
|
VectorCopy( jumpVel, self->client->ps.velocity );
|
|
//wasn't allowing them to attack when jumping, but that was annoying
|
|
//self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
|
|
|
|
WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.fd.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION] );
|
|
//self->client->ps.fd.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
|
|
self->client->ps.fd.forceJumpCharge = 0;
|
|
self->client->ps.forceJumpFlip = qtrue;
|
|
}
|
|
|
|
void WP_AddAsMindtricked(forcedata_t *fd, int entNum)
|
|
{
|
|
if (!fd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (entNum > 47)
|
|
{
|
|
fd->forceMindtrickTargetIndex4 |= (1 << (entNum-48));
|
|
}
|
|
else if (entNum > 31)
|
|
{
|
|
fd->forceMindtrickTargetIndex3 |= (1 << (entNum-32));
|
|
}
|
|
else if (entNum > 15)
|
|
{
|
|
fd->forceMindtrickTargetIndex2 |= (1 << (entNum-16));
|
|
}
|
|
else
|
|
{
|
|
fd->forceMindtrickTargetIndex |= (1 << entNum);
|
|
}
|
|
}
|
|
|
|
qboolean ForceTelepathyCheckDirectNPCTarget( gentity_t *self, trace_t *tr, qboolean *tookPower )
|
|
{
|
|
gentity_t *traceEnt;
|
|
qboolean targetLive = qfalse, mindTrickDone = qfalse;
|
|
vec3_t tfrom, tto, fwd;
|
|
float radius = MAX_TRICK_DISTANCE;
|
|
|
|
//Check for a direct usage on NPCs first
|
|
VectorCopy(self->client->ps.origin, tfrom);
|
|
tfrom[2] += self->client->ps.viewheight;
|
|
AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL);
|
|
tto[0] = tfrom[0] + fwd[0]*radius/2;
|
|
tto[1] = tfrom[1] + fwd[1]*radius/2;
|
|
tto[2] = tfrom[2] + fwd[2]*radius/2;
|
|
|
|
trap_Trace( tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID );
|
|
|
|
if ( tr->entityNum == ENTITYNUM_NONE
|
|
|| tr->fraction == 1.0f
|
|
|| tr->allsolid
|
|
|| tr->startsolid )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
traceEnt = &g_entities[tr->entityNum];
|
|
|
|
if( traceEnt->NPC
|
|
&& traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( traceEnt && traceEnt->client )
|
|
{
|
|
switch ( traceEnt->client->NPC_class )
|
|
{
|
|
case CLASS_GALAKMECH://cant grip him, he's in armor
|
|
case CLASS_ATST://much too big to grip!
|
|
//no droids either
|
|
case CLASS_PROBE:
|
|
case CLASS_GONK:
|
|
case CLASS_R2D2:
|
|
case CLASS_R5D2:
|
|
case CLASS_MARK1:
|
|
case CLASS_MARK2:
|
|
case CLASS_MOUSE:
|
|
case CLASS_SEEKER:
|
|
case CLASS_REMOTE:
|
|
case CLASS_PROTOCOL:
|
|
case CLASS_BOBAFETT:
|
|
case CLASS_RANCOR:
|
|
break;
|
|
default:
|
|
targetLive = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( traceEnt->s.number < MAX_CLIENTS )
|
|
{//a regular client
|
|
return qfalse;
|
|
}
|
|
|
|
if ( targetLive && traceEnt->NPC )
|
|
{//hit an organic non-player
|
|
vec3_t eyeDir;
|
|
if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
|
|
{//activated a script on him
|
|
//FIXME: do the visual sparkles effect on their heads, still?
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
|
|
}
|
|
else if ( (self->NPC && traceEnt->client->playerTeam != self->client->playerTeam)
|
|
|| (!self->NPC && traceEnt->client->playerTeam != self->client->sess.sessionTeam) )
|
|
{//an enemy
|
|
int override = 0;
|
|
if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
|
|
{
|
|
}
|
|
else if ( traceEnt->s.weapon != WP_SABER
|
|
&& traceEnt->client->NPC_class != CLASS_REBORN )
|
|
{//haha! Jedi aren't easily confused!
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 )
|
|
{//turn them to our side
|
|
//if mind trick 3 and aiming at an enemy need more force power
|
|
if ( traceEnt->s.weapon != WP_NONE )
|
|
{//don't charm people who aren't capable of fighting... like ugnaughts and droids
|
|
int newPlayerTeam, newEnemyTeam;
|
|
|
|
if ( traceEnt->enemy )
|
|
{
|
|
G_ClearEnemy( traceEnt );
|
|
}
|
|
if ( traceEnt->NPC )
|
|
{
|
|
//traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
|
|
traceEnt->client->leader = self;
|
|
}
|
|
//FIXME: maybe pick an enemy right here?
|
|
if ( self->NPC )
|
|
{//NPC
|
|
newPlayerTeam = self->client->playerTeam;
|
|
newEnemyTeam = self->client->enemyTeam;
|
|
}
|
|
else
|
|
{//client/bot
|
|
if ( self->client->sess.sessionTeam == TEAM_BLUE )
|
|
{//rebel
|
|
newPlayerTeam = NPCTEAM_PLAYER;
|
|
newEnemyTeam = NPCTEAM_ENEMY;
|
|
}
|
|
else if ( self->client->sess.sessionTeam == TEAM_RED )
|
|
{//imperial
|
|
newPlayerTeam = NPCTEAM_ENEMY;
|
|
newEnemyTeam = NPCTEAM_PLAYER;
|
|
}
|
|
else
|
|
{//neutral - wan't attack anyone
|
|
newPlayerTeam = NPCTEAM_NEUTRAL;
|
|
newEnemyTeam = NPCTEAM_NEUTRAL;
|
|
}
|
|
}
|
|
//store these for retrieval later
|
|
traceEnt->genericValue1 = traceEnt->client->playerTeam;
|
|
traceEnt->genericValue2 = traceEnt->client->enemyTeam;
|
|
traceEnt->genericValue3 = traceEnt->s.teamowner;
|
|
//set the new values
|
|
traceEnt->client->playerTeam = newPlayerTeam;
|
|
traceEnt->client->enemyTeam = newEnemyTeam;
|
|
traceEnt->s.teamowner = newPlayerTeam;
|
|
//FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
|
|
traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.fd.forcePowerLevel[FP_TELEPATHY]];
|
|
}
|
|
}
|
|
else
|
|
{//just confuse them
|
|
//somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot?
|
|
traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.fd.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
|
|
NPC_PlayConfusionSound( traceEnt );
|
|
if ( traceEnt->enemy )
|
|
{
|
|
G_ClearEnemy( traceEnt );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NPC_Jedi_PlayConfusionSound( traceEnt );
|
|
}
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, override );
|
|
}
|
|
else if ( traceEnt->client->playerTeam == self->client->playerTeam )
|
|
{//an ally
|
|
//maybe just have him look at you? Respond? Take your enemy?
|
|
if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
|
|
{
|
|
NPC_UseResponse( traceEnt, self, qfalse );
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
|
|
}
|
|
}//NOTE: no effect on TEAM_NEUTRAL?
|
|
AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
|
|
VectorNormalize( eyeDir );
|
|
G_PlayEffectID( G_EffectIndex( "force/force_touch" ), traceEnt->client->renderInfo.eyePoint, eyeDir );
|
|
|
|
//make sure this plays and that you cannot press fire for about 1 second after this
|
|
//FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
|
|
//NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
|
|
//FIXME: build-up or delay this until in proper part of anim
|
|
mindTrickDone = qtrue;
|
|
}
|
|
else
|
|
{
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr->fraction * 2048 > 64 )
|
|
{//don't create a diversion less than 64 from you of if at power level 1
|
|
//use distraction anim instead
|
|
G_PlayEffectID( G_EffectIndex( "force/force_touch" ), tr->endpos, tr->plane.normal );
|
|
//FIXME: these events don't seem to always be picked up...?
|
|
AddSoundEvent( self, tr->endpos, 512, AEL_SUSPICIOUS, qtrue );//, qtrue );
|
|
AddSightEvent( self, tr->endpos, 512, AEL_SUSPICIOUS, 50 );
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
|
|
*tookPower = qtrue;
|
|
}
|
|
//NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
|
|
}
|
|
//self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
|
|
self->client->ps.saberBlocked = BLOCKED_NONE;
|
|
self->client->ps.weaponTime = 1000;
|
|
/*
|
|
if ( self->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
|
|
{
|
|
self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
|
|
}
|
|
*/
|
|
return qtrue;
|
|
}
|
|
|
|
void ForceTelepathy(gentity_t *self)
|
|
{
|
|
trace_t tr;
|
|
vec3_t tto, thispush_org, a;
|
|
vec3_t mins, maxs, fwdangles, forward, right, center;
|
|
int i;
|
|
float visionArc = 0;
|
|
float radius = MAX_TRICK_DISTANCE;
|
|
qboolean tookPower = qfalse;
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.powerups[PW_REDFLAG] ||
|
|
self->client->ps.powerups[PW_BLUEFLAG])
|
|
{ //can't mindtrick while carrying the flag
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.forceAllowDeactivateTime < level.time &&
|
|
(self->client->ps.fd.forcePowersActive & (1 << FP_TELEPATHY)) )
|
|
{
|
|
WP_ForcePowerStop( self, FP_TELEPATHY );
|
|
return;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, FP_TELEPATHY ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ForceTelepathyCheckDirectNPCTarget( self, &tr, &tookPower ) )
|
|
{//hit an NPC directly
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") );
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_2)
|
|
{
|
|
visionArc = 180;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_3)
|
|
{
|
|
visionArc = 360;
|
|
radius = MAX_TRICK_DISTANCE*2.0f;
|
|
}
|
|
|
|
VectorCopy( self->client->ps.viewangles, fwdangles );
|
|
AngleVectors( fwdangles, forward, right, NULL );
|
|
VectorCopy( self->client->ps.origin, center );
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = center[i] - radius;
|
|
maxs[i] = center[i] + radius;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_1)
|
|
{
|
|
if (tr.fraction != 1.0 &&
|
|
tr.entityNum != ENTITYNUM_NONE &&
|
|
g_entities[tr.entityNum].inuse &&
|
|
g_entities[tr.entityNum].client &&
|
|
g_entities[tr.entityNum].client->pers.connected &&
|
|
g_entities[tr.entityNum].client->sess.sessionTeam != TEAM_SPECTATOR)
|
|
{
|
|
WP_AddAsMindtricked(&self->client->ps.fd, tr.entityNum);
|
|
if ( !tookPower )
|
|
{
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
|
|
}
|
|
|
|
G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") );
|
|
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else //level 2 & 3
|
|
{
|
|
gentity_t *ent;
|
|
int entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
int e = 0;
|
|
qboolean gotatleastone = qfalse;
|
|
|
|
numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
while (e < numListedEntities)
|
|
{
|
|
ent = &g_entities[entityList[e]];
|
|
|
|
if (ent)
|
|
{ //not in the arc, don't consider it
|
|
if (ent->client)
|
|
{
|
|
VectorCopy(ent->client->ps.origin, thispush_org);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->s.pos.trBase, thispush_org);
|
|
}
|
|
VectorCopy(self->client->ps.origin, tto);
|
|
tto[2] += self->client->ps.viewheight;
|
|
VectorSubtract(thispush_org, tto, a);
|
|
vectoangles(a, a);
|
|
|
|
if (!ent->client)
|
|
{
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
else if (!InFieldOfVision(self->client->ps.viewangles, visionArc, a))
|
|
{ //only bother with arc rules if the victim is a client
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
else if (!ForcePowerUsableOn(self, ent, FP_TELEPATHY))
|
|
{
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
else if (OnSameTeam(self, ent))
|
|
{
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
}
|
|
ent = &g_entities[entityList[e]];
|
|
if (ent && ent != self && ent->client)
|
|
{
|
|
gotatleastone = qtrue;
|
|
WP_AddAsMindtricked(&self->client->ps.fd, ent->s.number);
|
|
}
|
|
e++;
|
|
}
|
|
|
|
if (gotatleastone)
|
|
{
|
|
self->client->ps.forceAllowDeactivateTime = level.time + 1500;
|
|
|
|
if ( !tookPower )
|
|
{
|
|
WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
|
|
}
|
|
|
|
G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") );
|
|
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void GEntity_UseFunc( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
GlobalUse(self, other, activator);
|
|
}
|
|
|
|
qboolean CanCounterThrow(gentity_t *self, gentity_t *thrower, qboolean pull)
|
|
{
|
|
int powerUse = 0;
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( self->client->ps.powerups[PW_DISINT_4] > level.time )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (self->client->ps.weaponstate == WEAPON_CHARGING ||
|
|
self->client->ps.weaponstate == WEAPON_CHARGING_ALT)
|
|
{ //don't autodefend when charging a weapon
|
|
return 0;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
pull &&
|
|
thrower && thrower->client)
|
|
{ //in siege, pull will affect people if they are not facing you, so they can't run away so much
|
|
vec3_t d;
|
|
float a;
|
|
|
|
VectorSubtract(thrower->client->ps.origin, self->client->ps.origin, d);
|
|
vectoangles(d, d);
|
|
|
|
a = AngleSubtract(d[YAW], self->client->ps.viewangles[YAW]);
|
|
|
|
if (a > 60.0f || a < -60.0f)
|
|
{ //if facing more than 60 degrees away they cannot defend
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (pull)
|
|
{
|
|
powerUse = FP_PULL;
|
|
}
|
|
else
|
|
{
|
|
powerUse = FP_PUSH;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, powerUse ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (self->client->ps.groundEntityNum == ENTITYNUM_NONE)
|
|
{ //you cannot counter a push/pull if you're in the air
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
qboolean G_InGetUpAnim(playerState_t *ps)
|
|
{
|
|
switch( (ps->legsAnim) )
|
|
{
|
|
case BOTH_GETUP1:
|
|
case BOTH_GETUP2:
|
|
case BOTH_GETUP3:
|
|
case BOTH_GETUP4:
|
|
case BOTH_GETUP5:
|
|
case BOTH_FORCE_GETUP_F1:
|
|
case BOTH_FORCE_GETUP_F2:
|
|
case BOTH_FORCE_GETUP_B1:
|
|
case BOTH_FORCE_GETUP_B2:
|
|
case BOTH_FORCE_GETUP_B3:
|
|
case BOTH_FORCE_GETUP_B4:
|
|
case BOTH_FORCE_GETUP_B5:
|
|
case BOTH_GETUP_BROLL_B:
|
|
case BOTH_GETUP_BROLL_F:
|
|
case BOTH_GETUP_BROLL_L:
|
|
case BOTH_GETUP_BROLL_R:
|
|
case BOTH_GETUP_FROLL_B:
|
|
case BOTH_GETUP_FROLL_F:
|
|
case BOTH_GETUP_FROLL_L:
|
|
case BOTH_GETUP_FROLL_R:
|
|
return qtrue;
|
|
}
|
|
|
|
switch( (ps->torsoAnim) )
|
|
{
|
|
case BOTH_GETUP1:
|
|
case BOTH_GETUP2:
|
|
case BOTH_GETUP3:
|
|
case BOTH_GETUP4:
|
|
case BOTH_GETUP5:
|
|
case BOTH_FORCE_GETUP_F1:
|
|
case BOTH_FORCE_GETUP_F2:
|
|
case BOTH_FORCE_GETUP_B1:
|
|
case BOTH_FORCE_GETUP_B2:
|
|
case BOTH_FORCE_GETUP_B3:
|
|
case BOTH_FORCE_GETUP_B4:
|
|
case BOTH_FORCE_GETUP_B5:
|
|
case BOTH_GETUP_BROLL_B:
|
|
case BOTH_GETUP_BROLL_F:
|
|
case BOTH_GETUP_BROLL_L:
|
|
case BOTH_GETUP_BROLL_R:
|
|
case BOTH_GETUP_FROLL_B:
|
|
case BOTH_GETUP_FROLL_F:
|
|
case BOTH_GETUP_FROLL_L:
|
|
case BOTH_GETUP_FROLL_R:
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
void G_LetGoOfWall( gentity_t *ent )
|
|
{
|
|
if ( !ent || !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
ent->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL;
|
|
if ( BG_InReboundJump( ent->client->ps.legsAnim )
|
|
|| BG_InReboundHold( ent->client->ps.legsAnim ) )
|
|
{
|
|
ent->client->ps.legsTimer = 0;
|
|
}
|
|
if ( BG_InReboundJump( ent->client->ps.torsoAnim )
|
|
|| BG_InReboundHold( ent->client->ps.torsoAnim ) )
|
|
{
|
|
ent->client->ps.torsoTimer = 0;
|
|
}
|
|
}
|
|
|
|
float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
|
|
{
|
|
0,//none
|
|
384,//256,
|
|
448,//384,
|
|
512
|
|
};
|
|
//rwwFIXMEFIXME: incorporate this into the below function? Currently it's only being used by jedi AI
|
|
|
|
extern void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace );
|
|
void ForceThrow( gentity_t *self, qboolean pull )
|
|
{
|
|
//shove things in front of you away
|
|
float dist;
|
|
gentity_t *ent;
|
|
int entityList[MAX_GENTITIES];
|
|
gentity_t *push_list[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t mins, maxs;
|
|
vec3_t v;
|
|
int i, e;
|
|
int ent_count = 0;
|
|
int radius = 1024; //since it's view-based now. //350;
|
|
int powerLevel;
|
|
int visionArc;
|
|
int pushPower;
|
|
int pushPowerMod;
|
|
vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0};
|
|
float dot1;
|
|
trace_t tr;
|
|
int x;
|
|
vec3_t pushDir;
|
|
vec3_t thispush_org;
|
|
vec3_t tfrom, tto, fwd, a;
|
|
float knockback = pull?0:200;
|
|
int powerUse = 0;
|
|
|
|
visionArc = 0;
|
|
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE && (self->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN || !G_InGetUpAnim(&self->client->ps)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!g_useWhileThrowing.integer && self->client->ps.saberInFlight)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.weaponTime > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->client->ps.powerups[PW_DISINT_4] > level.time )
|
|
{
|
|
return;
|
|
}
|
|
if (pull)
|
|
{
|
|
powerUse = FP_PULL;
|
|
}
|
|
else
|
|
{
|
|
powerUse = FP_PUSH;
|
|
}
|
|
|
|
if ( !WP_ForcePowerUsable( self, powerUse ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pull && self->client->ps.saberLockTime > level.time && self->client->ps.saberLockFrame)
|
|
{
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) );
|
|
self->client->ps.powerups[PW_DISINT_4] = level.time + 1500;
|
|
|
|
self->client->ps.saberLockHits += self->client->ps.fd.forcePowerLevel[FP_PUSH]*2;
|
|
|
|
WP_ForcePowerStart( self, FP_PUSH, 0 );
|
|
return;
|
|
}
|
|
|
|
WP_ForcePowerStart( self, powerUse, 0 );
|
|
|
|
//make sure this plays and that you cannot press fire for about 1 second after this
|
|
if ( pull )
|
|
{
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_NONE)
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCEPULL;
|
|
if ( g_gametype.integer == GT_SIEGE && self->client->ps.weapon == WP_SABER )
|
|
{//hold less so can attack right after a pull
|
|
self->client->ps.forceHandExtendTime = level.time + 200;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.forceHandExtendTime = level.time + 400;
|
|
}
|
|
}
|
|
self->client->ps.powerups[PW_DISINT_4] = self->client->ps.forceHandExtendTime + 200;
|
|
self->client->ps.powerups[PW_PULL] = self->client->ps.powerups[PW_DISINT_4];
|
|
}
|
|
else
|
|
{
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) );
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_NONE)
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
}
|
|
else if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN && G_InGetUpAnim(&self->client->ps))
|
|
{
|
|
if (self->client->ps.forceDodgeAnim > 4)
|
|
{
|
|
self->client->ps.forceDodgeAnim -= 8;
|
|
}
|
|
self->client->ps.forceDodgeAnim += 8; //special case, play push on upper torso, but keep playing current knockdown anim on legs
|
|
}
|
|
self->client->ps.powerups[PW_DISINT_4] = level.time + 1100;
|
|
self->client->ps.powerups[PW_PULL] = 0;
|
|
}
|
|
|
|
VectorCopy( self->client->ps.viewangles, fwdangles );
|
|
AngleVectors( fwdangles, forward, right, NULL );
|
|
VectorCopy( self->client->ps.origin, center );
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = center[i] - radius;
|
|
maxs[i] = center[i] + radius;
|
|
}
|
|
|
|
|
|
if (pull)
|
|
{
|
|
powerLevel = self->client->ps.fd.forcePowerLevel[FP_PULL];
|
|
pushPower = 256*self->client->ps.fd.forcePowerLevel[FP_PULL];
|
|
}
|
|
else
|
|
{
|
|
powerLevel = self->client->ps.fd.forcePowerLevel[FP_PUSH];
|
|
pushPower = 256*self->client->ps.fd.forcePowerLevel[FP_PUSH];
|
|
}
|
|
|
|
if (!powerLevel)
|
|
{ //Shouldn't have made it here..
|
|
return;
|
|
}
|
|
|
|
if (powerLevel == FORCE_LEVEL_2)
|
|
{
|
|
visionArc = 60;
|
|
}
|
|
else if (powerLevel == FORCE_LEVEL_3)
|
|
{
|
|
visionArc = 180;
|
|
}
|
|
|
|
if (powerLevel == FORCE_LEVEL_1)
|
|
{ //can only push/pull targeted things at level 1
|
|
VectorCopy(self->client->ps.origin, tfrom);
|
|
tfrom[2] += self->client->ps.viewheight;
|
|
AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL);
|
|
tto[0] = tfrom[0] + fwd[0]*radius/2;
|
|
tto[1] = tfrom[1] + fwd[1]*radius/2;
|
|
tto[2] = tfrom[2] + fwd[2]*radius/2;
|
|
|
|
trap_Trace(&tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID);
|
|
|
|
if (tr.fraction != 1.0 &&
|
|
tr.entityNum != ENTITYNUM_NONE)
|
|
{
|
|
if (!g_entities[tr.entityNum].client && g_entities[tr.entityNum].s.eType == ET_NPC)
|
|
{ //g2animent
|
|
if (g_entities[tr.entityNum].s.genericenemyindex < level.time)
|
|
{
|
|
g_entities[tr.entityNum].s.genericenemyindex = level.time + 2000;
|
|
}
|
|
}
|
|
|
|
numListedEntities = 0;
|
|
entityList[numListedEntities] = tr.entityNum;
|
|
|
|
if (pull)
|
|
{
|
|
if (!ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_PULL))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_PUSH))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
numListedEntities++;
|
|
}
|
|
else
|
|
{
|
|
//didn't get anything, so just
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
e = 0;
|
|
|
|
while (e < numListedEntities)
|
|
{
|
|
ent = &g_entities[entityList[e]];
|
|
|
|
if (!ent->client && ent->s.eType == ET_NPC)
|
|
{ //g2animent
|
|
if (ent->s.genericenemyindex < level.time)
|
|
{
|
|
ent->s.genericenemyindex = level.time + 2000;
|
|
}
|
|
}
|
|
|
|
if (ent)
|
|
{
|
|
if (ent->client)
|
|
{
|
|
VectorCopy(ent->client->ps.origin, thispush_org);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->s.pos.trBase, thispush_org);
|
|
}
|
|
}
|
|
|
|
if (ent)
|
|
{ //not in the arc, don't consider it
|
|
VectorCopy(self->client->ps.origin, tto);
|
|
tto[2] += self->client->ps.viewheight;
|
|
VectorSubtract(thispush_org, tto, a);
|
|
vectoangles(a, a);
|
|
|
|
if (ent->client && !InFieldOfVision(self->client->ps.viewangles, visionArc, a) &&
|
|
ForcePowerUsableOn(self, ent, powerUse))
|
|
{ //only bother with arc rules if the victim is a client
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
else if (ent->client)
|
|
{
|
|
if (pull)
|
|
{
|
|
if (!ForcePowerUsableOn(self, ent, FP_PULL))
|
|
{
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!ForcePowerUsableOn(self, ent, FP_PUSH))
|
|
{
|
|
entityList[e] = ENTITYNUM_NONE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
e++;
|
|
}
|
|
}
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
if (entityList[e] != ENTITYNUM_NONE &&
|
|
entityList[e] >= 0 &&
|
|
entityList[e] < MAX_GENTITIES)
|
|
{
|
|
ent = &g_entities[entityList[e]];
|
|
}
|
|
else
|
|
{
|
|
ent = NULL;
|
|
}
|
|
|
|
if (!ent)
|
|
continue;
|
|
if (ent == self)
|
|
continue;
|
|
if (ent->client && OnSameTeam(ent, self))
|
|
{
|
|
continue;
|
|
}
|
|
if ( !(ent->inuse) )
|
|
continue;
|
|
if ( ent->s.eType != ET_MISSILE )
|
|
{
|
|
if ( ent->s.eType != ET_ITEM )
|
|
{
|
|
//FIXME: need pushable objects
|
|
if ( Q_stricmp( "func_button", ent->classname ) == 0 )
|
|
{//we might push it
|
|
if ( pull || !(ent->spawnflags&SPF_BUTTON_FPUSHABLE) )
|
|
{//not force-pushable, never pullable
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ent->s.eFlags & EF_NODRAW )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !ent->client )
|
|
{
|
|
if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
|
|
{//not a lightsaber
|
|
if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
|
|
{//not a force-usable door
|
|
if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) )
|
|
{//not a force-usable func_static
|
|
if ( Q_stricmp( "limb", ent->classname ) )
|
|
{//not a limb
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
|
|
{//not at rest
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if ( ent->client->NPC_class == CLASS_GALAKMECH
|
|
|| ent->client->NPC_class == CLASS_ATST
|
|
|| ent->client->NPC_class == CLASS_RANCOR )
|
|
{//can't push ATST or Galak or Rancor
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
|
|
{//can't force-push/pull stuck missiles (detpacks, tripmines)
|
|
continue;
|
|
}
|
|
if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
|
|
{//only thermal detonators can be pushed once stopped
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( center[i] < ent->r.absmin[i] )
|
|
{
|
|
v[i] = ent->r.absmin[i] - center[i];
|
|
} else if ( center[i] > ent->r.absmax[i] )
|
|
{
|
|
v[i] = center[i] - ent->r.absmax[i];
|
|
} else
|
|
{
|
|
v[i] = 0;
|
|
}
|
|
}
|
|
|
|
VectorSubtract( ent->r.absmax, ent->r.absmin, size );
|
|
VectorMA( ent->r.absmin, 0.5, size, ent_org );
|
|
|
|
VectorSubtract( ent_org, center, dir );
|
|
VectorNormalize( dir );
|
|
if ( (dot1 = DotProduct( dir, forward )) < 0.6 )
|
|
continue;
|
|
|
|
dist = VectorLength( v );
|
|
|
|
//Now check and see if we can actually deflect it
|
|
//method1
|
|
//if within a certain range, deflect it
|
|
if ( dist >= radius )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//in PVS?
|
|
if ( !ent->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) )
|
|
{//must be in PVS
|
|
continue;
|
|
}
|
|
|
|
//really should have a clear LOS to this thing...
|
|
trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT );
|
|
if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
|
|
{//must have clear LOS
|
|
//try from eyes too before you give up
|
|
vec3_t eyePoint;
|
|
VectorCopy(self->client->ps.origin, eyePoint);
|
|
eyePoint[2] += self->client->ps.viewheight;
|
|
trap_Trace( &tr, eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT );
|
|
|
|
if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// ok, we are within the radius, add us to the incoming list
|
|
push_list[ent_count] = ent;
|
|
ent_count++;
|
|
}
|
|
|
|
if ( ent_count )
|
|
{
|
|
//method1:
|
|
for ( x = 0; x < ent_count; x++ )
|
|
{
|
|
int modPowerLevel = powerLevel;
|
|
|
|
|
|
if (push_list[x]->client)
|
|
{
|
|
modPowerLevel = WP_AbsorbConversion(push_list[x], push_list[x]->client->ps.fd.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[powerUse]][powerUse]);
|
|
if (modPowerLevel == -1)
|
|
{
|
|
modPowerLevel = powerLevel;
|
|
}
|
|
}
|
|
|
|
pushPower = 256*modPowerLevel;
|
|
|
|
if (push_list[x]->client)
|
|
{
|
|
VectorCopy(push_list[x]->client->ps.origin, thispush_org);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(push_list[x]->s.origin, thispush_org);
|
|
}
|
|
|
|
if ( push_list[x]->client )
|
|
{//FIXME: make enemy jedi able to hunker down and resist this?
|
|
int otherPushPower = push_list[x]->client->ps.fd.forcePowerLevel[powerUse];
|
|
qboolean canPullWeapon = qtrue;
|
|
float dirLen = 0;
|
|
|
|
if ( g_debugMelee.integer )
|
|
{
|
|
if ( (push_list[x]->client->ps.pm_flags&PMF_STUCK_TO_WALL) )
|
|
{//no resistance if stuck to wall
|
|
//push/pull them off the wall
|
|
otherPushPower = 0;
|
|
G_LetGoOfWall( push_list[x] );
|
|
}
|
|
}
|
|
|
|
knockback = pull?0:200;
|
|
|
|
pushPowerMod = pushPower;
|
|
|
|
if (push_list[x]->client->pers.cmd.forwardmove ||
|
|
push_list[x]->client->pers.cmd.rightmove)
|
|
{ //if you are moving, you get one less level of defense
|
|
otherPushPower--;
|
|
|
|
if (otherPushPower < 0)
|
|
{
|
|
otherPushPower = 0;
|
|
}
|
|
}
|
|
|
|
if (otherPushPower && CanCounterThrow(push_list[x], self, pull))
|
|
{
|
|
if ( pull )
|
|
{
|
|
G_Sound( push_list[x], CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
|
|
push_list[x]->client->ps.forceHandExtend = HANDEXTEND_FORCEPULL;
|
|
push_list[x]->client->ps.forceHandExtendTime = level.time + 400;
|
|
}
|
|
else
|
|
{
|
|
G_Sound( push_list[x], CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) );
|
|
push_list[x]->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH;
|
|
push_list[x]->client->ps.forceHandExtendTime = level.time + 1000;
|
|
}
|
|
push_list[x]->client->ps.powerups[PW_DISINT_4] = push_list[x]->client->ps.forceHandExtendTime + 200;
|
|
|
|
if (pull)
|
|
{
|
|
push_list[x]->client->ps.powerups[PW_PULL] = push_list[x]->client->ps.powerups[PW_DISINT_4];
|
|
}
|
|
else
|
|
{
|
|
push_list[x]->client->ps.powerups[PW_PULL] = 0;
|
|
}
|
|
|
|
//Make a counter-throw effect
|
|
|
|
if (otherPushPower >= modPowerLevel)
|
|
{
|
|
pushPowerMod = 0;
|
|
canPullWeapon = qfalse;
|
|
}
|
|
else
|
|
{
|
|
int powerDif = (modPowerLevel - otherPushPower);
|
|
|
|
if (powerDif >= 3)
|
|
{
|
|
pushPowerMod -= pushPowerMod*0.2;
|
|
}
|
|
else if (powerDif == 2)
|
|
{
|
|
pushPowerMod -= pushPowerMod*0.4;
|
|
}
|
|
else if (powerDif == 1)
|
|
{
|
|
pushPowerMod -= pushPowerMod*0.8;
|
|
}
|
|
|
|
if (pushPowerMod < 0)
|
|
{
|
|
pushPowerMod = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//shove them
|
|
if ( pull )
|
|
{
|
|
VectorSubtract( self->client->ps.origin, thispush_org, pushDir );
|
|
|
|
if (push_list[x]->client && VectorLength(pushDir) <= 256)
|
|
{
|
|
int randfact = 0;
|
|
|
|
if (modPowerLevel == FORCE_LEVEL_1)
|
|
{
|
|
randfact = 3;
|
|
}
|
|
else if (modPowerLevel == FORCE_LEVEL_2)
|
|
{
|
|
randfact = 7;
|
|
}
|
|
else if (modPowerLevel == FORCE_LEVEL_3)
|
|
{
|
|
randfact = 10;
|
|
}
|
|
|
|
if (!OnSameTeam(self, push_list[x]) && Q_irand(1, 10) <= randfact && canPullWeapon)
|
|
{
|
|
vec3_t uorg, vecnorm;
|
|
|
|
VectorCopy(self->client->ps.origin, uorg);
|
|
uorg[2] += 64;
|
|
|
|
VectorSubtract(uorg, thispush_org, vecnorm);
|
|
VectorNormalize(vecnorm);
|
|
|
|
TossClientWeapon(push_list[x], vecnorm, 500);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( thispush_org, self->client->ps.origin, pushDir );
|
|
}
|
|
|
|
if ((modPowerLevel > otherPushPower || push_list[x]->client->ps.m_iVehicleNum) && push_list[x]->client)
|
|
{
|
|
if (modPowerLevel == FORCE_LEVEL_3 &&
|
|
push_list[x]->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN)
|
|
{
|
|
dirLen = VectorLength(pushDir);
|
|
|
|
if (BG_KnockDownable(&push_list[x]->client->ps) &&
|
|
dirLen <= (64*((modPowerLevel - otherPushPower)-1)))
|
|
{ //can only do a knockdown if fairly close
|
|
push_list[x]->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
|
|
push_list[x]->client->ps.forceHandExtendTime = level.time + 700;
|
|
push_list[x]->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
|
|
push_list[x]->client->ps.quickerGetup = qtrue;
|
|
}
|
|
else if (push_list[x]->s.number < MAX_CLIENTS && push_list[x]->client->ps.m_iVehicleNum &&
|
|
dirLen <= 128.0f )
|
|
{ //a player on a vehicle
|
|
gentity_t *vehEnt = &g_entities[push_list[x]->client->ps.m_iVehicleNum];
|
|
if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
|
|
{
|
|
if (vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER ||
|
|
vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
|
|
{ //push the guy off
|
|
vehEnt->m_pVehicle->m_pVehicleInfo->Eject(vehEnt->m_pVehicle, (bgEntity_t *)push_list[x], qfalse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!dirLen)
|
|
{
|
|
dirLen = VectorLength(pushDir);
|
|
}
|
|
|
|
VectorNormalize(pushDir);
|
|
|
|
if (push_list[x]->client)
|
|
{
|
|
//escape a force grip if we're in one
|
|
if (self->client->ps.fd.forceGripBeingGripped > level.time)
|
|
{ //force the enemy to stop gripping me if I managed to push him
|
|
if (push_list[x]->client->ps.fd.forceGripEntityNum == self->s.number)
|
|
{
|
|
if (modPowerLevel >= push_list[x]->client->ps.fd.forcePowerLevel[FP_GRIP])
|
|
{ //only break the grip if our push/pull level is >= their grip level
|
|
WP_ForcePowerStop(push_list[x], FP_GRIP);
|
|
self->client->ps.fd.forceGripBeingGripped = 0;
|
|
push_list[x]->client->ps.fd.forceGripUseTime = level.time + 1000; //since we just broke out of it..
|
|
}
|
|
}
|
|
}
|
|
|
|
push_list[x]->client->ps.otherKiller = self->s.number;
|
|
push_list[x]->client->ps.otherKillerTime = level.time + 5000;
|
|
push_list[x]->client->ps.otherKillerDebounceTime = level.time + 100;
|
|
push_list[x]->client->otherKillerMOD = MOD_UNKNOWN;
|
|
push_list[x]->client->otherKillerVehWeapon = 0;
|
|
push_list[x]->client->otherKillerWeaponType = WP_NONE;
|
|
|
|
pushPowerMod -= (dirLen*0.7);
|
|
if (pushPowerMod < 16)
|
|
{
|
|
pushPowerMod = 16;
|
|
}
|
|
|
|
//fullbody push effect
|
|
push_list[x]->client->pushEffectTime = level.time + 600;
|
|
|
|
push_list[x]->client->ps.velocity[0] = pushDir[0]*pushPowerMod;
|
|
push_list[x]->client->ps.velocity[1] = pushDir[1]*pushPowerMod;
|
|
|
|
if ((int)push_list[x]->client->ps.velocity[2] == 0)
|
|
{ //if not going anywhere vertically, boost them up a bit
|
|
push_list[x]->client->ps.velocity[2] = pushDir[2]*pushPowerMod;
|
|
|
|
if (push_list[x]->client->ps.velocity[2] < 128)
|
|
{
|
|
push_list[x]->client->ps.velocity[2] = 128;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
push_list[x]->client->ps.velocity[2] = pushDir[2]*pushPowerMod;
|
|
}
|
|
}
|
|
}
|
|
else if ( push_list[x]->s.eType == ET_MISSILE && push_list[x]->s.pos.trType != TR_STATIONARY && (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
|
|
{
|
|
if ( pull )
|
|
{//deflect rather than reflect?
|
|
}
|
|
else
|
|
{
|
|
G_ReflectMissile( self, push_list[x], forward );
|
|
}
|
|
}
|
|
else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
|
|
{//force-usable func_static
|
|
if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
|
|
{
|
|
GEntity_UseFunc( push_list[x], self, self );
|
|
}
|
|
else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
|
|
{
|
|
GEntity_UseFunc( push_list[x], self, self );
|
|
}
|
|
}
|
|
else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2) )
|
|
{//push/pull the door
|
|
vec3_t pos1, pos2;
|
|
vec3_t trFrom;
|
|
|
|
VectorCopy(self->client->ps.origin, trFrom);
|
|
trFrom[2] += self->client->ps.viewheight;
|
|
|
|
AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
|
|
VectorNormalize( forward );
|
|
VectorMA( trFrom, radius, forward, end );
|
|
trap_Trace( &tr, trFrom, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
|
|
if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
|
|
{//must be pointing right at it
|
|
continue;
|
|
}
|
|
|
|
if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
|
|
{//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
|
|
VectorSubtract( push_list[x]->r.absmax, push_list[x]->r.absmin, size );
|
|
VectorMA( push_list[x]->r.absmin, 0.5, size, center );
|
|
if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
|
|
{//if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
|
|
VectorSubtract( center, push_list[x]->pos1, center );
|
|
}
|
|
else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
|
|
{//if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
|
|
VectorSubtract( center, push_list[x]->pos2, center );
|
|
}
|
|
VectorAdd( center, push_list[x]->pos1, pos1 );
|
|
VectorAdd( center, push_list[x]->pos2, pos2 );
|
|
}
|
|
else
|
|
{//actually has an origin, pos1 and pos2 are absolute
|
|
VectorCopy( push_list[x]->r.currentOrigin, center );
|
|
VectorCopy( push_list[x]->pos1, pos1 );
|
|
VectorCopy( push_list[x]->pos2, pos2 );
|
|
}
|
|
|
|
if ( Distance( pos1, trFrom ) < Distance( pos2, trFrom ) )
|
|
{//pos1 is closer
|
|
if ( push_list[x]->moverState == MOVER_POS1 )
|
|
{//at the closest pos
|
|
if ( pull )
|
|
{//trying to pull, but already at closest point, so screw it
|
|
continue;
|
|
}
|
|
}
|
|
else if ( push_list[x]->moverState == MOVER_POS2 )
|
|
{//at farthest pos
|
|
if ( !pull )
|
|
{//trying to push, but already at farthest point, so screw it
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//pos2 is closer
|
|
if ( push_list[x]->moverState == MOVER_POS1 )
|
|
{//at the farthest pos
|
|
if ( !pull )
|
|
{//trying to push, but already at farthest point, so screw it
|
|
continue;
|
|
}
|
|
}
|
|
else if ( push_list[x]->moverState == MOVER_POS2 )
|
|
{//at closest pos
|
|
if ( pull )
|
|
{//trying to pull, but already at closest point, so screw it
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
GEntity_UseFunc( push_list[x], self, self );
|
|
}
|
|
else if ( Q_stricmp( "func_button", push_list[x]->classname ) == 0 )
|
|
{//pretend you pushed it
|
|
Touch_Button( push_list[x], self, NULL );
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//attempt to break any leftover grips
|
|
//if we're still in a current grip that wasn't broken by the push, it will still remain
|
|
self->client->dangerTime = level.time;
|
|
self->client->ps.eFlags &= ~EF_INVULNERABLE;
|
|
self->client->invulnerableTimer = 0;
|
|
|
|
if (self->client->ps.fd.forceGripBeingGripped > level.time)
|
|
{
|
|
self->client->ps.fd.forceGripBeingGripped = 0;
|
|
}
|
|
}
|
|
|
|
void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
|
|
{
|
|
int wasActive = self->client->ps.fd.forcePowersActive;
|
|
|
|
self->client->ps.fd.forcePowersActive &= ~( 1 << forcePower );
|
|
|
|
switch( (int)forcePower )
|
|
{
|
|
case FP_HEAL:
|
|
self->client->ps.fd.forceHealAmount = 0;
|
|
self->client->ps.fd.forceHealTime = 0;
|
|
break;
|
|
case FP_LEVITATION:
|
|
break;
|
|
case FP_SPEED:
|
|
if (wasActive & (1 << FP_SPEED))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_2-50], CHAN_VOICE);
|
|
}
|
|
break;
|
|
case FP_PUSH:
|
|
break;
|
|
case FP_PULL:
|
|
break;
|
|
case FP_TELEPATHY:
|
|
if (wasActive & (1 << FP_TELEPATHY))
|
|
{
|
|
G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distractstop.wav") );
|
|
}
|
|
self->client->ps.fd.forceMindtrickTargetIndex = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex2 = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex3 = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex4 = 0;
|
|
break;
|
|
case FP_SEE:
|
|
if (wasActive & (1 << FP_SEE))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_5-50], CHAN_VOICE);
|
|
}
|
|
break;
|
|
case FP_GRIP:
|
|
self->client->ps.fd.forceGripUseTime = level.time + 3000;
|
|
if (self->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 &&
|
|
g_entities[self->client->ps.fd.forceGripEntityNum].client &&
|
|
g_entities[self->client->ps.fd.forceGripEntityNum].health > 0 &&
|
|
g_entities[self->client->ps.fd.forceGripEntityNum].inuse &&
|
|
(level.time - g_entities[self->client->ps.fd.forceGripEntityNum].client->ps.fd.forceGripStarted) > 500)
|
|
{ //if we had our throat crushed in for more than half a second, gasp for air when we're let go
|
|
if (wasActive & (1 << FP_GRIP))
|
|
{
|
|
G_EntitySound( &g_entities[self->client->ps.fd.forceGripEntityNum], CHAN_VOICE, G_SoundIndex("*gasp.wav") );
|
|
}
|
|
}
|
|
|
|
if (g_entities[self->client->ps.fd.forceGripEntityNum].client &&
|
|
g_entities[self->client->ps.fd.forceGripEntityNum].inuse)
|
|
{
|
|
|
|
g_entities[self->client->ps.fd.forceGripEntityNum].client->ps.forceGripChangeMovetype = PM_NORMAL;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD)
|
|
{
|
|
self->client->ps.forceHandExtendTime = 0;
|
|
}
|
|
|
|
self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE;
|
|
|
|
self->client->ps.powerups[PW_DISINT_4] = 0;
|
|
break;
|
|
case FP_LIGHTNING:
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
|
|
{//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
|
|
self->client->ps.fd.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerDebounce[FP_LIGHTNING] = level.time + 1500;
|
|
}
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD)
|
|
{
|
|
self->client->ps.forceHandExtendTime = 0; //reset hand position
|
|
}
|
|
|
|
self->client->ps.activeForcePass = 0;
|
|
break;
|
|
case FP_RAGE:
|
|
self->client->ps.fd.forceRageRecoveryTime = level.time + 10000;
|
|
if (wasActive & (1 << FP_RAGE))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE);
|
|
}
|
|
break;
|
|
case FP_ABSORB:
|
|
if (wasActive & (1 << FP_ABSORB))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE);
|
|
}
|
|
break;
|
|
case FP_PROTECT:
|
|
if (wasActive & (1 << FP_PROTECT))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE);
|
|
}
|
|
break;
|
|
case FP_DRAIN:
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
|
|
{//don't do it again for 3 seconds, minimum...
|
|
self->client->ps.fd.forcePowerDebounce[FP_DRAIN] = level.time + 3000;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerDebounce[FP_DRAIN] = level.time + 1500;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD)
|
|
{
|
|
self->client->ps.forceHandExtendTime = 0; //reset hand position
|
|
}
|
|
|
|
self->client->ps.activeForcePass = 0;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DoGripAction(gentity_t *self, forcePowers_t forcePower)
|
|
{
|
|
gentity_t *gripEnt;
|
|
int gripLevel = 0;
|
|
trace_t tr;
|
|
vec3_t a;
|
|
vec3_t fwd, fwd_o, start_o, nvel;
|
|
|
|
self->client->dangerTime = level.time;
|
|
self->client->ps.eFlags &= ~EF_INVULNERABLE;
|
|
self->client->invulnerableTimer = 0;
|
|
|
|
gripEnt = &g_entities[self->client->ps.fd.forceGripEntityNum];
|
|
|
|
if (!gripEnt || !gripEnt->client || !gripEnt->inuse || gripEnt->health < 1 || !ForcePowerUsableOn(self, gripEnt, FP_GRIP))
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE;
|
|
|
|
if (gripEnt && gripEnt->client && gripEnt->inuse)
|
|
{
|
|
gripEnt->client->ps.forceGripChangeMovetype = PM_NORMAL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
VectorSubtract(gripEnt->client->ps.origin, self->client->ps.origin, a);
|
|
|
|
trap_Trace(&tr, self->client->ps.origin, NULL, NULL, gripEnt->client->ps.origin, self->s.number, MASK_PLAYERSOLID);
|
|
|
|
gripLevel = WP_AbsorbConversion(gripEnt, gripEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.fd.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP]);
|
|
|
|
if (gripLevel == -1)
|
|
{
|
|
gripLevel = self->client->ps.fd.forcePowerLevel[FP_GRIP];
|
|
}
|
|
|
|
if (!gripLevel)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
return;
|
|
}
|
|
|
|
if (VectorLength(a) > MAX_GRIP_DISTANCE)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
return;
|
|
}
|
|
|
|
if ( !InFront( gripEnt->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.9f ) &&
|
|
gripLevel < FORCE_LEVEL_3)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
return;
|
|
}
|
|
|
|
if (tr.fraction != 1.0f &&
|
|
tr.entityNum != gripEnt->s.number /*&&
|
|
gripLevel < FORCE_LEVEL_3*/)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerDebounce[FP_GRIP] < level.time)
|
|
{ //2 damage per second while choking, resulting in 10 damage total (not including The Squeeze<tm>)
|
|
self->client->ps.fd.forcePowerDebounce[FP_GRIP] = level.time + 1000;
|
|
G_Damage(gripEnt, self, self, NULL, NULL, 2, DAMAGE_NO_ARMOR, MOD_FORCE_DARK);
|
|
}
|
|
|
|
Jetpack_Off(gripEnt); //make sure the guy being gripped has his jetpack off.
|
|
|
|
if (gripLevel == FORCE_LEVEL_1)
|
|
{
|
|
gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000;
|
|
|
|
if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 5000)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (gripLevel == FORCE_LEVEL_2)
|
|
{
|
|
gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000;
|
|
|
|
if (gripEnt->client->ps.forceGripMoveInterval < level.time)
|
|
{
|
|
gripEnt->client->ps.velocity[2] = 30;
|
|
|
|
gripEnt->client->ps.forceGripMoveInterval = level.time + 300; //only update velocity every 300ms, so as to avoid heavy bandwidth usage
|
|
}
|
|
|
|
gripEnt->client->ps.otherKiller = self->s.number;
|
|
gripEnt->client->ps.otherKillerTime = level.time + 5000;
|
|
gripEnt->client->ps.otherKillerDebounceTime = level.time + 100;
|
|
gripEnt->client->otherKillerMOD = MOD_UNKNOWN;
|
|
gripEnt->client->otherKillerVehWeapon = 0;
|
|
gripEnt->client->otherKillerWeaponType = WP_NONE;
|
|
|
|
gripEnt->client->ps.forceGripChangeMovetype = PM_FLOAT;
|
|
|
|
if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 3000 && !self->client->ps.fd.forceGripDamageDebounceTime)
|
|
{ //if we managed to lift him into the air for 2 seconds, give him a crack
|
|
self->client->ps.fd.forceGripDamageDebounceTime = 1;
|
|
G_Damage(gripEnt, self, self, NULL, NULL, 20, DAMAGE_NO_ARMOR, MOD_FORCE_DARK);
|
|
|
|
//Must play custom sounds on the actual entity. Don't use G_Sound (it creates a temp entity for the sound)
|
|
G_EntitySound( gripEnt, CHAN_VOICE, G_SoundIndex(va( "*choke%d.wav", Q_irand( 1, 3 ) )) );
|
|
|
|
gripEnt->client->ps.forceHandExtend = HANDEXTEND_CHOKE;
|
|
gripEnt->client->ps.forceHandExtendTime = level.time + 2000;
|
|
|
|
if (gripEnt->client->ps.fd.forcePowersActive & (1 << FP_GRIP))
|
|
{ //choking, so don't let him keep gripping himself
|
|
WP_ForcePowerStop(gripEnt, FP_GRIP);
|
|
}
|
|
}
|
|
else if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 4000)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (gripLevel == FORCE_LEVEL_3)
|
|
{
|
|
gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000;
|
|
|
|
gripEnt->client->ps.otherKiller = self->s.number;
|
|
gripEnt->client->ps.otherKillerTime = level.time + 5000;
|
|
gripEnt->client->ps.otherKillerDebounceTime = level.time + 100;
|
|
gripEnt->client->otherKillerMOD = MOD_UNKNOWN;
|
|
gripEnt->client->otherKillerVehWeapon = 0;
|
|
gripEnt->client->otherKillerWeaponType = WP_NONE;
|
|
|
|
gripEnt->client->ps.forceGripChangeMovetype = PM_FLOAT;
|
|
|
|
if (gripEnt->client->ps.forceGripMoveInterval < level.time)
|
|
{
|
|
float nvLen = 0;
|
|
|
|
VectorCopy(gripEnt->client->ps.origin, start_o);
|
|
AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL);
|
|
fwd_o[0] = self->client->ps.origin[0] + fwd[0]*128;
|
|
fwd_o[1] = self->client->ps.origin[1] + fwd[1]*128;
|
|
fwd_o[2] = self->client->ps.origin[2] + fwd[2]*128;
|
|
fwd_o[2] += 16;
|
|
VectorSubtract(fwd_o, start_o, nvel);
|
|
|
|
nvLen = VectorLength(nvel);
|
|
|
|
if (nvLen < 16)
|
|
{ //within x units of desired spot
|
|
VectorNormalize(nvel);
|
|
gripEnt->client->ps.velocity[0] = nvel[0]*8;
|
|
gripEnt->client->ps.velocity[1] = nvel[1]*8;
|
|
gripEnt->client->ps.velocity[2] = nvel[2]*8;
|
|
}
|
|
else if (nvLen < 64)
|
|
{
|
|
VectorNormalize(nvel);
|
|
gripEnt->client->ps.velocity[0] = nvel[0]*128;
|
|
gripEnt->client->ps.velocity[1] = nvel[1]*128;
|
|
gripEnt->client->ps.velocity[2] = nvel[2]*128;
|
|
}
|
|
else if (nvLen < 128)
|
|
{
|
|
VectorNormalize(nvel);
|
|
gripEnt->client->ps.velocity[0] = nvel[0]*256;
|
|
gripEnt->client->ps.velocity[1] = nvel[1]*256;
|
|
gripEnt->client->ps.velocity[2] = nvel[2]*256;
|
|
}
|
|
else if (nvLen < 200)
|
|
{
|
|
VectorNormalize(nvel);
|
|
gripEnt->client->ps.velocity[0] = nvel[0]*512;
|
|
gripEnt->client->ps.velocity[1] = nvel[1]*512;
|
|
gripEnt->client->ps.velocity[2] = nvel[2]*512;
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize(nvel);
|
|
gripEnt->client->ps.velocity[0] = nvel[0]*700;
|
|
gripEnt->client->ps.velocity[1] = nvel[1]*700;
|
|
gripEnt->client->ps.velocity[2] = nvel[2]*700;
|
|
}
|
|
|
|
gripEnt->client->ps.forceGripMoveInterval = level.time + 300; //only update velocity every 300ms, so as to avoid heavy bandwidth usage
|
|
}
|
|
|
|
if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 3000 && !self->client->ps.fd.forceGripDamageDebounceTime)
|
|
{ //if we managed to lift him into the air for 2 seconds, give him a crack
|
|
self->client->ps.fd.forceGripDamageDebounceTime = 1;
|
|
G_Damage(gripEnt, self, self, NULL, NULL, 40, DAMAGE_NO_ARMOR, MOD_FORCE_DARK);
|
|
|
|
//Must play custom sounds on the actual entity. Don't use G_Sound (it creates a temp entity for the sound)
|
|
G_EntitySound( gripEnt, CHAN_VOICE, G_SoundIndex(va( "*choke%d.wav", Q_irand( 1, 3 ) )) );
|
|
|
|
gripEnt->client->ps.forceHandExtend = HANDEXTEND_CHOKE;
|
|
gripEnt->client->ps.forceHandExtendTime = level.time + 2000;
|
|
|
|
if (gripEnt->client->ps.fd.forcePowersActive & (1 << FP_GRIP))
|
|
{ //choking, so don't let him keep gripping himself
|
|
WP_ForcePowerStop(gripEnt, FP_GRIP);
|
|
}
|
|
}
|
|
else if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 4000)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
qboolean G_IsMindTricked(forcedata_t *fd, int client)
|
|
{
|
|
int checkIn;
|
|
int trickIndex1, trickIndex2, trickIndex3, trickIndex4;
|
|
int sub = 0;
|
|
|
|
if (!fd)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
trickIndex1 = fd->forceMindtrickTargetIndex;
|
|
trickIndex2 = fd->forceMindtrickTargetIndex2;
|
|
trickIndex3 = fd->forceMindtrickTargetIndex3;
|
|
trickIndex4 = fd->forceMindtrickTargetIndex4;
|
|
|
|
if (client > 47)
|
|
{
|
|
checkIn = trickIndex4;
|
|
sub = 48;
|
|
}
|
|
else if (client > 31)
|
|
{
|
|
checkIn = trickIndex3;
|
|
sub = 32;
|
|
}
|
|
else if (client > 15)
|
|
{
|
|
checkIn = trickIndex2;
|
|
sub = 16;
|
|
}
|
|
else
|
|
{
|
|
checkIn = trickIndex1;
|
|
}
|
|
|
|
if (checkIn & (1 << (client-sub)))
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
static void RemoveTrickedEnt(forcedata_t *fd, int client)
|
|
{
|
|
if (!fd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (client > 47)
|
|
{
|
|
fd->forceMindtrickTargetIndex4 &= ~(1 << (client-48));
|
|
}
|
|
else if (client > 31)
|
|
{
|
|
fd->forceMindtrickTargetIndex3 &= ~(1 << (client-32));
|
|
}
|
|
else if (client > 15)
|
|
{
|
|
fd->forceMindtrickTargetIndex2 &= ~(1 << (client-16));
|
|
}
|
|
else
|
|
{
|
|
fd->forceMindtrickTargetIndex &= ~(1 << client);
|
|
}
|
|
}
|
|
|
|
extern int g_LastFrameTime;
|
|
extern int g_TimeSinceLastFrame;
|
|
|
|
static void WP_UpdateMindtrickEnts(gentity_t *self)
|
|
{
|
|
int i = 0;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
if (G_IsMindTricked(&self->client->ps.fd, i))
|
|
{
|
|
gentity_t *ent = &g_entities[i];
|
|
|
|
if ( !ent || !ent->client || !ent->inuse || ent->health < 1 ||
|
|
(ent->client->ps.fd.forcePowersActive & (1 << FP_SEE)) )
|
|
{
|
|
RemoveTrickedEnt(&self->client->ps.fd, i);
|
|
}
|
|
else if ((level.time - self->client->dangerTime) < g_TimeSinceLastFrame*4)
|
|
{ //Untrick this entity if the tricker (self) fires while in his fov
|
|
if (trap_InPVS(ent->client->ps.origin, self->client->ps.origin) &&
|
|
OrgVisible(ent->client->ps.origin, self->client->ps.origin, ent->s.number))
|
|
{
|
|
RemoveTrickedEnt(&self->client->ps.fd, i);
|
|
}
|
|
}
|
|
else if (BG_HasYsalamiri(g_gametype.integer, &ent->client->ps))
|
|
{
|
|
RemoveTrickedEnt(&self->client->ps.fd, i);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!self->client->ps.fd.forceMindtrickTargetIndex &&
|
|
!self->client->ps.fd.forceMindtrickTargetIndex2 &&
|
|
!self->client->ps.fd.forceMindtrickTargetIndex3 &&
|
|
!self->client->ps.fd.forceMindtrickTargetIndex4)
|
|
{ //everyone who we had tricked is no longer tricked, so stop the power
|
|
WP_ForcePowerStop(self, FP_TELEPATHY);
|
|
}
|
|
else if (self->client->ps.powerups[PW_REDFLAG] ||
|
|
self->client->ps.powerups[PW_BLUEFLAG])
|
|
{
|
|
WP_ForcePowerStop(self, FP_TELEPATHY);
|
|
}
|
|
}
|
|
|
|
static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
|
|
{
|
|
extern usercmd_t ucmd;
|
|
|
|
switch( (int)forcePower )
|
|
{
|
|
case FP_HEAL:
|
|
if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_1)
|
|
{
|
|
if (self->client->ps.velocity[0] || self->client->ps.velocity[1] || self->client->ps.velocity[2])
|
|
{
|
|
WP_ForcePowerStop( self, forcePower );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (self->health < 1 || self->client->ps.stats[STAT_HEALTH] < 1)
|
|
{
|
|
WP_ForcePowerStop( self, forcePower );
|
|
break;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceHealTime > level.time)
|
|
{
|
|
break;
|
|
}
|
|
if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH])
|
|
{ //rww - we might start out over max_health and we don't want force heal taking us down to 100 or whatever max_health is
|
|
WP_ForcePowerStop( self, forcePower );
|
|
break;
|
|
}
|
|
self->client->ps.fd.forceHealTime = level.time + 1000;
|
|
self->health++;
|
|
self->client->ps.fd.forceHealAmount++;
|
|
|
|
if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH]) // Past max health
|
|
{
|
|
self->health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
|
|
if ( (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_1 && self->client->ps.fd.forceHealAmount >= 25) ||
|
|
(self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_2 && self->client->ps.fd.forceHealAmount >= 33))
|
|
{
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
break;
|
|
case FP_SPEED:
|
|
//This is handled in PM_WalkMove and PM_StepSlideMove
|
|
if ( self->client->holdingObjectiveItem >= MAX_CLIENTS
|
|
&& self->client->holdingObjectiveItem < ENTITYNUM_WORLD )
|
|
{
|
|
if ( g_entities[self->client->holdingObjectiveItem].genericValue15 )
|
|
{//disables force powers
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
}
|
|
/*
|
|
if ( self->client->ps.powerups[PW_REDFLAG]
|
|
|| self->client->ps.powerups[PW_BLUEFLAG]
|
|
|| self->client->ps.powerups[PW_NEUTRALFLAG] )
|
|
{//no force speed when carrying flag
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
*/
|
|
break;
|
|
case FP_GRIP:
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD)
|
|
{
|
|
WP_ForcePowerStop(self, FP_GRIP);
|
|
break;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerDebounce[FP_PULL] < level.time)
|
|
{ //This is sort of not ideal. Using the debounce value reserved for pull for this because pull doesn't need it.
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, 1 );
|
|
self->client->ps.fd.forcePowerDebounce[FP_PULL] = level.time + 100;
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePower < 1)
|
|
{
|
|
WP_ForcePowerStop(self, FP_GRIP);
|
|
break;
|
|
}
|
|
|
|
DoGripAction(self, forcePower);
|
|
break;
|
|
case FP_LEVITATION:
|
|
if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.fd.forceJumpZStart )
|
|
{//done with jump
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
break;
|
|
case FP_RAGE:
|
|
if (self->health < 1)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
break;
|
|
}
|
|
if (self->client->ps.forceRageDrainTime < level.time)
|
|
{
|
|
int addTime = 400;
|
|
|
|
self->health -= 2;
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1)
|
|
{
|
|
addTime = 150;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2)
|
|
{
|
|
addTime = 300;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3)
|
|
{
|
|
addTime = 450;
|
|
}
|
|
self->client->ps.forceRageDrainTime = level.time + addTime;
|
|
}
|
|
|
|
if (self->health < 1)
|
|
{
|
|
self->health = 1;
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
|
|
self->client->ps.stats[STAT_HEALTH] = self->health;
|
|
break;
|
|
case FP_DRAIN:
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
break;
|
|
}
|
|
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
|
|
{//higher than level 1
|
|
if ( (cmd->buttons & BUTTON_FORCE_DRAIN) || ((cmd->buttons & BUTTON_FORCEPOWER) && self->client->ps.fd.forcePowerSelected == FP_DRAIN) )
|
|
{//holding it keeps it going
|
|
self->client->ps.fd.forcePowerDuration[FP_DRAIN] = level.time + 500;
|
|
}
|
|
}
|
|
// OVERRIDEFIXME
|
|
if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) || self->client->ps.fd.forcePowerDuration[FP_DRAIN] < level.time ||
|
|
self->client->ps.fd.forcePower < 25)
|
|
{
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
else
|
|
{
|
|
ForceShootDrain( self );
|
|
}
|
|
break;
|
|
case FP_LIGHTNING:
|
|
if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD)
|
|
{ //Animation for hand extend doesn't end with hand out, so we have to limit lightning intervals by animation intervals (once hand starts to go in in animation, lightning should stop)
|
|
WP_ForcePowerStop(self, forcePower);
|
|
break;
|
|
}
|
|
|
|
if ( self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
|
|
{//higher than level 1
|
|
if ( (cmd->buttons & BUTTON_FORCE_LIGHTNING) || ((cmd->buttons & BUTTON_FORCEPOWER) && self->client->ps.fd.forcePowerSelected == FP_LIGHTNING) )
|
|
{//holding it keeps it going
|
|
self->client->ps.fd.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
|
|
}
|
|
}
|
|
// OVERRIDEFIXME
|
|
if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) || self->client->ps.fd.forcePowerDuration[FP_LIGHTNING] < level.time ||
|
|
self->client->ps.fd.forcePower < 25)
|
|
{
|
|
WP_ForcePowerStop( self, forcePower );
|
|
}
|
|
else
|
|
{
|
|
ForceShootLightning( self );
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, 0 );
|
|
}
|
|
break;
|
|
case FP_TELEPATHY:
|
|
if ( self->client->holdingObjectiveItem >= MAX_CLIENTS
|
|
&& self->client->holdingObjectiveItem < ENTITYNUM_WORLD
|
|
&& g_entities[self->client->holdingObjectiveItem].genericValue15 )
|
|
{ //if force hindered can't mindtrick whilst carrying a siege item
|
|
WP_ForcePowerStop( self, FP_TELEPATHY );
|
|
}
|
|
else
|
|
{
|
|
WP_UpdateMindtrickEnts(self);
|
|
}
|
|
break;
|
|
case FP_SABER_OFFENSE:
|
|
break;
|
|
case FP_SABER_DEFENSE:
|
|
break;
|
|
case FP_SABERTHROW:
|
|
break;
|
|
case FP_PROTECT:
|
|
if (self->client->ps.fd.forcePowerDebounce[forcePower] < level.time)
|
|
{
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, 1 );
|
|
if (self->client->ps.fd.forcePower < 1)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
|
|
self->client->ps.fd.forcePowerDebounce[forcePower] = level.time + 300;
|
|
}
|
|
break;
|
|
case FP_ABSORB:
|
|
if (self->client->ps.fd.forcePowerDebounce[forcePower] < level.time)
|
|
{
|
|
BG_ForcePowerDrain( &self->client->ps, forcePower, 1 );
|
|
if (self->client->ps.fd.forcePower < 1)
|
|
{
|
|
WP_ForcePowerStop(self, forcePower);
|
|
}
|
|
|
|
self->client->ps.fd.forcePowerDebounce[forcePower] = level.time + 600;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int WP_DoSpecificPower( gentity_t *self, usercmd_t *ucmd, forcePowers_t forcepower)
|
|
{
|
|
int powerSucceeded;
|
|
|
|
powerSucceeded = 1;
|
|
|
|
// OVERRIDEFIXME
|
|
if ( !WP_ForcePowerAvailable( self, forcepower, 0 ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
switch(forcepower)
|
|
{
|
|
case FP_HEAL:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceHeal(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_LEVITATION:
|
|
//if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
|
|
|
|
if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
self->client->ps.fd.forceJumpCharge = 0;
|
|
G_MuteSound( self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE );
|
|
//This only happens if the groundEntityNum == ENTITYNUM_NONE when the button is actually released
|
|
}
|
|
else
|
|
{//still on ground, so jump
|
|
ForceJump( self, ucmd );
|
|
}
|
|
break;
|
|
case FP_SPEED:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceSpeed(self, 0);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_GRIP:
|
|
if (self->client->ps.fd.forceGripEntityNum == ENTITYNUM_NONE)
|
|
{
|
|
ForceGrip( self );
|
|
}
|
|
|
|
if (self->client->ps.fd.forceGripEntityNum != ENTITYNUM_NONE)
|
|
{
|
|
if (!(self->client->ps.fd.forcePowersActive & (1 << FP_GRIP)))
|
|
{
|
|
WP_ForcePowerStart( self, FP_GRIP, 0 );
|
|
BG_ForcePowerDrain( &self->client->ps, FP_GRIP, GRIP_DRAIN_AMOUNT );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
powerSucceeded = 0;
|
|
}
|
|
break;
|
|
case FP_LIGHTNING:
|
|
ForceLightning(self);
|
|
break;
|
|
case FP_PUSH:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease && !(self->r.svFlags & SVF_BOT))
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceThrow(self, qfalse);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_PULL:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceThrow(self, qtrue);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_TELEPATHY:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceTelepathy(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_RAGE:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceRage(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_PROTECT:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceProtect(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_ABSORB:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceAbsorb(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_TEAM_HEAL:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceTeamHeal(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_TEAM_FORCE:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceTeamForceReplenish(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_DRAIN:
|
|
ForceDrain(self);
|
|
break;
|
|
case FP_SEE:
|
|
powerSucceeded = 0; //always 0 for nonhold powers
|
|
if (self->client->ps.fd.forceButtonNeedRelease)
|
|
{ //need to release before we can use nonhold powers again
|
|
break;
|
|
}
|
|
ForceSeeing(self);
|
|
self->client->ps.fd.forceButtonNeedRelease = 1;
|
|
break;
|
|
case FP_SABER_OFFENSE:
|
|
break;
|
|
case FP_SABER_DEFENSE:
|
|
break;
|
|
case FP_SABERTHROW:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return powerSucceeded;
|
|
}
|
|
|
|
void FindGenericEnemyIndex(gentity_t *self)
|
|
{ //Find another client that would be considered a threat.
|
|
int i = 0;
|
|
float tlen;
|
|
gentity_t *ent;
|
|
gentity_t *besten = NULL;
|
|
float blen = 99999999;
|
|
vec3_t a;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->client && ent->s.number != self->s.number && ent->health > 0 && !OnSameTeam(self, ent) && ent->client->ps.pm_type != PM_INTERMISSION && ent->client->ps.pm_type != PM_SPECTATOR)
|
|
{
|
|
VectorSubtract(ent->client->ps.origin, self->client->ps.origin, a);
|
|
tlen = VectorLength(a);
|
|
|
|
if (tlen < blen &&
|
|
InFront(ent->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.8f ) &&
|
|
OrgVisible(self->client->ps.origin, ent->client->ps.origin, self->s.number))
|
|
{
|
|
blen = tlen;
|
|
besten = ent;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!besten)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->client->ps.genericEnemyIndex = besten->s.number;
|
|
}
|
|
|
|
void SeekerDroneUpdate(gentity_t *self)
|
|
{
|
|
vec3_t org, elevated, dir, a, endir;
|
|
gentity_t *en;
|
|
float angle;
|
|
float prefig = 0;
|
|
trace_t tr;
|
|
|
|
if (!(self->client->ps.eFlags & EF_SEEKERDRONE))
|
|
{
|
|
self->client->ps.genericEnemyIndex = -1;
|
|
return;
|
|
}
|
|
|
|
if (self->health < 1)
|
|
{
|
|
VectorCopy(self->client->ps.origin, elevated);
|
|
elevated[2] += 40;
|
|
|
|
angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen
|
|
dir[0] = cos(angle) * 20;
|
|
dir[1] = sin(angle) * 20;
|
|
dir[2] = cos(angle) * 5;
|
|
VectorAdd(elevated, dir, org);
|
|
|
|
a[ROLL] = 0;
|
|
a[YAW] = 0;
|
|
a[PITCH] = 1;
|
|
|
|
G_PlayEffect(EFFECT_SPARK_EXPLOSION, org, a);
|
|
|
|
self->client->ps.eFlags -= EF_SEEKERDRONE;
|
|
self->client->ps.genericEnemyIndex = -1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.droneExistTime >= level.time &&
|
|
self->client->ps.droneExistTime < (level.time+5000))
|
|
{
|
|
self->client->ps.genericEnemyIndex = 1024+self->client->ps.droneExistTime;
|
|
if (self->client->ps.droneFireTime < level.time)
|
|
{
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/laser_trap/warning.wav") );
|
|
self->client->ps.droneFireTime = level.time + 100;
|
|
}
|
|
return;
|
|
}
|
|
else if (self->client->ps.droneExistTime < level.time)
|
|
{
|
|
VectorCopy(self->client->ps.origin, elevated);
|
|
elevated[2] += 40;
|
|
|
|
prefig = (self->client->ps.droneExistTime-level.time)/80;
|
|
|
|
if (prefig > 55)
|
|
{
|
|
prefig = 55;
|
|
}
|
|
else if (prefig < 1)
|
|
{
|
|
prefig = 1;
|
|
}
|
|
|
|
elevated[2] -= 55-prefig;
|
|
|
|
angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen
|
|
dir[0] = cos(angle) * 20;
|
|
dir[1] = sin(angle) * 20;
|
|
dir[2] = cos(angle) * 5;
|
|
VectorAdd(elevated, dir, org);
|
|
|
|
a[ROLL] = 0;
|
|
a[YAW] = 0;
|
|
a[PITCH] = 1;
|
|
|
|
G_PlayEffect(EFFECT_SPARK_EXPLOSION, org, a);
|
|
|
|
self->client->ps.eFlags -= EF_SEEKERDRONE;
|
|
self->client->ps.genericEnemyIndex = -1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.genericEnemyIndex == -1)
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
|
|
if (self->client->ps.genericEnemyIndex != ENTITYNUM_NONE && self->client->ps.genericEnemyIndex != -1)
|
|
{
|
|
en = &g_entities[self->client->ps.genericEnemyIndex];
|
|
|
|
if (!en || !en->client)
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
else if (en->s.number == self->s.number)
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
else if (en->health < 1)
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
else if (OnSameTeam(self, en))
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
else
|
|
{
|
|
if (!InFront(en->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.8f ))
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
else if (!OrgVisible(self->client->ps.origin, en->client->ps.origin, self->s.number))
|
|
{
|
|
self->client->ps.genericEnemyIndex = ENTITYNUM_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->client->ps.genericEnemyIndex == ENTITYNUM_NONE || self->client->ps.genericEnemyIndex == -1)
|
|
{
|
|
FindGenericEnemyIndex(self);
|
|
}
|
|
|
|
if (self->client->ps.genericEnemyIndex != ENTITYNUM_NONE && self->client->ps.genericEnemyIndex != -1)
|
|
{
|
|
en = &g_entities[self->client->ps.genericEnemyIndex];
|
|
|
|
VectorCopy(self->client->ps.origin, elevated);
|
|
elevated[2] += 40;
|
|
|
|
angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen
|
|
dir[0] = cos(angle) * 20;
|
|
dir[1] = sin(angle) * 20;
|
|
dir[2] = cos(angle) * 5;
|
|
VectorAdd(elevated, dir, org);
|
|
|
|
//org is now where the thing should be client-side because it uses the same time-based offset
|
|
if (self->client->ps.droneFireTime < level.time)
|
|
{
|
|
trap_Trace(&tr, org, NULL, NULL, en->client->ps.origin, -1, MASK_SOLID);
|
|
|
|
if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
|
|
{
|
|
VectorSubtract(en->client->ps.origin, org, endir);
|
|
VectorNormalize(endir);
|
|
|
|
WP_FireGenericBlasterMissile(self, org, endir, 0, 15, 2000, MOD_BLASTER);
|
|
G_SoundAtLoc( org, CHAN_WEAPON, G_SoundIndex("sound/weapons/bryar/fire.wav") );
|
|
|
|
self->client->ps.droneFireTime = level.time + Q_irand(400, 700);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HolocronUpdate(gentity_t *self)
|
|
{ //keep holocron status updated in holocron mode
|
|
int i = 0;
|
|
int noHRank = 0;
|
|
|
|
if (noHRank < FORCE_LEVEL_0)
|
|
{
|
|
noHRank = FORCE_LEVEL_0;
|
|
}
|
|
if (noHRank > FORCE_LEVEL_3)
|
|
{
|
|
noHRank = FORCE_LEVEL_3;
|
|
}
|
|
|
|
trap_Cvar_Update(&g_MaxHolocronCarry);
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (self->client->ps.holocronsCarried[i])
|
|
{ //carrying it, make sure we have the power
|
|
self->client->ps.holocronBits |= (1 << i);
|
|
self->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3;
|
|
}
|
|
else
|
|
{ //otherwise, make sure the power is cleared from us
|
|
self->client->ps.fd.forcePowerLevel[i] = 0;
|
|
if (self->client->ps.holocronBits & (1 << i))
|
|
{
|
|
self->client->ps.holocronBits -= (1 << i);
|
|
}
|
|
|
|
if ((self->client->ps.fd.forcePowersKnown & (1 << i)) && i != FP_LEVITATION && i != FP_SABER_OFFENSE)
|
|
{
|
|
self->client->ps.fd.forcePowersKnown -= (1 << i);
|
|
}
|
|
|
|
if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION && i != FP_SABER_OFFENSE)
|
|
{
|
|
WP_ForcePowerStop(self, i);
|
|
}
|
|
|
|
if (i == FP_LEVITATION)
|
|
{
|
|
if (noHRank >= FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = noHRank;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1;
|
|
}
|
|
}
|
|
else if (i == FP_SABER_OFFENSE)
|
|
{
|
|
self->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
|
|
if (noHRank >= FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = noHRank;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (HasSetSaberOnly())
|
|
{ //if saberonly, we get these powers no matter what (still need the holocrons for level 3)
|
|
if (self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
|
|
}
|
|
if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void JediMasterUpdate(gentity_t *self)
|
|
{ //keep jedi master status updated for JM gametype
|
|
int i = 0;
|
|
|
|
trap_Cvar_Update(&g_MaxHolocronCarry);
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (self->client->ps.isJediMaster)
|
|
{
|
|
self->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3;
|
|
|
|
if (i == FP_TEAM_HEAL || i == FP_TEAM_FORCE ||
|
|
i == FP_DRAIN || i == FP_ABSORB)
|
|
{ //team powers are useless in JM, absorb is too because no one else has powers to absorb. Drain is just
|
|
//relatively useless in comparison, because its main intent is not to heal, but rather to cripple others
|
|
//by draining their force at the same time. And no one needs force in JM except the JM himself.
|
|
self->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
self->client->ps.fd.forcePowerLevel[i] = 0;
|
|
}
|
|
|
|
if (i == FP_TELEPATHY)
|
|
{ //this decision was made because level 3 mindtrick allows the JM to just hide too much, and no one else has force
|
|
//sight to counteract it. Since the JM himself is the focus of gameplay in this mode, having him hidden for large
|
|
//durations is indeed a bad thing.
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((self->client->ps.fd.forcePowersKnown & (1 << i)) && i != FP_LEVITATION)
|
|
{
|
|
self->client->ps.fd.forcePowersKnown -= (1 << i);
|
|
}
|
|
|
|
if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION)
|
|
{
|
|
WP_ForcePowerStop(self, i);
|
|
}
|
|
|
|
if (i == FP_LEVITATION)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
qboolean WP_HasForcePowers( const playerState_t *ps )
|
|
{
|
|
int i;
|
|
if ( ps )
|
|
{
|
|
for ( i = 0; i < NUM_FORCE_POWERS; i++ )
|
|
{
|
|
if ( i == FP_LEVITATION )
|
|
{
|
|
if ( ps->fd.forcePowerLevel[i] > FORCE_LEVEL_1 )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
else if ( ps->fd.forcePowerLevel[i] > FORCE_LEVEL_0 )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
//try a special roll getup move
|
|
qboolean G_SpecialRollGetup(gentity_t *self)
|
|
{ //fixme: currently no knockdown will actually land you on your front... so froll's are pretty useless at the moment.
|
|
qboolean rolled = qfalse;
|
|
|
|
/*
|
|
if (self->client->ps.weapon != WP_SABER &&
|
|
self->client->ps.weapon != WP_MELEE)
|
|
{ //can't do acrobatics without saber selected
|
|
return qfalse;
|
|
}
|
|
*/
|
|
|
|
if (/*!self->client->pers.cmd.upmove &&*/
|
|
self->client->pers.cmd.rightmove > 0 &&
|
|
!self->client->pers.cmd.forwardmove)
|
|
{
|
|
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_R, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0);
|
|
rolled = qtrue;
|
|
}
|
|
else if (/*!self->client->pers.cmd.upmove &&*/
|
|
self->client->pers.cmd.rightmove < 0 &&
|
|
!self->client->pers.cmd.forwardmove)
|
|
{
|
|
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_L, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0);
|
|
rolled = qtrue;
|
|
}
|
|
else if (/*self->client->pers.cmd.upmove > 0 &&*/
|
|
!self->client->pers.cmd.rightmove &&
|
|
self->client->pers.cmd.forwardmove > 0)
|
|
{
|
|
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_F, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0);
|
|
rolled = qtrue;
|
|
}
|
|
else if (/*self->client->pers.cmd.upmove > 0 &&*/
|
|
!self->client->pers.cmd.rightmove &&
|
|
self->client->pers.cmd.forwardmove < 0)
|
|
{
|
|
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_B, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0);
|
|
rolled = qtrue;
|
|
}
|
|
else if (self->client->pers.cmd.upmove)
|
|
{
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP);
|
|
self->client->ps.forceDodgeAnim = 2;
|
|
self->client->ps.forceHandExtendTime = level.time + 500;
|
|
|
|
//self->client->ps.velocity[2] = 300;
|
|
}
|
|
|
|
if (rolled)
|
|
{
|
|
G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") );
|
|
}
|
|
|
|
return rolled;
|
|
}
|
|
|
|
void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
|
|
{
|
|
qboolean usingForce = qfalse;
|
|
int i, holo, holoregen;
|
|
int prepower = 0;
|
|
//see if any force powers are running
|
|
if ( !self )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !self->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->client->ps.pm_flags & PMF_FOLLOW)
|
|
{ //not a "real" game client, it's a spectator following someone
|
|
return;
|
|
}
|
|
if (self->client->sess.sessionTeam == TEAM_SPECTATOR)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if (self->client->ps.fd.saberAnimLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
|
|
{
|
|
self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
|
|
}
|
|
else if (!self->client->ps.fd.saberAnimLevel)
|
|
{
|
|
self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
|
|
}
|
|
*/
|
|
//The stance in relation to power level is no longer applicable with the crazy new akimbo/staff stances.
|
|
if (!self->client->ps.fd.saberAnimLevel)
|
|
{
|
|
self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;
|
|
}
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{
|
|
if (!(self->client->ps.fd.forcePowersKnown & (1 << FP_LEVITATION)))
|
|
{
|
|
self->client->ps.fd.forcePowersKnown |= (1 << FP_LEVITATION);
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_1)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
|
|
}
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowerSelected < 0)
|
|
{ //bad
|
|
self->client->ps.fd.forcePowerSelected = 0;
|
|
}
|
|
|
|
if ( ((self->client->sess.selectedFP != self->client->ps.fd.forcePowerSelected) ||
|
|
(self->client->sess.saberLevel != self->client->ps.fd.saberAnimLevel)) &&
|
|
!(self->r.svFlags & SVF_BOT) )
|
|
{
|
|
if (self->client->sess.updateUITime < level.time)
|
|
{ //a bit hackish, but we don't want the client to flood with userinfo updates if they rapidly cycle
|
|
//through their force powers or saber attack levels
|
|
|
|
self->client->sess.selectedFP = self->client->ps.fd.forcePowerSelected;
|
|
self->client->sess.saberLevel = self->client->ps.fd.saberAnimLevel;
|
|
}
|
|
}
|
|
|
|
if (!g_LastFrameTime)
|
|
{
|
|
g_LastFrameTime = level.time;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN)
|
|
{
|
|
self->client->ps.zoomFov = 0;
|
|
self->client->ps.zoomMode = 0;
|
|
self->client->ps.zoomLocked = qfalse;
|
|
self->client->ps.zoomTime = 0;
|
|
}
|
|
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN &&
|
|
self->client->ps.forceHandExtendTime >= level.time)
|
|
{
|
|
self->client->ps.saberMove = 0;
|
|
self->client->ps.saberBlocking = 0;
|
|
self->client->ps.saberBlocked = 0;
|
|
self->client->ps.weaponTime = 0;
|
|
self->client->ps.weaponstate = WEAPON_READY;
|
|
}
|
|
else if (self->client->ps.forceHandExtend != HANDEXTEND_NONE &&
|
|
self->client->ps.forceHandExtendTime < level.time)
|
|
{
|
|
if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN &&
|
|
!self->client->ps.forceDodgeAnim)
|
|
{
|
|
if (self->health < 1 || (self->client->ps.eFlags & EF_DEAD))
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_NONE;
|
|
}
|
|
else if (G_SpecialRollGetup(self))
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_NONE;
|
|
}
|
|
else
|
|
{ //hmm.. ok.. no more getting up on your own, you've gotta push something, unless..
|
|
if ((level.time-self->client->ps.forceHandExtendTime) > 4000)
|
|
{ //4 seconds elapsed, I guess they're too dumb to push something to get up!
|
|
if (self->client->pers.cmd.upmove &&
|
|
self->client->ps.fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1)
|
|
{ //force getup
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP);
|
|
self->client->ps.forceDodgeAnim = 2;
|
|
self->client->ps.forceHandExtendTime = level.time + 500;
|
|
|
|
//self->client->ps.velocity[2] = 400;
|
|
}
|
|
else if (self->client->ps.quickerGetup)
|
|
{
|
|
G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") );
|
|
self->client->ps.forceDodgeAnim = 3;
|
|
self->client->ps.forceHandExtendTime = level.time + 500;
|
|
self->client->ps.velocity[2] = 300;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.forceDodgeAnim = 1;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
}
|
|
}
|
|
}
|
|
self->client->ps.quickerGetup = qfalse;
|
|
}
|
|
else if (self->client->ps.forceHandExtend == HANDEXTEND_POSTTHROWN)
|
|
{
|
|
if (self->health < 1 || (self->client->ps.eFlags & EF_DEAD))
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_NONE;
|
|
}
|
|
else if (self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceDodgeAnim)
|
|
{
|
|
self->client->ps.forceDodgeAnim = 1;
|
|
self->client->ps.forceHandExtendTime = level.time + 1000;
|
|
G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") );
|
|
self->client->ps.velocity[2] = 100;
|
|
}
|
|
else if (!self->client->ps.forceDodgeAnim)
|
|
{
|
|
self->client->ps.forceHandExtendTime = level.time + 100;
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY;
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_HOLOCRON)
|
|
{
|
|
HolocronUpdate(self);
|
|
}
|
|
if (g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
JediMasterUpdate(self);
|
|
}
|
|
|
|
SeekerDroneUpdate(self);
|
|
|
|
if (self->client->ps.powerups[PW_FORCE_BOON])
|
|
{
|
|
prepower = self->client->ps.fd.forcePower;
|
|
}
|
|
|
|
if (self && self->client && (BG_HasYsalamiri(g_gametype.integer, &self->client->ps) ||
|
|
self->client->ps.fd.forceDeactivateAll || self->client->tempSpectate >= level.time))
|
|
{ //has ysalamiri.. or we want to forcefully stop all his active powers
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION)
|
|
{
|
|
WP_ForcePowerStop(self, i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (self->client->tempSpectate >= level.time)
|
|
{
|
|
self->client->ps.fd.forcePower = 100;
|
|
self->client->ps.fd.forceRageRecoveryTime = 0;
|
|
}
|
|
|
|
self->client->ps.fd.forceDeactivateAll = 0;
|
|
|
|
if (self->client->ps.fd.forceJumpCharge)
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
self->client->ps.fd.forceJumpCharge = 0;
|
|
}
|
|
}
|
|
else
|
|
{ //otherwise just do a check through them all to see if they need to be stopped for any reason.
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION &&
|
|
!BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, i))
|
|
{
|
|
WP_ForcePowerStop(self, i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
|
|
if (self->client->ps.powerups[PW_FORCE_ENLIGHTENED_LIGHT] || self->client->ps.powerups[PW_FORCE_ENLIGHTENED_DARK])
|
|
{ //enlightenment
|
|
if (!self->client->ps.fd.forceUsingAdded)
|
|
{
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
self->client->ps.fd.forcePowerBaseLevel[i] = self->client->ps.fd.forcePowerLevel[i];
|
|
|
|
if (!forcePowerDarkLight[i] ||
|
|
self->client->ps.fd.forceSide == forcePowerDarkLight[i])
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3;
|
|
self->client->ps.fd.forcePowersKnown |= (1 << i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
self->client->ps.fd.forceUsingAdded = 1;
|
|
}
|
|
}
|
|
else if (self->client->ps.fd.forceUsingAdded)
|
|
{ //we don't have enlightenment but we're still using enlightened powers, so clear them back to how they should be.
|
|
i = 0;
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
self->client->ps.fd.forcePowerLevel[i] = self->client->ps.fd.forcePowerBaseLevel[i];
|
|
if (!self->client->ps.fd.forcePowerLevel[i])
|
|
{
|
|
if (self->client->ps.fd.forcePowersActive & (1 << i))
|
|
{
|
|
WP_ForcePowerStop(self, i);
|
|
}
|
|
self->client->ps.fd.forcePowersKnown &= ~(1 << i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
self->client->ps.fd.forceUsingAdded = 0;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
if (!(self->client->ps.fd.forcePowersActive & (1 << FP_TELEPATHY)))
|
|
{ //clear the mindtrick index values
|
|
self->client->ps.fd.forceMindtrickTargetIndex = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex2 = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex3 = 0;
|
|
self->client->ps.fd.forceMindtrickTargetIndex4 = 0;
|
|
}
|
|
|
|
if (self->health < 1)
|
|
{
|
|
self->client->ps.fd.forceGripBeingGripped = 0;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceGripBeingGripped > level.time)
|
|
{
|
|
self->client->ps.fd.forceGripCripple = 1;
|
|
|
|
//keep the saber off during this period
|
|
if (self->client->ps.weapon == WP_SABER && !self->client->ps.saberHolstered)
|
|
{
|
|
Cmd_ToggleSaber_f(self);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forceGripCripple = 0;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceJumpSound)
|
|
{
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP);
|
|
self->client->ps.fd.forceJumpSound = 0;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceGripCripple)
|
|
{
|
|
if (self->client->ps.fd.forceGripSoundTime < level.time)
|
|
{
|
|
G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEGRIP);
|
|
self->client->ps.fd.forceGripSoundTime = level.time + 1000;
|
|
}
|
|
}
|
|
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_SPEED))
|
|
{
|
|
self->client->ps.powerups[PW_SPEED] = level.time + 100;
|
|
}
|
|
|
|
if ( self->health <= 0 )
|
|
{//if dead, deactivate any active force powers
|
|
for ( i = 0; i < NUM_FORCE_POWERS; i++ )
|
|
{
|
|
if ( self->client->ps.fd.forcePowerDuration[i] || (self->client->ps.fd.forcePowersActive&( 1 << i )) )
|
|
{
|
|
WP_ForcePowerStop( self, (forcePowers_t)i );
|
|
self->client->ps.fd.forcePowerDuration[i] = 0;
|
|
}
|
|
}
|
|
goto powersetcheck;
|
|
}
|
|
|
|
if (self->client->ps.groundEntityNum != ENTITYNUM_NONE)
|
|
{
|
|
self->client->fjDidJump = qfalse;
|
|
}
|
|
|
|
if (self->client->ps.fd.forceJumpCharge && self->client->ps.groundEntityNum == ENTITYNUM_NONE && self->client->fjDidJump)
|
|
{ //this was for the "charge" jump method... I guess
|
|
if (ucmd->upmove < 10 && (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_LEVITATION))
|
|
{
|
|
G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE);
|
|
self->client->ps.fd.forceJumpCharge = 0;
|
|
}
|
|
}
|
|
|
|
#ifndef METROID_JUMP
|
|
else if ( (ucmd->upmove > 10) && (self->client->ps.pm_flags & PMF_JUMP_HELD) && self->client->ps.groundTime && (level.time - self->client->ps.groundTime) > 150 && !BG_HasYsalamiri(g_gametype.integer, &self->client->ps) && BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, FP_LEVITATION) )
|
|
{//just charging up
|
|
ForceJumpCharge( self, ucmd );
|
|
usingForce = qtrue;
|
|
}
|
|
else if (ucmd->upmove < 10 && self->client->ps.groundEntityNum == ENTITYNUM_NONE && self->client->ps.fd.forceJumpCharge)
|
|
{
|
|
self->client->ps.pm_flags &= ~(PMF_JUMP_HELD);
|
|
}
|
|
#endif
|
|
|
|
if (!(self->client->ps.pm_flags & PMF_JUMP_HELD) && self->client->ps.fd.forceJumpCharge)
|
|
{
|
|
if (!(ucmd->buttons & BUTTON_FORCEPOWER) ||
|
|
self->client->ps.fd.forcePowerSelected != FP_LEVITATION)
|
|
{
|
|
if (WP_DoSpecificPower( self, ucmd, FP_LEVITATION ))
|
|
{
|
|
usingForce = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ucmd->buttons & BUTTON_FORCEGRIP )
|
|
{ //grip is one of the powers with its own button.. if it's held, call the specific grip power function.
|
|
if (WP_DoSpecificPower( self, ucmd, FP_GRIP ))
|
|
{
|
|
usingForce = qtrue;
|
|
}
|
|
else
|
|
{ //don't let recharge even if the grip misses if the player still has the button down
|
|
usingForce = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{ //see if we're using it generically.. if not, stop.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_GRIP))
|
|
{
|
|
if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_GRIP)
|
|
{
|
|
WP_ForcePowerStop(self, FP_GRIP);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
|
|
{ //lightning
|
|
WP_DoSpecificPower(self, ucmd, FP_LIGHTNING);
|
|
usingForce = qtrue;
|
|
}
|
|
else
|
|
{ //see if we're using it generically.. if not, stop.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_LIGHTNING))
|
|
{
|
|
if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_LIGHTNING)
|
|
{
|
|
WP_ForcePowerStop(self, FP_LIGHTNING);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ucmd->buttons & BUTTON_FORCE_DRAIN )
|
|
{ //drain
|
|
WP_DoSpecificPower(self, ucmd, FP_DRAIN);
|
|
usingForce = qtrue;
|
|
}
|
|
else
|
|
{ //see if we're using it generically.. if not, stop.
|
|
if (self->client->ps.fd.forcePowersActive & (1 << FP_DRAIN))
|
|
{
|
|
if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_DRAIN)
|
|
{
|
|
WP_ForcePowerStop(self, FP_DRAIN);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (ucmd->buttons & BUTTON_FORCEPOWER) &&
|
|
BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, self->client->ps.fd.forcePowerSelected))
|
|
{
|
|
if (self->client->ps.fd.forcePowerSelected == FP_LEVITATION)
|
|
{
|
|
ForceJumpCharge( self, ucmd );
|
|
usingForce = qtrue;
|
|
}
|
|
else if (WP_DoSpecificPower( self, ucmd, self->client->ps.fd.forcePowerSelected ))
|
|
{
|
|
usingForce = qtrue;
|
|
}
|
|
else if (self->client->ps.fd.forcePowerSelected == FP_GRIP)
|
|
{
|
|
usingForce = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forceButtonNeedRelease = 0;
|
|
}
|
|
|
|
for ( i = 0; i < NUM_FORCE_POWERS; i++ )
|
|
{
|
|
if ( self->client->ps.fd.forcePowerDuration[i] )
|
|
{
|
|
if ( self->client->ps.fd.forcePowerDuration[i] < level.time )
|
|
{
|
|
if ( (self->client->ps.fd.forcePowersActive&( 1 << i )) )
|
|
{//turn it off
|
|
WP_ForcePowerStop( self, (forcePowers_t)i );
|
|
}
|
|
self->client->ps.fd.forcePowerDuration[i] = 0;
|
|
}
|
|
}
|
|
if ( (self->client->ps.fd.forcePowersActive&( 1 << i )) )
|
|
{
|
|
usingForce = qtrue;
|
|
WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
|
|
}
|
|
}
|
|
if ( self->client->ps.saberInFlight && self->client->ps.saberEntityNum )
|
|
{//don't regen force power while throwing saber
|
|
if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
|
|
{//
|
|
if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
|
|
{//fell to the ground and we're trying to pull it back
|
|
usingForce = qtrue;
|
|
}
|
|
}
|
|
}
|
|
if ( !self->client->ps.fd.forcePowersActive || self->client->ps.fd.forcePowersActive == (1 << FP_DRAIN) )
|
|
{//when not using the force, regenerate at 1 point per half second
|
|
if ( !self->client->ps.saberInFlight && self->client->ps.fd.forcePowerRegenDebounceTime < level.time &&
|
|
(self->client->ps.weapon != WP_SABER || !BG_SaberInSpecial(self->client->ps.saberMove)) )
|
|
{
|
|
if (g_gametype.integer != GT_HOLOCRON || g_MaxHolocronCarry.value)
|
|
{
|
|
//if (!g_trueJedi.integer || self->client->ps.weapon == WP_SABER)
|
|
//let non-jedi force regen since we're doing a more strict jedi/non-jedi thing... this gives dark jedi something to drain
|
|
{
|
|
if (self->client->ps.powerups[PW_FORCE_BOON])
|
|
{
|
|
WP_ForcePowerRegenerate( self, 6 );
|
|
}
|
|
else if (self->client->ps.isJediMaster && g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
WP_ForcePowerRegenerate( self, 4 ); //jedi master regenerates 4 times as fast
|
|
}
|
|
else
|
|
{
|
|
WP_ForcePowerRegenerate( self, 0 );
|
|
}
|
|
}
|
|
/*
|
|
else if (g_trueJedi.integer && self->client->ps.weapon != WP_SABER)
|
|
{
|
|
self->client->ps.fd.forcePower = 0;
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{ //regenerate based on the number of holocrons carried
|
|
holoregen = 0;
|
|
holo = 0;
|
|
while (holo < NUM_FORCE_POWERS)
|
|
{
|
|
if (self->client->ps.holocronsCarried[holo])
|
|
{
|
|
holoregen++;
|
|
}
|
|
holo++;
|
|
}
|
|
|
|
WP_ForcePowerRegenerate(self, holoregen);
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
if (self->client->holdingObjectiveItem &&
|
|
g_entities[self->client->holdingObjectiveItem].inuse &&
|
|
g_entities[self->client->holdingObjectiveItem].genericValue15)
|
|
{ //1 point per 7 seconds.. super slow
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + 7000;
|
|
}
|
|
else if (self->client->siegeClass != -1 &&
|
|
(bgSiegeClasses[self->client->siegeClass].classflags & (1<<CFL_FASTFORCEREGEN)))
|
|
{ //if this is siege and our player class has the fast force regen ability, then recharge with 1/5th the usual delay
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer*0.2);
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + g_forceRegenTime.integer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( g_gametype.integer == GT_POWERDUEL && self->client->sess.duelTeam == DUELTEAM_LONE )
|
|
{
|
|
if ( g_duel_fraglimit.integer )
|
|
{
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer*
|
|
(0.6 + (.3 * (float)self->client->sess.wins / (float)g_duel_fraglimit.integer)));
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer*0.7);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->client->ps.fd.forcePowerRegenDebounceTime = level.time + g_forceRegenTime.integer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
powersetcheck:
|
|
|
|
if (prepower && self->client->ps.fd.forcePower < prepower)
|
|
{
|
|
int dif = ((prepower - self->client->ps.fd.forcePower)/2);
|
|
if (dif < 1)
|
|
{
|
|
dif = 1;
|
|
}
|
|
|
|
self->client->ps.fd.forcePower = (prepower-dif);
|
|
}
|
|
}
|
|
|
|
qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc )
|
|
{
|
|
int dodgeAnim = -1;
|
|
|
|
if ( !self || !self->client || self->health <= 0 )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (!g_forceDodge.integer)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (g_forceDodge.integer != 2)
|
|
{
|
|
if (!(self->client->ps.fd.forcePowersActive & (1 << FP_SEE)))
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{//can't dodge in mid-air
|
|
return qfalse;
|
|
}
|
|
|
|
if ( self->client->ps.weaponTime > 0 || self->client->ps.forceHandExtend != HANDEXTEND_NONE )
|
|
{//in some effect that stops me from moving on my own
|
|
return qfalse;
|
|
}
|
|
|
|
if (g_forceDodge.integer == 2)
|
|
{
|
|
if (self->client->ps.fd.forcePowersActive)
|
|
{ //for now just don't let us dodge if we're using a force power at all
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if (g_forceDodge.integer == 2)
|
|
{
|
|
if ( !WP_ForcePowerUsable( self, FP_SPEED ) )
|
|
{//make sure we have it and have enough force power
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
if (g_forceDodge.integer == 2)
|
|
{
|
|
if ( Q_irand( 1, 7 ) > self->client->ps.fd.forcePowerLevel[FP_SPEED] )
|
|
{//more likely to fail on lower force speed level
|
|
return qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//We now dodge all the time, but only on level 3
|
|
if (self->client->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_3)
|
|
{//more likely to fail on lower force sight level
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
switch( hitLoc )
|
|
{
|
|
case HL_NONE:
|
|
return qfalse;
|
|
break;
|
|
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
case HL_LEG_RT:
|
|
case HL_LEG_LT:
|
|
return qfalse;
|
|
|
|
case HL_BACK_RT:
|
|
dodgeAnim = BOTH_DODGE_FL;
|
|
break;
|
|
case HL_CHEST_RT:
|
|
dodgeAnim = BOTH_DODGE_FR;
|
|
break;
|
|
case HL_BACK_LT:
|
|
dodgeAnim = BOTH_DODGE_FR;
|
|
break;
|
|
case HL_CHEST_LT:
|
|
dodgeAnim = BOTH_DODGE_FR;
|
|
break;
|
|
case HL_BACK:
|
|
case HL_CHEST:
|
|
case HL_WAIST:
|
|
dodgeAnim = BOTH_DODGE_FL;
|
|
break;
|
|
case HL_ARM_RT:
|
|
case HL_HAND_RT:
|
|
dodgeAnim = BOTH_DODGE_L;
|
|
break;
|
|
case HL_ARM_LT:
|
|
case HL_HAND_LT:
|
|
dodgeAnim = BOTH_DODGE_R;
|
|
break;
|
|
case HL_HEAD:
|
|
dodgeAnim = BOTH_DODGE_FL;
|
|
break;
|
|
default:
|
|
return qfalse;
|
|
}
|
|
|
|
if ( dodgeAnim != -1 )
|
|
{
|
|
//Our own happy way of forcing an anim:
|
|
self->client->ps.forceHandExtend = HANDEXTEND_DODGE;
|
|
self->client->ps.forceDodgeAnim = dodgeAnim;
|
|
self->client->ps.forceHandExtendTime = level.time + 300;
|
|
|
|
self->client->ps.powerups[PW_SPEEDBURST] = level.time + 100;
|
|
|
|
if (g_forceDodge.integer == 2)
|
|
{
|
|
ForceSpeed( self, 500 );
|
|
}
|
|
else
|
|
{
|
|
G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/speed.wav") );
|
|
}
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|