mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-29 23:42:38 +00:00
0723c2335e
g_saberRealisticCombat should be set to "1" g_dismemberment should be set to "4" g_saberMoreRealistic is no longer used changed logic so that it should be the same approach for dealing damage as JKO with the above settings when TBDC is enabled fixed saber bounce in JKA too
2168 lines
59 KiB
C++
2168 lines
59 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999 - 2005, Id Software, Inc.
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "Q3_Interface.h"
|
|
#include "g_nav.h"
|
|
#include "g_roff.h"
|
|
#include "g_navigator.h"
|
|
#include "b_local.h"
|
|
#include "anims.h"
|
|
#include "objectives.h"
|
|
#include "../cgame/cg_local.h" // yeah I know this is naughty, but we're shipping soon...
|
|
|
|
//rww - RAGDOLL_BEGIN
|
|
#include "../ghoul2/ghoul2_gore.h"
|
|
//rww - RAGDOLL_END
|
|
|
|
#include "qcommon/ojk_saved_game_helper.h"
|
|
#include "qcommon/q_version.h"
|
|
|
|
extern void WP_SaberLoadParms( void );
|
|
extern qboolean G_PlayerSpawned( void );
|
|
|
|
extern void Rail_Initialize( void );
|
|
extern void Rail_Update( void );
|
|
extern void Rail_Reset(void);
|
|
|
|
extern void Troop_Initialize( void );
|
|
extern void Troop_Update( void );
|
|
extern void Troop_Reset(void);
|
|
|
|
extern void Pilot_Reset(void);
|
|
extern void Pilot_Update(void);
|
|
|
|
extern void G_ASPreCacheFree(void);
|
|
|
|
|
|
int eventClearTime = 0;
|
|
|
|
extern qboolean g_bCollidableRoffs;
|
|
|
|
|
|
#define STEPSIZE 18
|
|
|
|
level_locals_t level;
|
|
game_import_t gi;
|
|
game_export_t globals;
|
|
gentity_t g_entities[MAX_GENTITIES];
|
|
unsigned int g_entityInUseBits[MAX_GENTITIES/32];
|
|
|
|
static void ClearAllInUse(void)
|
|
{
|
|
memset(g_entityInUseBits,0,sizeof(g_entityInUseBits));
|
|
}
|
|
|
|
void SetInUse(gentity_t *ent)
|
|
{
|
|
assert(((uintptr_t)ent)>=(uintptr_t)g_entities);
|
|
assert(((uintptr_t)ent)<=(uintptr_t)(g_entities+MAX_GENTITIES-1));
|
|
unsigned int entNum=ent-g_entities;
|
|
g_entityInUseBits[entNum/32]|=((unsigned int)1)<<(entNum&0x1f);
|
|
}
|
|
|
|
void ClearInUse(gentity_t *ent)
|
|
{
|
|
assert(((uintptr_t)ent)>=(uintptr_t)g_entities);
|
|
assert(((uintptr_t)ent)<=(uintptr_t)(g_entities+MAX_GENTITIES-1));
|
|
unsigned int entNum=ent-g_entities;
|
|
g_entityInUseBits[entNum/32]&=~(((unsigned int)1)<<(entNum&0x1f));
|
|
}
|
|
|
|
qboolean PInUse(unsigned int entNum)
|
|
{
|
|
assert(entNum>=0);
|
|
assert(entNum<MAX_GENTITIES);
|
|
return (qboolean)((g_entityInUseBits[entNum/32]&(((unsigned int)1)<<(entNum&0x1f)))!=0);
|
|
}
|
|
|
|
/*qboolean PInUse2(gentity_t *ent)
|
|
{
|
|
assert(((unsigned int)ent)>=(unsigned int)g_entities);
|
|
assert(((unsigned int)ent)<=(unsigned int)(g_entities+MAX_GENTITIES-1));
|
|
unsigned int entNum=ent-g_entities;
|
|
return((g_entityInUseBits[entNum/32]&(((unsigned int)1)<<(entNum&0x1f)))!=0);
|
|
}
|
|
*/
|
|
|
|
void WriteInUseBits()
|
|
{
|
|
ojk::SavedGameHelper saved_game(
|
|
::gi.saved_game);
|
|
|
|
saved_game.write_chunk<uint32_t>(
|
|
INT_ID('I', 'N', 'U', 'S'),
|
|
::g_entityInUseBits);
|
|
}
|
|
|
|
void ReadInUseBits()
|
|
{
|
|
ojk::SavedGameHelper saved_game(
|
|
::gi.saved_game);
|
|
|
|
saved_game.read_chunk<uint32_t>(
|
|
INT_ID('I', 'N', 'U', 'S'),
|
|
::g_entityInUseBits);
|
|
|
|
// This is only temporary. Once I have converted all the ent->inuse refs,
|
|
// it won;t be needed -MW.
|
|
for(int i=0;i<MAX_GENTITIES;i++)
|
|
{
|
|
g_entities[i].inuse=PInUse(i);
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
static void ValidateInUseBits(void)
|
|
{
|
|
for(int i=0;i<MAX_GENTITIES;i++)
|
|
{
|
|
assert(g_entities[i].inuse==PInUse(i));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
gentity_t *player;
|
|
|
|
cvar_t *g_speed;
|
|
cvar_t *g_gravity;
|
|
cvar_t *g_stepSlideFix;
|
|
|
|
cvar_t *g_sex;
|
|
cvar_t *g_spskill;
|
|
cvar_t *g_cheats;
|
|
cvar_t *g_developer;
|
|
cvar_t *g_timescale;
|
|
cvar_t *g_knockback;
|
|
cvar_t *g_dismemberment;
|
|
cvar_t *g_corpseRemovalTime;
|
|
|
|
cvar_t *g_synchSplitAnims;
|
|
#ifndef FINAL_BUILD
|
|
cvar_t *g_AnimWarning;
|
|
#endif
|
|
cvar_t *g_noFootSlide;
|
|
cvar_t *g_noFootSlideRunScale;
|
|
cvar_t *g_noFootSlideWalkScale;
|
|
|
|
cvar_t *g_nav1;
|
|
cvar_t *g_nav2;
|
|
cvar_t *g_bobaDebug;
|
|
|
|
cvar_t *g_delayedShutdown;
|
|
|
|
cvar_t *g_inactivity;
|
|
cvar_t *g_debugMove;
|
|
cvar_t *g_debugDamage;
|
|
cvar_t *g_weaponRespawn;
|
|
cvar_t *g_subtitles;
|
|
cvar_t *g_ICARUSDebug;
|
|
|
|
cvar_t *com_buildScript;
|
|
|
|
cvar_t *g_skippingcin;
|
|
cvar_t *g_AIsurrender;
|
|
cvar_t *g_numEntities;
|
|
//cvar_t *g_iscensored;
|
|
|
|
cvar_t *g_TeamBeefDirectorsCut;
|
|
|
|
cvar_t *g_saberAutoDeflect1stPerson;
|
|
cvar_t *g_saberAutoBlocking;
|
|
cvar_t *g_saberRealisticCombat;
|
|
cvar_t *debug_subdivision;
|
|
cvar_t *g_saberDamageCapping;
|
|
cvar_t *g_saberMoveSpeed;
|
|
cvar_t *g_saberAnimSpeed;
|
|
cvar_t *g_saberAutoAim;
|
|
cvar_t *g_saberNewControlScheme;
|
|
cvar_t *g_debugSaberLock;
|
|
cvar_t *g_saberLockRandomNess;
|
|
cvar_t *g_debugMelee;
|
|
cvar_t *g_saberRestrictForce;
|
|
cvar_t *g_saberPickuppableDroppedSabers;
|
|
cvar_t *g_dismemberProbabilities;
|
|
|
|
cvar_t *g_speederControlScheme;
|
|
|
|
cvar_t *g_char_model;
|
|
cvar_t *g_char_skin_head;
|
|
cvar_t *g_char_skin_torso;
|
|
cvar_t *g_char_skin_legs;
|
|
cvar_t *g_char_color_red;
|
|
cvar_t *g_char_color_green;
|
|
cvar_t *g_char_color_blue;
|
|
cvar_t *g_saber;
|
|
cvar_t *g_saber2;
|
|
cvar_t *g_saber_color;
|
|
cvar_t *g_saber2_color;
|
|
cvar_t *g_saberDarkSideSaberColor;
|
|
|
|
// kef -- used with DebugTraceForNPC
|
|
cvar_t *g_npcdebug;
|
|
|
|
// mcg -- testing: make NPCs obey do not enter brushes better?
|
|
cvar_t *g_navSafetyChecks;
|
|
|
|
cvar_t *g_broadsword;
|
|
|
|
cvar_t *g_allowBunnyhopping;
|
|
|
|
qboolean stop_icarus = qfalse;
|
|
|
|
extern char *G_GetLocationForEnt( gentity_t *ent );
|
|
extern void CP_FindCombatPointWaypoints( void );
|
|
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
|
|
|
|
void G_RunFrame (int levelTime);
|
|
void PrintEntClassname( int gentNum );
|
|
void ClearNPCGlobals( void );
|
|
extern void AI_UpdateGroups( void );
|
|
|
|
void ClearPlayerAlertEvents( void );
|
|
extern void NPC_ShowDebugInfo (void);
|
|
extern int killPlayerTimer;
|
|
|
|
/*
|
|
static void G_DynamicMusicUpdate( usercmd_t *ucmd )
|
|
|
|
FIXME: can we merge any of this with the G_ChooseLookEnemy stuff?
|
|
*/
|
|
static void G_DynamicMusicUpdate( void )
|
|
{
|
|
gentity_t *ent;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t mins, maxs;
|
|
int i, e;
|
|
int distSq, radius = 2048;
|
|
vec3_t center;
|
|
int danger = 0;
|
|
int battle = 0;
|
|
int entTeam;
|
|
//qboolean dangerNear = qfalse;
|
|
//qboolean suspicious = qfalse;
|
|
qboolean LOScalced = qfalse, clearLOS = qfalse;
|
|
|
|
//FIXME: intro and/or other cues? (one-shot music sounds)
|
|
|
|
//loops
|
|
|
|
//player-based
|
|
if ( !player )
|
|
{//WTF?
|
|
player = &g_entities[0];
|
|
return;
|
|
}
|
|
|
|
if ( !G_PlayerSpawned() )
|
|
{//player hasn't spawned yet!
|
|
return;
|
|
}
|
|
|
|
if ( player->health <= 0 && player->max_health > 0 )
|
|
{//defeat music
|
|
if ( level.dmState != DM_DEATH )
|
|
{
|
|
level.dmState = DM_DEATH;
|
|
}
|
|
}
|
|
|
|
if ( level.dmState == DM_DEATH )
|
|
{
|
|
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "death" );
|
|
return;
|
|
}
|
|
|
|
if ( level.dmState == DM_BOSS )
|
|
{
|
|
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "boss" );
|
|
return;
|
|
}
|
|
|
|
if ( level.dmState == DM_SILENCE )
|
|
{
|
|
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "silence" );
|
|
return;
|
|
}
|
|
|
|
if ( level.dmBeatTime > level.time )
|
|
{//not on a beat
|
|
return;
|
|
}
|
|
|
|
level.dmBeatTime = level.time + 1000;//1 second beats
|
|
|
|
if ( player->health <= 20 )
|
|
{
|
|
danger = 1;
|
|
}
|
|
|
|
//enemy-based
|
|
VectorCopy( player->currentOrigin, center );
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = center[i] - radius;
|
|
maxs[i] = center[i] + radius;
|
|
}
|
|
|
|
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
ent = entityList[ e ];
|
|
if ( !ent || !ent->inuse )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !ent->client || !ent->NPC )
|
|
{
|
|
if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) )
|
|
{//a turret
|
|
entTeam = ent->noDamageTeam;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{//an NPC
|
|
entTeam = ent->client->playerTeam;
|
|
}
|
|
|
|
if ( entTeam == player->client->playerTeam )
|
|
{//ally
|
|
continue;
|
|
}
|
|
|
|
if ( entTeam == TEAM_NEUTRAL && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) )
|
|
{//a droid that is not mad at me or my allies
|
|
continue;
|
|
}
|
|
|
|
if ( !gi.inPVS( player->currentOrigin, ent->currentOrigin ) )
|
|
{//not potentially visible
|
|
continue;
|
|
}
|
|
|
|
if ( ent->client && ent->s.weapon == WP_NONE )
|
|
{//they don't have a weapon... FIXME: only do this for droids?
|
|
continue;
|
|
}
|
|
|
|
LOScalced = clearLOS = qfalse;
|
|
if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTime<level.time)) || (ent->client&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time))
|
|
{//mad
|
|
if ( ent->health > 0 )
|
|
{//alive
|
|
//FIXME: do I really need this check?
|
|
if ( ent->s.weapon == WP_SABER && ent->client && !ent->client->ps.SaberActive() && ent->enemy != player )
|
|
{//a Jedi who has not yet gotten made at me
|
|
continue;
|
|
}
|
|
if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC )
|
|
{//they're not actually going to do anything about being mad at me...
|
|
continue;
|
|
}
|
|
//okay, they're in my PVS, but how close are they? Are they actively attacking me?
|
|
if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 )
|
|
{//a turret that shot at me less than ten seconds ago
|
|
}
|
|
else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 )
|
|
{//an NPC that shot at me less than ten seconds ago
|
|
}
|
|
else
|
|
{//not actively attacking me lately, see how far away they are
|
|
distSq = DistanceSquared( ent->currentOrigin, player->currentOrigin );
|
|
if ( distSq > 4194304/*2048*2048*/ )
|
|
{//> 2048 away
|
|
continue;
|
|
}
|
|
else if ( distSq > 1048576/*1024*1024*/ )
|
|
{//> 1024 away
|
|
clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent );
|
|
LOScalced = qtrue;
|
|
if ( clearLOS == qfalse )
|
|
{//No LOS
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
battle++;
|
|
}
|
|
}
|
|
|
|
if ( level.dmState == DM_EXPLORE )
|
|
{//only do these visibility checks if you're still in exploration mode
|
|
if ( !InFront( ent->currentOrigin, player->currentOrigin, player->client->ps.viewangles, 0.0f) )
|
|
{//not in front
|
|
continue;
|
|
}
|
|
|
|
if ( !LOScalced )
|
|
{
|
|
clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent );
|
|
}
|
|
if ( !clearLOS )
|
|
{//can't see them directly
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ent->health <= 0 )
|
|
{//dead
|
|
if ( !ent->client || level.time - ent->s.time > 10000 )
|
|
{//corpse has been dead for more than 10 seconds
|
|
//FIXME: coming across corpses should cause danger sounds too?
|
|
continue;
|
|
}
|
|
}
|
|
//we see enemies and/or corpses
|
|
danger++;
|
|
}
|
|
|
|
if ( !battle )
|
|
{//no active enemies, but look for missiles, shot impacts, etc...
|
|
int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS );
|
|
if ( alert != -1 )
|
|
{//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme
|
|
if ( G_CheckForDanger( player, alert ) )
|
|
{//found danger near by
|
|
danger++;
|
|
battle = 1;
|
|
}
|
|
else if ( level.alertEvents[alert].owner && (level.alertEvents[alert].owner == player->enemy || (level.alertEvents[alert].owner->client && level.alertEvents[alert].owner->client->playerTeam == player->client->enemyTeam) ) )
|
|
{//NPC on enemy team of player made some noise
|
|
switch ( level.alertEvents[alert].level )
|
|
{
|
|
case AEL_DISCOVERED:
|
|
//dangerNear = qtrue;
|
|
break;
|
|
case AEL_SUSPICIOUS:
|
|
//suspicious = qtrue;
|
|
break;
|
|
case AEL_MINOR:
|
|
//distraction = qtrue;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( battle )
|
|
{//battle - this can interrupt level.dmDebounceTime of lower intensity levels
|
|
//play battle
|
|
if ( level.dmState != DM_ACTION )
|
|
{
|
|
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "action" );
|
|
}
|
|
level.dmState = DM_ACTION;
|
|
if ( battle > 5 )
|
|
{
|
|
//level.dmDebounceTime = level.time + 8000;//don't change again for 5 seconds
|
|
}
|
|
else
|
|
{
|
|
//level.dmDebounceTime = level.time + 3000 + 1000*battle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( level.dmDebounceTime > level.time )
|
|
{//not ready to switch yet
|
|
return;
|
|
}
|
|
else
|
|
{//at least 1 second (for beats)
|
|
//level.dmDebounceTime = level.time + 1000;//FIXME: define beat time?
|
|
}
|
|
/*
|
|
if ( danger || dangerNear )
|
|
{//danger
|
|
//stay on whatever we were on, action or exploration
|
|
if ( !danger )
|
|
{//minimum
|
|
danger = 1;
|
|
}
|
|
if ( danger > 3 )
|
|
{
|
|
level.dmDebounceTime = level.time + 5000;
|
|
}
|
|
else
|
|
{
|
|
level.dmDebounceTime = level.time + 2000 + 1000*danger;
|
|
}
|
|
}
|
|
else
|
|
*/
|
|
{//still nothing dangerous going on
|
|
if ( level.dmState != DM_EXPLORE )
|
|
{//just went to explore, hold it for a couple seconds at least
|
|
//level.dmDebounceTime = level.time + 2000;
|
|
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "explore" );
|
|
}
|
|
level.dmState = DM_EXPLORE;
|
|
//FIXME: look for interest points and play "mysterious" music instead of exploration?
|
|
//FIXME: suspicious and distraction sounds should play some cue or change music in a subtle way?
|
|
//play exploration
|
|
}
|
|
//FIXME: when do we go to silence?
|
|
}
|
|
}
|
|
|
|
void G_ConnectNavs( const char *mapname, int checkSum )
|
|
{
|
|
NAV::LoadFromEntitiesAndSaveToFile(mapname, checkSum);
|
|
CP_FindCombatPointWaypoints();
|
|
}
|
|
|
|
/*
|
|
================
|
|
G_FindTeams
|
|
|
|
Chain together all entities with a matching team field.
|
|
Entity teams are used for item groups and multi-entity mover groups.
|
|
|
|
All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
|
|
All but the last will have the teamchain field set to the next one
|
|
================
|
|
*/
|
|
void G_FindTeams( void ) {
|
|
gentity_t *e, *e2;
|
|
int i, j;
|
|
int c, c2;
|
|
|
|
c = 0;
|
|
c2 = 0;
|
|
// for ( i=1, e=g_entities,i ; i < globals.num_entities ; i++,e++ )
|
|
for ( i=MAX_CLIENTS ; i < globals.num_entities ; i++ )
|
|
{
|
|
// if (!e->inuse)
|
|
// continue;
|
|
if(!PInUse(i))
|
|
continue;
|
|
e=&g_entities[i];
|
|
|
|
if (!e->team)
|
|
continue;
|
|
if (e->flags & FL_TEAMSLAVE)
|
|
continue;
|
|
e->teammaster = e;
|
|
c++;
|
|
c2++;
|
|
// for (j=i+1, e2=e+1 ; j < globals.num_entities ; j++,e2++)
|
|
for (j=i+1; j < globals.num_entities ; j++)
|
|
{
|
|
// if (!e2->inuse)
|
|
// continue;
|
|
if(!PInUse(j))
|
|
continue;
|
|
|
|
e2=&g_entities[j];
|
|
if (!e2->team)
|
|
continue;
|
|
if (e2->flags & FL_TEAMSLAVE)
|
|
continue;
|
|
if (!strcmp(e->team, e2->team))
|
|
{
|
|
c2++;
|
|
e2->teamchain = e->teamchain;
|
|
e->teamchain = e2;
|
|
e2->teammaster = e;
|
|
e2->flags |= FL_TEAMSLAVE;
|
|
|
|
// make sure that targets only point at the master
|
|
if ( e2->targetname ) {
|
|
e->targetname = G_NewString(e2->targetname);
|
|
e2->targetname = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//gi.Printf ("%i teams with %i entities\n", c, c2);
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
G_InitCvars
|
|
|
|
============
|
|
*/
|
|
void G_InitCvars( void ) {
|
|
// don't override the cheat state set by the system
|
|
g_cheats = gi.cvar ("helpUsObi", "", 0);
|
|
g_developer = gi.cvar ("developer", "", 0);
|
|
|
|
// noset vars
|
|
gi.cvar( "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM );
|
|
gi.cvar( "gamedate", SOURCE_DATE , CVAR_ROM );
|
|
g_skippingcin = gi.cvar ("skippingCinematic", "0", CVAR_ROM);
|
|
|
|
// latched vars
|
|
|
|
// change anytime vars
|
|
g_speed = gi.cvar( "g_speed", "250", CVAR_CHEAT );
|
|
g_gravity = gi.cvar( "g_gravity", "800", CVAR_SAVEGAME|CVAR_ROM );
|
|
g_stepSlideFix = gi.cvar( "g_stepSlideFix", "1", CVAR_ARCHIVE );
|
|
g_sex = gi.cvar ("sex", "f", CVAR_USERINFO | CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_spskill = gi.cvar ("g_spskill", "0", CVAR_ARCHIVE | CVAR_SAVEGAME|CVAR_NORESTART);
|
|
g_knockback = gi.cvar( "g_knockback", "1000", CVAR_CHEAT );
|
|
g_dismemberment = gi.cvar ( "g_dismemberment", "4", CVAR_ARCHIVE );//0 = none, 1 = arms and hands, 2 = legs, 3 = waist and head
|
|
// for now I'm making default 10 seconds
|
|
g_corpseRemovalTime = gi.cvar ( "g_corpseRemovalTime", "10", CVAR_ARCHIVE );//number of seconds bodies stick around for, at least... 0 = never go away
|
|
g_synchSplitAnims = gi.cvar ( "g_synchSplitAnims", "1", 0 );
|
|
#ifndef FINAL_BUILD
|
|
g_AnimWarning = gi.cvar ( "g_AnimWarning", "1", 0 );
|
|
#endif
|
|
g_noFootSlide = gi.cvar ( "g_noFootSlide", "1", 0 );
|
|
g_noFootSlideRunScale = gi.cvar ( "g_noFootSlideRunScale", "150.0", 0 );
|
|
g_noFootSlideWalkScale = gi.cvar ( "g_noFootSlideWalkScale", "50.0", 0 );
|
|
|
|
g_nav1 = gi.cvar ( "g_nav1", "", 0 );
|
|
g_nav2 = gi.cvar ( "g_nav2", "", 0 );
|
|
|
|
g_bobaDebug = gi.cvar ( "g_bobaDebug", "", 0 );
|
|
|
|
g_delayedShutdown = gi.cvar ( "g_delayedShutdown", "0", 0 );
|
|
|
|
g_inactivity = gi.cvar ("g_inactivity", "0", 0);
|
|
g_debugMove = gi.cvar ("g_debugMove", "0", CVAR_CHEAT );
|
|
g_debugDamage = gi.cvar ("g_debugDamage", "0", CVAR_CHEAT );
|
|
g_ICARUSDebug = gi.cvar( "g_ICARUSDebug", "0", CVAR_CHEAT );
|
|
g_timescale = gi.cvar( "timescale", "1", 0 );
|
|
g_npcdebug = gi.cvar( "g_npcdebug", "0", 0 );
|
|
g_navSafetyChecks = gi.cvar( "g_navSafetyChecks", "0", 0 );
|
|
// NOTE : I also create this is UI_Init()
|
|
g_subtitles = gi.cvar( "g_subtitles", "0", CVAR_ARCHIVE );
|
|
com_buildScript = gi.cvar ("com_buildscript", "0", 0);
|
|
|
|
g_TeamBeefDirectorsCut = gi.cvar( "g_TeamBeefDirectorsCut", "1", CVAR_ARCHIVE ); // Team Beef Director's Cut tweaks
|
|
|
|
g_saberAutoDeflect1stPerson = gi.cvar( "g_saberAutoDeflect1stPerson", "0", CVAR_ARCHIVE );//Whether the saber will auto deflect missiles in first person
|
|
g_saberAutoBlocking = gi.cvar( "g_saberAutoBlocking", "1", CVAR_ARCHIVE|CVAR_CHEAT );//must press +block button to do any blocking
|
|
|
|
//This is the original setting we had, but it makes the sabers too powerful
|
|
// g_saberRealisticCombat = gi.cvar( "g_saberMoreRealistic", "3", CVAR_ARCHIVE );//makes collision more precise, increases damage
|
|
|
|
//This is the new setting, using the original JKO cvar name, so that any existing players get this new var and the sabers work like JKO
|
|
g_saberRealisticCombat = gi.cvar( "g_saberRealisticCombat", "1", CVAR_ARCHIVE );//makes collision more precise, increases damage
|
|
|
|
debug_subdivision = gi.cvar( "debug_subdivision", "0", CVAR_ARCHIVE );//debug for dismemberment
|
|
g_dismemberProbabilities = gi.cvar ( "g_dismemberProbabilities", "100", CVAR_ARCHIVE );//0 = ignore probabilities, 1 = use probabilities
|
|
g_saberDamageCapping = gi.cvar( "g_saberDamageCapping", "1", CVAR_CHEAT );//caps damage of sabers vs players and NPC who use sabers
|
|
g_saberMoveSpeed = gi.cvar( "g_saberMoveSpeed", "1", CVAR_CHEAT );//how fast you run while attacking with a saber
|
|
g_saberAnimSpeed = gi.cvar( "g_saberAnimSpeed", "1.2", CVAR_ARCHIVE|CVAR_CHEAT );//how fast saber animations run
|
|
g_saberAutoAim = gi.cvar( "g_saberAutoAim", "1", CVAR_CHEAT );//auto-aims at enemies when not moving or when just running forward
|
|
g_saberNewControlScheme = gi.cvar( "g_saberNewControlScheme", "0", CVAR_ARCHIVE );//use +forcefocus to pull off all the special moves
|
|
g_debugSaberLock = gi.cvar( "g_debugSaberLock", "0", CVAR_CHEAT );//just for debugging/development, makes saberlocks happen all the time
|
|
g_saberLockRandomNess = gi.cvar( "g_saberLockRandomNess", "2", CVAR_ARCHIVE );//just for debugging/development, controls frequency of saberlocks
|
|
g_debugMelee = gi.cvar( "g_debugMelee", "0", CVAR_CHEAT );//just for debugging/development, test kicks and grabs
|
|
g_saberRestrictForce = gi.cvar( "g_saberRestrictForce", "0", CVAR_ARCHIVE );//restricts certain force powers when using a 2-handed saber or 2 sabers
|
|
g_saberPickuppableDroppedSabers = gi.cvar( "g_saberPickuppableDroppedSabers", "0", CVAR_CHEAT );//lets you pick up sabers that are dropped
|
|
|
|
g_AIsurrender = gi.cvar( "g_AIsurrender", "0", CVAR_CHEAT );
|
|
g_numEntities = gi.cvar( "g_numEntities", "0", 0 );
|
|
|
|
gi.cvar( "newTotalSecrets", "0", CVAR_ROM );
|
|
gi.cvar_set("newTotalSecrets", "0");//used to carry over the count from SP_target_secret to ClientBegin
|
|
//g_iscensored = gi.cvar( "ui_iscensored", "0", CVAR_ARCHIVE|CVAR_ROM|CVAR_INIT|CVAR_CHEAT|CVAR_NORESTART );
|
|
|
|
g_speederControlScheme = gi.cvar( "g_speederControlScheme", "2", CVAR_ARCHIVE );//2 is default, 1 is alternate
|
|
|
|
g_char_model = gi.cvar( "g_char_model", "jedi_tf", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_skin_head = gi.cvar( "g_char_skin_head", "head_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_skin_torso = gi.cvar( "g_char_skin_torso", "torso_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_skin_legs = gi.cvar( "g_char_skin_legs", "lower_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_color_red = gi.cvar( "g_char_color_red", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_color_green = gi.cvar( "g_char_color_green", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_char_color_blue = gi.cvar( "g_char_color_blue", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_saber = gi.cvar( "g_saber", "single_1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_saber2 = gi.cvar( "g_saber2", "", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_saber_color = gi.cvar( "g_saber_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_saber2_color = gi.cvar( "g_saber2_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
|
|
g_saberDarkSideSaberColor = gi.cvar( "g_saberDarkSideSaberColor", "0", CVAR_ARCHIVE ); //when you turn evil, it turns your saber red!
|
|
|
|
g_broadsword = gi.cvar( "broadsword", "1", 0);
|
|
|
|
g_allowBunnyhopping = gi.cvar( "g_allowBunnyhopping", "0", 0 );
|
|
|
|
gi.cvar( "tier_storyinfo", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART);
|
|
gi.cvar( "tiers_complete", "", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART);
|
|
|
|
gi.cvar( "ui_prisonerobj_currtotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART);
|
|
gi.cvar( "ui_prisonerobj_maxtotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART);
|
|
|
|
gi.cvar( "g_clearstats", "1", CVAR_ROM|CVAR_NORESTART);
|
|
|
|
gi.cvar( "g_JKXRHelpEnabled", "1", CVAR_ARCHIVE ); // Help menu item
|
|
}
|
|
/*
|
|
============
|
|
InitGame
|
|
|
|
============
|
|
*/
|
|
|
|
// I'm just declaring a global here which I need to get at in NAV_GenerateSquadPaths for deciding if pre-calc'd
|
|
// data is valid, and this saves changing the proto of G_SpawnEntitiesFromString() to include a checksum param which
|
|
// may get changed anyway if a new nav system is ever used. This way saves messing with g_local.h each time -slc
|
|
int giMapChecksum;
|
|
SavedGameJustLoaded_e g_eSavedGameJustLoaded;
|
|
qboolean g_qbLoadTransition = qfalse;
|
|
void InitGame( const char *mapname, const char *spawntarget, int checkSum, const char *entities, int levelTime, int randomSeed, int globalTime, SavedGameJustLoaded_e eSavedGameJustLoaded, qboolean qbLoadTransition )
|
|
{
|
|
//rww - default this to 0, we will auto-set it to 1 if we run into a terrain ent
|
|
gi.cvar_set("RMG", "0");
|
|
|
|
g_bCollidableRoffs = qfalse;
|
|
|
|
giMapChecksum = checkSum;
|
|
g_eSavedGameJustLoaded = eSavedGameJustLoaded;
|
|
g_qbLoadTransition = qbLoadTransition;
|
|
|
|
gi.Printf ("------- Game Initialization -------\n");
|
|
gi.Printf ("gamename: %s\n", GAMEVERSION);
|
|
gi.Printf ("gamedate: %s\n", SOURCE_DATE);
|
|
|
|
srand( randomSeed );
|
|
|
|
G_InitCvars();
|
|
|
|
G_InitMemory();
|
|
|
|
// set some level globals
|
|
memset( &level, 0, sizeof( level ) );
|
|
level.time = levelTime;
|
|
level.globalTime = globalTime;
|
|
Q_strncpyz( level.mapname, mapname, sizeof(level.mapname) );
|
|
if ( spawntarget != NULL && spawntarget[0] )
|
|
{
|
|
Q_strncpyz( level.spawntarget, spawntarget, sizeof(level.spawntarget) );
|
|
}
|
|
else
|
|
{
|
|
level.spawntarget[0] = 0;
|
|
}
|
|
|
|
|
|
G_InitWorldSession();
|
|
|
|
// initialize all entities for this game
|
|
memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
|
|
globals.gentities = g_entities;
|
|
ClearAllInUse();
|
|
// initialize all clients for this game
|
|
level.maxclients = 1;
|
|
level.clients = (gclient_t*) G_Alloc( level.maxclients * sizeof(level.clients[0]) );
|
|
memset(level.clients, 0, level.maxclients * sizeof(level.clients[0]));
|
|
|
|
// set client fields on player
|
|
g_entities[0].client = level.clients;
|
|
|
|
// always leave room for the max number of clients,
|
|
// even if they aren't all used, so numbers inside that
|
|
// range are NEVER anything but clients
|
|
globals.num_entities = MAX_CLIENTS;
|
|
|
|
//Load sabers.cfg data
|
|
WP_SaberLoadParms();
|
|
//Set up NPC init data
|
|
NPC_InitGame();
|
|
|
|
TIMER_Clear();
|
|
Rail_Reset();
|
|
Troop_Reset();
|
|
Pilot_Reset();
|
|
|
|
IT_LoadItemParms ();
|
|
|
|
ClearRegisteredItems();
|
|
|
|
// clear out old nav info, attempt to load from file
|
|
NAV::LoadFromFile(level.mapname, giMapChecksum);
|
|
|
|
// parse the key/value pairs and spawn gentities
|
|
G_SpawnEntitiesFromString( entities );
|
|
|
|
// general initialization
|
|
G_FindTeams();
|
|
|
|
// SaveRegisteredItems();
|
|
|
|
gi.Printf ("-----------------------------------\n");
|
|
|
|
Rail_Initialize();
|
|
Troop_Initialize();
|
|
|
|
|
|
player = &g_entities[0];
|
|
|
|
//Init dynamic music
|
|
level.dmState = DM_EXPLORE;
|
|
level.dmDebounceTime = 0;
|
|
level.dmBeatTime = 0;
|
|
|
|
level.curAlertID = 1;//0 is default for lastAlertEvent, so...
|
|
eventClearTime = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
ShutdownGame
|
|
=================
|
|
*/
|
|
void ShutdownGame( void )
|
|
{
|
|
// write all the client session data so we can get it back
|
|
G_WriteSessionData();
|
|
|
|
// Destroy the Game Interface.
|
|
IGameInterface::Destroy();
|
|
|
|
// Shut ICARUS down.
|
|
IIcarusInterface::DestroyIcarus();
|
|
|
|
// Destroy the Game Interface again. Only way to really free everything.
|
|
IGameInterface::Destroy();
|
|
|
|
TAG_Init(); //Clear the reference tags
|
|
/*
|
|
Ghoul2 Insert Start
|
|
*/
|
|
for (int i=0; i<MAX_GENTITIES; i++)
|
|
{
|
|
gi.G2API_CleanGhoul2Models(g_entities[i].ghoul2);
|
|
}
|
|
/*
|
|
Ghoul2 Insert End
|
|
*/
|
|
G_ASPreCacheFree();
|
|
}
|
|
|
|
|
|
|
|
//===================================================================
|
|
|
|
static void G_Cvar_Create( const char *var_name, const char *var_value, int flags ) {
|
|
gi.cvar( var_name, var_value, flags );
|
|
}
|
|
|
|
//BEGIN GAMESIDE RMG
|
|
qboolean G_ParseSpawnVars( const char **data );
|
|
void G_SpawnGEntityFromSpawnVars( void );
|
|
|
|
void G_GameSpawnRMGEntity(char *s)
|
|
{
|
|
if (G_ParseSpawnVars((const char **)&s))
|
|
{
|
|
G_SpawnGEntityFromSpawnVars();
|
|
}
|
|
}
|
|
//END GAMESIDE RMG
|
|
|
|
/*
|
|
=================
|
|
GetGameAPI
|
|
|
|
Returns a pointer to the structure with all entry points
|
|
and global variables
|
|
=================
|
|
*/
|
|
extern int PM_ValidateAnimRange( const int startFrame, const int endFrame, const float animSpeed );
|
|
extern "C" Q_EXPORT game_export_t* QDECL GetGameAPI( game_import_t *import ) {
|
|
gameinfo_import_t gameinfo_import;
|
|
|
|
gi = *import;
|
|
|
|
globals.apiversion = GAME_API_VERSION;
|
|
globals.Init = InitGame;
|
|
globals.Shutdown = ShutdownGame;
|
|
|
|
globals.WriteLevel = WriteLevel;
|
|
globals.ReadLevel = ReadLevel;
|
|
globals.GameAllowedToSaveHere = GameAllowedToSaveHere;
|
|
|
|
globals.ClientThink = ClientThink;
|
|
globals.ClientConnect = ClientConnect;
|
|
globals.ClientUserinfoChanged = ClientUserinfoChanged;
|
|
globals.ClientDisconnect = ClientDisconnect;
|
|
globals.ClientBegin = ClientBegin;
|
|
globals.ClientCommand = ClientCommand;
|
|
|
|
globals.RunFrame = G_RunFrame;
|
|
globals.ConnectNavs = G_ConnectNavs;
|
|
|
|
globals.ConsoleCommand = ConsoleCommand;
|
|
//globals.PrintEntClassname = PrintEntClassname;
|
|
|
|
//globals.ValidateAnimRange = PM_ValidateAnimRange;
|
|
|
|
globals.GameSpawnRMGEntity = G_GameSpawnRMGEntity;
|
|
|
|
globals.gentitySize = sizeof(gentity_t);
|
|
|
|
gameinfo_import.FS_FOpenFile = gi.FS_FOpenFile;
|
|
gameinfo_import.FS_Read = gi.FS_Read;
|
|
gameinfo_import.FS_FCloseFile = gi.FS_FCloseFile;
|
|
gameinfo_import.Cvar_Set = gi.cvar_set;
|
|
gameinfo_import.Cvar_VariableStringBuffer = gi.Cvar_VariableStringBuffer;
|
|
gameinfo_import.Cvar_Create = G_Cvar_Create;
|
|
|
|
GI_Init( &gameinfo_import );
|
|
|
|
return &globals;
|
|
}
|
|
|
|
void QDECL G_Error( const char *fmt, ... ) {
|
|
va_list argptr;
|
|
char text[1024];
|
|
|
|
va_start (argptr, fmt);
|
|
Q_vsnprintf (text, sizeof(text), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
gi.Error( ERR_DROP, "%s", text);
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Com_Error
|
|
-------------------------
|
|
*/
|
|
|
|
void Com_Error ( int level, const char *error, ... ) {
|
|
va_list argptr;
|
|
char text[1024];
|
|
|
|
va_start (argptr, error);
|
|
Q_vsnprintf (text, sizeof(text), error, argptr);
|
|
va_end (argptr);
|
|
|
|
gi.Error( level, "%s", text);
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Com_Printf
|
|
-------------------------
|
|
*/
|
|
|
|
void Com_Printf( const char *msg, ... ) {
|
|
va_list argptr;
|
|
char text[1024];
|
|
|
|
va_start (argptr, msg);
|
|
Q_vsnprintf (text, sizeof(text), msg, argptr);
|
|
va_end (argptr);
|
|
|
|
gi.Printf ("%s", text);
|
|
}
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
MAP CHANGING
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
FUNCTIONS CALLED EVERY FRAME
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
static void G_CheckTasksCompleted (gentity_t *ent)
|
|
{
|
|
if ( Q3_TaskIDPending( ent, TID_CHAN_VOICE ) )
|
|
{
|
|
if ( !gi.VoiceVolume[ent->s.number] )
|
|
{//not playing a voice sound
|
|
//return task_complete
|
|
Q3_TaskIDComplete( ent, TID_CHAN_VOICE );
|
|
}
|
|
}
|
|
|
|
if ( Q3_TaskIDPending( ent, TID_LOCATION ) )
|
|
{
|
|
char *currentLoc = G_GetLocationForEnt( ent );
|
|
|
|
if ( currentLoc && currentLoc[0] && Q_stricmp( ent->message, currentLoc ) == 0 )
|
|
{//we're in the desired location
|
|
Q3_TaskIDComplete( ent, TID_LOCATION );
|
|
}
|
|
//FIXME: else see if were in other trigger_locations?
|
|
}
|
|
}
|
|
|
|
static void G_CheckSpecialPersistentEvents( gentity_t *ent )
|
|
{//special-case alerts that would be a pain in the ass to have the ent's think funcs generate
|
|
if ( ent == NULL )
|
|
{
|
|
return;
|
|
}
|
|
if ( ent->s.eType == ET_MISSILE && ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_STATIONARY )
|
|
{
|
|
if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
|
|
{//events were just cleared out so add me again
|
|
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue );
|
|
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
|
|
}
|
|
}
|
|
if ( ent->forcePushTime >= level.time )
|
|
{//being pushed
|
|
if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
|
|
{//events were just cleared out so add me again
|
|
//NOTE: presumes the player did the pushing, this is not always true, but shouldn't really matter?
|
|
if ( ent->item && ent->item->giTag == INV_SECURITY_KEY )
|
|
{
|
|
AddSightEvent( player, ent->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
|
|
}
|
|
else
|
|
{
|
|
AddSightEvent( player, ent->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
|
|
}
|
|
}
|
|
}
|
|
if ( ent->contents == CONTENTS_LIGHTSABER && !Q_stricmp( "lightsaber", ent->classname ) )
|
|
{//lightsaber
|
|
if( ent->owner && ent->owner->client )
|
|
{
|
|
if ( ent->owner->client->ps.SaberLength() > 0 )
|
|
{//it's on
|
|
//sight event
|
|
AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
=============
|
|
G_RunThink
|
|
|
|
Runs thinking code for this frame if necessary
|
|
=============
|
|
*/
|
|
void G_RunThink (gentity_t *ent)
|
|
{
|
|
if ( (ent->nextthink <= 0) || (ent->nextthink > level.time) )
|
|
{
|
|
goto runicarus;
|
|
}
|
|
|
|
ent->nextthink = 0;
|
|
if ( ent->e_ThinkFunc == thinkF_NULL ) // actually you don't need this since I check for it in the next function -slc
|
|
{
|
|
goto runicarus;
|
|
}
|
|
|
|
GEntity_ThinkFunc( ent );
|
|
|
|
runicarus:
|
|
if ( ent->inuse ) // GEntity_ThinkFunc( ent ) can have freed up this ent if it was a type flier_child (stasis1 crash)
|
|
{
|
|
if ( ent->NPC == NULL )
|
|
{
|
|
if ( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus )
|
|
{
|
|
IIcarusInterface::GetIcarus()->Update( ent->m_iIcarusID );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
G_Animate
|
|
-------------------------
|
|
*/
|
|
|
|
static void G_Animate ( gentity_t *self )
|
|
{
|
|
if ( self->s.eFlags & EF_SHADER_ANIM )
|
|
{
|
|
return;
|
|
}
|
|
if ( self->s.frame == self->endFrame )
|
|
{
|
|
if ( self->svFlags & SVF_ANIMATING )
|
|
{
|
|
// ghoul2 requires some extra checks to see if the animation is done since it doesn't set the current frame directly
|
|
if ( self->ghoul2.size() )
|
|
{
|
|
float frame, junk2;
|
|
int junk;
|
|
|
|
// I guess query ghoul2 to find out what the current frame is and see if we are done.
|
|
gi.G2API_GetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone,
|
|
(cg.time?cg.time:level.time), &frame, &junk, &junk, &junk, &junk2, NULL );
|
|
|
|
// It NEVER seems to get to what you'd think the last frame would be, so I'm doing this to try and catch when the animation has stopped
|
|
if ( frame + 1 >= self->endFrame )
|
|
{
|
|
self->svFlags &= ~SVF_ANIMATING;
|
|
Q3_TaskIDComplete( self, TID_ANIM_BOTH );
|
|
}
|
|
}
|
|
else // not ghoul2
|
|
{
|
|
if ( self->loopAnim )
|
|
{
|
|
self->s.frame = self->startFrame;
|
|
}
|
|
else
|
|
{
|
|
self->svFlags &= ~SVF_ANIMATING;
|
|
}
|
|
|
|
//Finished sequence - FIXME: only do this once even on looping anims?
|
|
Q3_TaskIDComplete( self, TID_ANIM_BOTH );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
self->svFlags |= SVF_ANIMATING;
|
|
|
|
// With ghoul2, we'll just set the desired start and end frame and let it do it's thing.
|
|
if ( self->ghoul2.size())
|
|
{
|
|
self->s.frame = self->endFrame;
|
|
|
|
gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone,
|
|
self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, -1, -1 );
|
|
return;
|
|
}
|
|
|
|
if ( self->startFrame < self->endFrame )
|
|
{
|
|
if ( self->s.frame < self->startFrame || self->s.frame > self->endFrame )
|
|
{
|
|
self->s.frame = self->startFrame;
|
|
}
|
|
else
|
|
{
|
|
self->s.frame++;
|
|
}
|
|
}
|
|
else if ( self->startFrame > self->endFrame )
|
|
{
|
|
if ( self->s.frame > self->startFrame || self->s.frame < self->endFrame )
|
|
{
|
|
self->s.frame = self->startFrame;
|
|
}
|
|
else
|
|
{
|
|
self->s.frame--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->s.frame = self->endFrame;
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ResetTeamCounters
|
|
-------------------------
|
|
*/
|
|
|
|
/*
|
|
void ResetTeamCounters( void )
|
|
{
|
|
//clear team enemy counters
|
|
for ( int team = TEAM_FREE; team < TEAM_NUM_TEAMS; team++ )
|
|
{
|
|
teamEnemyCount[team] = 0;
|
|
teamCount[team] = 0;
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
-------------------------
|
|
UpdateTeamCounters
|
|
-------------------------
|
|
*/
|
|
/*
|
|
void UpdateTeamCounters( gentity_t *ent )
|
|
{
|
|
if ( !ent->NPC )
|
|
{
|
|
return;
|
|
}
|
|
if ( !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
if ( ent->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
if ( (ent->s.eFlags&EF_NODRAW) )
|
|
{
|
|
return;
|
|
}
|
|
if ( ent->client->playerTeam == TEAM_FREE )
|
|
{
|
|
return;
|
|
}
|
|
//this is an NPC who is alive and visible and is on a specific team
|
|
|
|
teamCount[ent->client->playerTeam]++;
|
|
if ( !ent->enemy )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//ent has an enemy
|
|
if ( !ent->enemy->client )
|
|
{//enemy is a normal ent
|
|
if ( ent->noDamageTeam == ent->client->playerTeam )
|
|
{//it's on my team, don't count it as an enemy
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{//enemy is another NPC/player
|
|
if ( ent->enemy->client->playerTeam == ent->client->playerTeam)
|
|
{//enemy is on the same team, don't count it as an enemy
|
|
return;
|
|
}
|
|
}
|
|
|
|
//ent's enemy is not on the same team
|
|
teamLastEnemyTime[ent->client->playerTeam] = level.time;
|
|
teamEnemyCount[ent->client->playerTeam]++;
|
|
}
|
|
*/
|
|
void G_PlayerGuiltDeath( void )
|
|
{
|
|
if ( player && player->client )
|
|
{//simulate death
|
|
player->client->ps.stats[STAT_HEALTH] = 0;
|
|
//turn off saber
|
|
if ( player->client->ps.weapon == WP_SABER && player->client->ps.SaberActive() )
|
|
{
|
|
G_SoundIndexOnEnt( player, CHAN_WEAPON, player->client->ps.saber[0].soundOff );
|
|
player->client->ps.SaberDeactivate();
|
|
}
|
|
//play the "what have I done?!" anim
|
|
NPC_SetAnim( player, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
player->client->ps.legsAnimTimer = player->client->ps.torsoAnimTimer = -1;
|
|
//look at yourself
|
|
player->client->ps.stats[STAT_DEAD_YAW] = player->client->ps.viewangles[YAW]+180;
|
|
}
|
|
}
|
|
extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend);
|
|
extern void G_MakeTeamVulnerable( void );
|
|
int killPlayerTimer = 0;
|
|
static void G_CheckEndLevelTimers( gentity_t *ent )
|
|
{
|
|
if ( killPlayerTimer && level.time > killPlayerTimer )
|
|
{
|
|
killPlayerTimer = 0;
|
|
ent->health = 0;
|
|
if ( ent->client && ent->client->ps.stats[STAT_HEALTH] > 0 )
|
|
{
|
|
G_PlayerGuiltDeath();
|
|
//cg.missionStatusShow = qtrue;
|
|
statusTextIndex = MISSIONFAILED_TURNED;
|
|
//debounce respawn time
|
|
ent->client->respawnTime = level.time + 2000;
|
|
//stop all scripts
|
|
stop_icarus = qtrue;
|
|
//make the team killable
|
|
G_MakeTeamVulnerable();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//rww - RAGDOLL_BEGIN
|
|
class CGameRagDollUpdateParams : public CRagDollUpdateParams
|
|
{
|
|
void EffectorCollision(const SRagDollEffectorCollision &data)
|
|
{
|
|
//Com_Printf("Effector Collision at (%f %f %f)\n",data.effectorPosition[0],data.effectorPosition[1],data.effectorPosition[2]);
|
|
vec3_t effectorPosDif;
|
|
|
|
if (data.useTracePlane)
|
|
{
|
|
float magicFactor42=64.0f;
|
|
VectorScale(data.tr.plane.normal,magicFactor42,effectorPosDif);
|
|
}
|
|
else
|
|
{
|
|
gentity_t *thisguy = &g_entities[me];
|
|
|
|
if (thisguy && thisguy->client)
|
|
{
|
|
VectorSubtract(thisguy->client->ps.origin, data.effectorPosition, effectorPosDif);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
VectorAdd(effectorTotal, effectorPosDif, effectorTotal);
|
|
|
|
hasEffectorData = qtrue;
|
|
return;
|
|
}
|
|
void RagDollBegin()
|
|
{
|
|
return;
|
|
}
|
|
virtual void RagDollSettled()
|
|
{
|
|
return;
|
|
}
|
|
void Collision() // we had a collision, please stop animating and (sometime soon) call SetRagDoll RP_DEATH_COLLISION
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void DebugLine(vec3_t p1,vec3_t p2,int color,bool bbox)
|
|
{
|
|
if (!bbox)
|
|
{
|
|
CG_TestLine(p1, p2, 50, color, 1);
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
public:
|
|
vec3_t effectorTotal;
|
|
qboolean hasEffectorData;
|
|
};
|
|
|
|
//list of valid ragdoll effectors
|
|
static const char *g_effectorStringTable[] =
|
|
{ //commented out the ones I don't want dragging to affect
|
|
// "thoracic",
|
|
// "rhand",
|
|
"lhand",
|
|
"rtibia",
|
|
"ltibia",
|
|
"rtalus",
|
|
"ltalus",
|
|
// "rradiusX",
|
|
"lradiusX",
|
|
"rfemurX",
|
|
"lfemurX",
|
|
// "ceyebrow",
|
|
NULL //always terminate
|
|
};
|
|
|
|
extern qboolean G_StandardHumanoid( gentity_t *self );
|
|
extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
|
|
extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
|
|
extern qboolean G_ReleaseEntity( gentity_t *grabber );
|
|
|
|
static void G_BodyDragUpdate(gentity_t *ent, gentity_t *dragger)
|
|
{
|
|
vec3_t handVec;
|
|
float handDist;
|
|
|
|
assert(ent && ent->inuse && ent->client && ent->ghoul2.size() &&
|
|
dragger && dragger->inuse && dragger->client && dragger->ghoul2.size());
|
|
|
|
VectorSubtract(dragger->client->renderInfo.handRPoint, ent->client->renderInfo.torsoPoint, handVec);
|
|
handDist = VectorLength(handVec);
|
|
|
|
if (handDist > 64.0f)
|
|
{
|
|
G_ReleaseEntity(dragger);
|
|
}
|
|
else if (handDist > 12.0f)
|
|
{
|
|
VectorNormalize(handVec);
|
|
VectorScale(handVec, 256.0f, handVec);
|
|
handVec[2] = 0;
|
|
|
|
//VectorAdd(ent->client->ps.velocity, handVec, ent->client->ps.velocity);
|
|
//VectorCopy(handVec, ent->client->ps.velocity);
|
|
ent->client->ps.velocity[0] = handVec[0];
|
|
ent->client->ps.velocity[1] = handVec[1];
|
|
}
|
|
}
|
|
|
|
//we want to see which way the pelvis is facing to get a relatively oriented base settling frame
|
|
//this is to avoid the arms stretching in opposite directions on the body trying to reach the base
|
|
//pose if the pelvis is flipped opposite of the base pose or something -rww
|
|
static int G_RagAnimForPositioning(gentity_t *ent)
|
|
{
|
|
vec3_t dir;
|
|
mdxaBone_t matrix;
|
|
assert(ent->client);
|
|
vec3_t G2Angles = {0, ent->client->ps.viewangles[YAW], 0};
|
|
|
|
assert(ent->ghoul2.size() > 0);
|
|
assert(ent->crotchBolt > -1);
|
|
|
|
gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, ent->crotchBolt, &matrix, G2Angles, ent->client->ps.origin,
|
|
(cg.time?cg.time:level.time), NULL, ent->s.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix(matrix, NEGATIVE_Z, dir);
|
|
|
|
if (dir[2] > 0.1f)
|
|
{ //facing up
|
|
return BOTH_DEADFLOP2;
|
|
}
|
|
else
|
|
{ //facing down
|
|
return BOTH_DEADFLOP1;
|
|
}
|
|
}
|
|
|
|
static inline qboolean G_RagWantsHumanoidsOnly( CGhoul2Info *ghlInfo )
|
|
{
|
|
char *GLAName;
|
|
GLAName = gi.G2API_GetGLAName( ghlInfo );
|
|
assert(GLAName);
|
|
|
|
if ( !Q_stricmp( "models/players/_humanoid/_humanoid", GLAName ) )
|
|
{//only _humanoid skeleton is expected to have these
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//rww - game interface for the ragdoll stuff.
|
|
//Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse.
|
|
//(ported from MP's CG version)
|
|
|
|
qboolean G_RagDoll(gentity_t *ent, vec3_t forcedAngles)
|
|
{
|
|
vec3_t G2Angles;
|
|
vec3_t usedOrg;
|
|
qboolean inSomething = qfalse;
|
|
int ragAnim;
|
|
//int ragVar = gi.Cvar_VariableIntegerValue("broadsword");
|
|
int ragVar = g_broadsword->integer;
|
|
|
|
if (!ragVar)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (!ent ||
|
|
!ent->inuse ||
|
|
!ent->client ||
|
|
ent->health > 0 ||
|
|
ent->client->noRagTime >= level.time ||
|
|
ent->client->noRagTime==-1 ||
|
|
(ent->s.powerups & (1 << PW_DISRUPTION)) ||
|
|
!ent->e_DieFunc ||
|
|
ent->playerModel < 0 ||
|
|
!ent->ghoul2.size() ||
|
|
!G_RagWantsHumanoidsOnly(&ent->ghoul2[ent->playerModel])
|
|
)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
VectorCopy(forcedAngles, G2Angles);
|
|
//forcedAngles[0] = forcedAngles[2] = 0;
|
|
|
|
if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD)
|
|
{
|
|
gentity_t *grabbedBy = &g_entities[ent->client->ps.heldByClient];
|
|
|
|
if (grabbedBy->inuse && grabbedBy->client &&
|
|
grabbedBy->ghoul2.size())
|
|
{
|
|
G_BodyDragUpdate(ent, grabbedBy);
|
|
}
|
|
}
|
|
|
|
//--FIXME: do not go into ragdoll if in a spinning death anim or something, it really messes things up
|
|
//rww 12/17/02 - should be ok now actually with my new stuff
|
|
|
|
VectorCopy(ent->client->ps.origin, usedOrg);
|
|
|
|
if (!ent->client->isRagging)
|
|
{ //If we're not in a ragdoll state, perform the checks.
|
|
if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD)
|
|
{ //want to rag no matter what then
|
|
inSomething = qtrue;
|
|
}
|
|
else if (ent->client->ps.groundEntityNum == ENTITYNUM_NONE)
|
|
{
|
|
vec3_t cVel;
|
|
|
|
VectorCopy(ent->client->ps.velocity, cVel);
|
|
|
|
if (VectorNormalize(cVel) > 400)
|
|
{ //if he's flying through the air at a good enough speed, switch into ragdoll
|
|
inSomething = qtrue;
|
|
}
|
|
}
|
|
|
|
if (ragVar > 1)
|
|
{ //go into rag instantly upon death
|
|
inSomething = qtrue;
|
|
|
|
//also shove them a tad so they don't just collapse onto the floor
|
|
//VectorScale(ent->client->ps.velocity, 1.3f, ent->client->ps.velocity);
|
|
ent->client->ps.velocity[2] += 32;
|
|
}
|
|
|
|
if (!inSomething)
|
|
{
|
|
int i = 0;
|
|
int boltChecks[5];
|
|
vec3_t boltPoints[5];
|
|
vec3_t trStart, trEnd;
|
|
vec3_t tAng;
|
|
//qboolean deathDone = qfalse;
|
|
trace_t tr;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
VectorSet( tAng, 0, ent->client->ps.viewangles[YAW], 0 );
|
|
|
|
if (ent->client->ps.legsAnimTimer <= 0)
|
|
{ //Looks like the death anim is done playing
|
|
//deathDone = qtrue;
|
|
}
|
|
|
|
//if (deathDone)
|
|
if (1)
|
|
{ //only trace from the hands if the death anim is already done.
|
|
boltChecks[0] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rhand");
|
|
boltChecks[1] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lhand");
|
|
}
|
|
else
|
|
{ //otherwise start the trace loop at the cranium.
|
|
i = 2;
|
|
}
|
|
boltChecks[2] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium");
|
|
boltChecks[3] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rtalus");
|
|
boltChecks[4] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "ltalus");
|
|
|
|
//Do the head first, because the hands reference it anyway.
|
|
gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[2], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[2]);
|
|
|
|
while (i < 5)
|
|
{
|
|
if (i < 2)
|
|
{ //when doing hands, trace to the head instead of origin
|
|
gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[i], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[i]);
|
|
VectorCopy(boltPoints[i], trStart);
|
|
VectorCopy(boltPoints[2], trEnd);
|
|
}
|
|
else
|
|
{
|
|
if (i > 2)
|
|
{ //2 is the head, which already has the bolt point.
|
|
gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[i], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale);
|
|
gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[i]);
|
|
}
|
|
VectorCopy(boltPoints[i], trStart);
|
|
VectorCopy(ent->client->ps.origin, trEnd);
|
|
}
|
|
|
|
//Now that we have all that sorted out, trace between the two points we desire.
|
|
gi.trace(&tr, trStart, NULL, NULL, trEnd, ent->s.number, MASK_SOLID, (EG2_Collision)0, 0);
|
|
|
|
if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid)
|
|
{ //Hit something or start in solid, so flag it and break.
|
|
//This is a slight hack, but if we aren't done with the death anim, we don't really want to
|
|
//go into ragdoll unless our body has a relatively "flat" pitch.
|
|
#if 0
|
|
vec3_t vSub;
|
|
|
|
//Check the pitch from the head to the right foot (should be reasonable)
|
|
VectorSubtract(boltPoints[2], boltPoints[3], vSub);
|
|
VectorNormalize(vSub);
|
|
vectoangles(vSub, vSub);
|
|
|
|
if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50))
|
|
{
|
|
inSomething = qtrue;
|
|
}
|
|
#else
|
|
inSomething = qtrue;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (inSomething)
|
|
{
|
|
/*
|
|
PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, 0 );
|
|
PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 );
|
|
|
|
PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,SETANIM_BOTH,ragAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD,
|
|
&ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent,500);
|
|
*/
|
|
ent->client->isRagging = qtrue;
|
|
}
|
|
}
|
|
|
|
if (ent->client->isRagging)
|
|
{ //We're in a ragdoll state, so make the call to keep our positions updated and whatnot.
|
|
CRagDollParams tParms;
|
|
CGameRagDollUpdateParams tuParms;
|
|
|
|
ragAnim = G_RagAnimForPositioning(ent);
|
|
|
|
/*
|
|
if (ent->ikStatus)
|
|
{ //ik must be reset before ragdoll is started, or you'll get some interesting results.
|
|
trap_G2API_SetBoneIKState(cent->ghoul2, cg.time, NULL, IKS_NONE, NULL);
|
|
cent->ikStatus = qfalse;
|
|
}
|
|
*/
|
|
|
|
//these will be used as "base" frames for the ragoll settling.
|
|
tParms.startFrame = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].firstFrame;
|
|
tParms.endFrame = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].firstFrame+level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].numFrames;
|
|
#if 1
|
|
{
|
|
float currentFrame;
|
|
int startFrame, endFrame;
|
|
int flags;
|
|
float animSpeed;
|
|
|
|
if (gi.G2API_GetBoneAnim(&ent->ghoul2[0], "model_root", (cg.time?cg.time:level.time), ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, NULL))
|
|
{ //lock the anim on the current frame.
|
|
int blendTime = 500;
|
|
|
|
gi.G2API_SetBoneAnim(&ent->ghoul2[0], "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,(cg.time?cg.time:level.time), currentFrame, blendTime);
|
|
gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", currentFrame, currentFrame+1, flags, animSpeed, (cg.time?cg.time:level.time), currentFrame, blendTime);
|
|
gi.G2API_SetBoneAnim(&ent->ghoul2[0], "Motion", currentFrame, currentFrame+1, flags, animSpeed, (cg.time?cg.time:level.time), currentFrame, blendTime);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) );
|
|
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) );
|
|
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) );
|
|
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) );
|
|
|
|
VectorCopy(G2Angles, tParms.angles);
|
|
VectorCopy(usedOrg, tParms.position);
|
|
VectorCopy(ent->s.modelScale, tParms.scale);
|
|
tParms.me = ent->s.number;
|
|
tParms.groundEnt = ent->client->ps.groundEntityNum;
|
|
|
|
tParms.collisionType = 1;
|
|
tParms.RagPhase=CRagDollParams::RP_DEATH_COLLISION;
|
|
tParms.fShotStrength = 4;
|
|
|
|
gi.G2API_SetRagDoll(ent->ghoul2, &tParms);
|
|
|
|
|
|
tuParms.hasEffectorData = qfalse;
|
|
VectorClear(tuParms.effectorTotal);
|
|
|
|
VectorCopy(G2Angles, tuParms.angles);
|
|
VectorCopy(usedOrg, tuParms.position);
|
|
VectorCopy(ent->s.modelScale, tuParms.scale);
|
|
tuParms.me = ent->s.number;
|
|
tuParms.settleFrame = tParms.endFrame-1;
|
|
tuParms.groundEnt = ent->client->ps.groundEntityNum;
|
|
|
|
if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
|
|
{
|
|
VectorClear(tuParms.velocity);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(ent->client->ps.velocity, 0.4f, tuParms.velocity);
|
|
}
|
|
|
|
gi.G2API_AnimateG2Models(ent->ghoul2, (cg.time?cg.time:level.time), &tuParms);
|
|
|
|
if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD)
|
|
{
|
|
gentity_t *grabEnt;
|
|
|
|
grabEnt = &g_entities[ent->client->ps.heldByClient];
|
|
|
|
if (grabEnt->client && grabEnt->ghoul2.size())
|
|
{
|
|
vec3_t bOrg;
|
|
vec3_t thisHand;
|
|
vec3_t hands;
|
|
vec3_t pcjMin, pcjMax;
|
|
vec3_t pDif;
|
|
vec3_t thorPoint;
|
|
float difLen;
|
|
|
|
//Get the person who is holding our hand's hand location
|
|
//gi.G2API_GetBoltMatrix(grabEnt->ghoul2, 0, grabEnt->gent->client->renderInfo.handRPoint, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin,
|
|
// cg.time, cgs.gameModels, grabEnt->modelScale);
|
|
//BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg);
|
|
VectorCopy(grabEnt->client->renderInfo.handRPoint, bOrg);
|
|
|
|
//Get our hand's location
|
|
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin,
|
|
// cg.time, cgs.gameModels, cent->modelScale);
|
|
//BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand);
|
|
VectorCopy(ent->client->renderInfo.handRPoint, thisHand);
|
|
|
|
//Get the position of the thoracic bone for hinting its velocity later on
|
|
//thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic");
|
|
//trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin,
|
|
// cg.time, cgs.gameModels, cent->modelScale);
|
|
//BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint);
|
|
VectorCopy(ent->client->renderInfo.torsoPoint, thorPoint);
|
|
|
|
VectorSubtract(bOrg, thisHand, hands);
|
|
|
|
if (VectorLength(hands) < 3.0f)
|
|
{
|
|
gi.G2API_RagForceSolve(ent->ghoul2, qfalse);
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_RagForceSolve(ent->ghoul2, qtrue);
|
|
}
|
|
|
|
//got the hand pos of him, now we want to make our hand go to it
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhand", bOrg);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rradius", bOrg);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rradiusX", bOrg);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerusX", bOrg);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerus", bOrg);
|
|
|
|
//Make these two solve quickly so we can update decently
|
|
gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rhumerus", 1.5f);
|
|
gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rradius", 1.5f);
|
|
|
|
//Break the constraints on them I suppose
|
|
VectorSet(pcjMin, -999, -999, -999);
|
|
VectorSet(pcjMax, 999, 999, 999);
|
|
gi.G2API_RagPCJConstraint(ent->ghoul2, "rhumerus", pcjMin, pcjMax);
|
|
gi.G2API_RagPCJConstraint(ent->ghoul2, "rradius", pcjMin, pcjMax);
|
|
|
|
ent->client->overridingBones = level.time + 2000;
|
|
|
|
//hit the thoracic velocity to the hand point
|
|
VectorSubtract(bOrg, thorPoint, hands);
|
|
VectorNormalize(hands);
|
|
VectorScale(hands, 2048.0f, hands);
|
|
gi.G2API_RagEffectorKick(ent->ghoul2, "thoracic", hands);
|
|
gi.G2API_RagEffectorKick(ent->ghoul2, "ceyebrow", hands);
|
|
|
|
VectorSubtract(ent->client->ragLastOrigin, ent->client->ps.origin, pDif);
|
|
VectorCopy(ent->client->ps.origin, ent->client->ragLastOrigin);
|
|
|
|
if (ent->client->ragLastOriginTime >= level.time && ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
|
|
{ //make sure it's reasonably updated
|
|
difLen = VectorLength(pDif);
|
|
if (difLen > 0.0f)
|
|
{ //if we're being dragged, then kick all the bones around a bit
|
|
vec3_t dVel;
|
|
vec3_t rVel;
|
|
int i = 0;
|
|
|
|
if (difLen < 12.0f)
|
|
{
|
|
VectorScale(pDif, 12.0f/difLen, pDif);
|
|
difLen = 12.0f;
|
|
}
|
|
|
|
while (g_effectorStringTable[i])
|
|
{
|
|
VectorCopy(pDif, dVel);
|
|
dVel[2] = 0;
|
|
|
|
//Factor in a random velocity
|
|
VectorSet(rVel, Q_flrand(-0.1f, 0.1f), Q_flrand(-0.1f, 0.1f), Q_flrand(0.1f, 0.5));
|
|
VectorScale(rVel, 8.0f, rVel);
|
|
|
|
VectorAdd(dVel, rVel, dVel);
|
|
VectorScale(dVel, 10.0f, dVel);
|
|
|
|
gi.G2API_RagEffectorKick(ent->ghoul2, g_effectorStringTable[i], dVel);
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
ent->client->ragLastOriginTime = level.time + 1000;
|
|
}
|
|
}
|
|
else if (ent->client->overridingBones)
|
|
{ //reset things to their normal rag state
|
|
vec3_t pcjMin, pcjMax;
|
|
vec3_t dVel;
|
|
|
|
//got the hand pos of him, now we want to make our hand go to it
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhand", NULL);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rradius", NULL);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rradiusX", NULL);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerusX", NULL);
|
|
gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerus", NULL);
|
|
|
|
VectorSet(dVel, 0.0f, 0.0f, -64.0f);
|
|
gi.G2API_RagEffectorKick(ent->ghoul2, "rhand", dVel);
|
|
|
|
gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rhumerus", 0.0f);
|
|
gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rradius", 0.0f);
|
|
|
|
VectorSet(pcjMin,-100.0f,-40.0f,-15.0f);
|
|
VectorSet(pcjMax,-15.0f,80.0f,15.0f);
|
|
gi.G2API_RagPCJConstraint(ent->ghoul2, "rhumerus", pcjMin, pcjMax);
|
|
|
|
VectorSet(pcjMin,-25.0f,-20.0f,-20.0f);
|
|
VectorSet(pcjMax,90.0f,20.0f,-20.0f);
|
|
gi.G2API_RagPCJConstraint(ent->ghoul2, "rradius", pcjMin, pcjMax);
|
|
|
|
if (ent->client->overridingBones < level.time)
|
|
{
|
|
gi.G2API_RagForceSolve(ent->ghoul2, qfalse);
|
|
ent->client->overridingBones = 0;
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_RagForceSolve(ent->ghoul2, qtrue);
|
|
}
|
|
}
|
|
|
|
if (tuParms.hasEffectorData)
|
|
{
|
|
/*
|
|
vec3_t efDir;
|
|
vec3_t existingVelocity;
|
|
float evValue = 0;
|
|
|
|
VectorCopy(tuParms.effectorTotal, efDir);
|
|
VectorNormalize(efDir);
|
|
VectorCopy(ent->client->ps.velocity, existingVelocity);
|
|
evValue = VectorNormalize(existingVelocity);
|
|
|
|
if (evValue < 32)
|
|
{
|
|
ent->client->ps.velocity[0] += efDir[0]*64;
|
|
ent->client->ps.velocity[1] += efDir[1]*64;
|
|
ent->client->ps.velocity[2] += efDir[2]*64;
|
|
}
|
|
*/
|
|
|
|
VectorNormalize(tuParms.effectorTotal);
|
|
VectorScale(tuParms.effectorTotal, 7.0f, tuParms.effectorTotal);
|
|
|
|
VectorAdd(ent->client->ps.velocity, tuParms.effectorTotal, ent->client->ps.velocity);
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
//rww - RAGDOLL_END
|
|
|
|
|
|
/*
|
|
================
|
|
G_RunFrame
|
|
|
|
Advances the non-player objects in the world
|
|
================
|
|
*/
|
|
#if AI_TIMERS
|
|
int AITime = 0;
|
|
int navTime = 0;
|
|
#endif// AI_TIMERS
|
|
|
|
|
|
void G_RunFrame( int levelTime ) {
|
|
int i;
|
|
gentity_t *ent;
|
|
int ents_inuse=0; // someone's gonna be pissed I put this here...
|
|
#if AI_TIMERS
|
|
AITime = 0;
|
|
navTime = 0;
|
|
#endif// AI_TIMERS
|
|
|
|
level.framenum++;
|
|
level.previousTime = level.time;
|
|
level.time = levelTime;
|
|
|
|
//ResetTeamCounters();
|
|
NAV::DecayDangerSenses();
|
|
Rail_Update();
|
|
Troop_Update();
|
|
Pilot_Update();
|
|
|
|
|
|
if (player && gi.WE_IsShaking(player->currentOrigin))
|
|
{
|
|
CGCam_Shake(0.45f, 100);
|
|
}
|
|
|
|
|
|
AI_UpdateGroups();
|
|
|
|
|
|
|
|
|
|
//Look to clear out old events
|
|
ClearPlayerAlertEvents();
|
|
|
|
//Run the frame for all entities
|
|
// for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++)
|
|
for ( i = 0; i < globals.num_entities ; i++)
|
|
{
|
|
// if ( !ent->inuse )
|
|
// continue;
|
|
|
|
if(!PInUse(i))
|
|
continue;
|
|
ents_inuse++;
|
|
ent = &g_entities[i];
|
|
|
|
// clear events that are too old
|
|
if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
|
|
if ( ent->s.event ) {
|
|
ent->s.event = 0; // &= EV_EVENT_BITS;
|
|
if ( ent->client ) {
|
|
ent->client->ps.externalEvent = 0;
|
|
}
|
|
}
|
|
if ( ent->freeAfterEvent ) {
|
|
// tempEntities or dropped items completely go away after their event
|
|
G_FreeEntity( ent );
|
|
continue;
|
|
}
|
|
/* // This is never set to true anywhere. Killing the field (BTO - VV)
|
|
else if ( ent->unlinkAfterEvent ) {
|
|
// items that will respawn will hide themselves after their pickup event
|
|
ent->unlinkAfterEvent = qfalse;
|
|
gi.unlinkentity( ent );
|
|
}
|
|
*/
|
|
}
|
|
|
|
// temporary entities don't think
|
|
if ( ent->freeAfterEvent )
|
|
continue;
|
|
|
|
G_CheckTasksCompleted(ent);
|
|
|
|
G_Roff( ent );
|
|
|
|
if( !ent->client )
|
|
{
|
|
if ( !(ent->svFlags & SVF_SELF_ANIMATING) )
|
|
{//FIXME: make sure this is done only for models with frames?
|
|
//Or just flag as animating?
|
|
if ( ent->s.eFlags & EF_ANIM_ONCE )
|
|
{
|
|
ent->s.frame++;
|
|
}
|
|
else if ( !(ent->s.eFlags & EF_ANIM_ALLFAST) )
|
|
{
|
|
G_Animate( ent );
|
|
}
|
|
}
|
|
}
|
|
G_CheckSpecialPersistentEvents( ent );
|
|
|
|
if ( ent->s.eType == ET_MISSILE )
|
|
{
|
|
G_RunMissile( ent );
|
|
continue;
|
|
}
|
|
|
|
if ( ent->s.eType == ET_ITEM )
|
|
{
|
|
G_RunItem( ent );
|
|
continue;
|
|
}
|
|
|
|
if ( ent->s.eType == ET_MOVER )
|
|
{
|
|
// FIXME string comparison in per-frame thinks wut???
|
|
if ( ent->model && Q_stricmp( "models/test/mikeg/tie_fighter.md3", ent->model ) == 0 )
|
|
{
|
|
TieFighterThink( ent );
|
|
}
|
|
G_RunMover( ent );
|
|
continue;
|
|
}
|
|
|
|
//The player
|
|
if ( i == 0 )
|
|
{
|
|
// decay batteries if the goggles are active
|
|
if ( cg.zoomMode == 1 && ent->client->ps.batteryCharge > 0 )
|
|
{
|
|
ent->client->ps.batteryCharge--;
|
|
}
|
|
else if ( cg.zoomMode == 3 && ent->client->ps.batteryCharge > 0 )
|
|
{
|
|
ent->client->ps.batteryCharge -= 2;
|
|
|
|
if ( ent->client->ps.batteryCharge < 0 )
|
|
{
|
|
ent->client->ps.batteryCharge = 0;
|
|
}
|
|
}
|
|
|
|
G_CheckEndLevelTimers( ent );
|
|
//Recalculate the nearest waypoint for the coming NPC updates
|
|
NAV::GetNearestNode( ent );
|
|
|
|
|
|
if( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus )
|
|
{
|
|
IIcarusInterface::GetIcarus()->Update( ent->m_iIcarusID );
|
|
}
|
|
//dead
|
|
if ( ent->health <= 0 )
|
|
{
|
|
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
{//on the ground
|
|
pitch_roll_for_slope( ent );
|
|
}
|
|
}
|
|
|
|
continue; // players are ucmd driven
|
|
}
|
|
|
|
G_RunThink( ent ); // be aware that ent may be free after returning from here, at least one func frees them
|
|
ClearNPCGlobals(); // but these 2 funcs are ok
|
|
//UpdateTeamCounters( ent ); // to call anyway on a freed ent.
|
|
}
|
|
|
|
// perform final fixups on the player
|
|
ent = &g_entities[0];
|
|
if ( ent->inuse )
|
|
{
|
|
ClientEndFrame( ent );
|
|
}
|
|
if( g_numEntities->integer )
|
|
{
|
|
gi.Printf( S_COLOR_WHITE"Number of Entities in use : %d\n", ents_inuse );
|
|
}
|
|
//DEBUG STUFF
|
|
NAV::ShowDebugInfo(ent->currentOrigin, ent->waypoint);
|
|
NPC_ShowDebugInfo();
|
|
|
|
G_DynamicMusicUpdate();
|
|
|
|
#if AI_TIMERS
|
|
AITime -= navTime;
|
|
if ( AITime > 20 )
|
|
{
|
|
gi.Printf( S_COLOR_RED"ERROR: total AI time: %d\n", AITime );
|
|
}
|
|
else if ( AITime > 10 )
|
|
{
|
|
gi.Printf( S_COLOR_YELLOW"WARNING: total AI time: %d\n", AITime );
|
|
}
|
|
else if ( AITime > 2 )
|
|
{
|
|
gi.Printf( S_COLOR_GREEN"total AI time: %d\n", AITime );
|
|
}
|
|
if ( navTime > 20 )
|
|
{
|
|
gi.Printf( S_COLOR_RED"ERROR: total nav time: %d\n", navTime );
|
|
}
|
|
else if ( navTime > 10 )
|
|
{
|
|
gi.Printf( S_COLOR_YELLOW"WARNING: total nav time: %d\n", navTime );
|
|
}
|
|
else if ( navTime > 2 )
|
|
{
|
|
gi.Printf( S_COLOR_GREEN"total nav time: %d\n", navTime );
|
|
}
|
|
#endif// AI_TIMERS
|
|
|
|
extern int delayedShutDown;
|
|
if ( g_delayedShutdown->integer && delayedShutDown != 0 && delayedShutDown < level.time )
|
|
{
|
|
assert(0);
|
|
G_Error( "Game Errors. Scroll up the console to read them.\n" );
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if(!(level.framenum&0xff))
|
|
{
|
|
ValidateInUseBits();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
extern qboolean player_locked;
|
|
|
|
void G_LoadSave_WriteMiscData()
|
|
{
|
|
ojk::SavedGameHelper saved_game(
|
|
::gi.saved_game);
|
|
|
|
saved_game.write_chunk<int32_t>(
|
|
INT_ID('L', 'C', 'K', 'D'),
|
|
::player_locked);
|
|
}
|
|
|
|
|
|
|
|
void G_LoadSave_ReadMiscData()
|
|
{
|
|
ojk::SavedGameHelper saved_game(
|
|
::gi.saved_game);
|
|
|
|
saved_game.read_chunk<int32_t>(
|
|
INT_ID('L', 'C', 'K', 'D'),
|
|
::player_locked);
|
|
}
|
|
|
|
|
|
/*
|
|
void PrintEntClassname( int gentNum )
|
|
{
|
|
Com_Printf( "%d: %s in snapshot\n", gentNum, g_entities[gentNum].classname );
|
|
}
|
|
*/
|
|
IGhoul2InfoArray &TheGameGhoul2InfoArray()
|
|
{
|
|
return gi.TheGhoul2InfoArray();
|
|
}
|