sof2-sdk/code/game/bg_player.c
2002-07-15 00:00:00 +00:00

1746 lines
38 KiB
C

// Copyright (C) 2001-2002 Raven Software.
//
// BG_Player.c
#include "q_shared.h"
#include "bg_public.h"
#include "bg_local.h"
#include "..\ghoul2\g2.h"
#include "../cgame/animtable.h"
#ifdef QAGAME
#include "g_local.h"
#endif
#ifdef UI_EXPORTS
#include "../ui/ui_local.h"
#endif
#ifndef UI_EXPORTS
#ifndef QAGAME
#include "../cgame/cg_local.h"
#endif
#endif
TCharacterTemplate *bg_characterTemplates = NULL;
TItemTemplate *bg_itemTemplates = NULL;
int bg_identityCount = 0;
TIdentity bg_identities[MAX_IDENTITIES];
goutfitting_t bg_outfittings[MAX_OUTFITTINGS];
int bg_outfittingCount = 0;
char bg_availableOutfitting[WP_NUM_WEAPONS] = {-1};
int bg_outfittingGroups[OUTFITTING_GROUP_MAX][MAX_OUTFITTING_GROUPITEM] =
{
{ MODELINDEX_WEAPON_AK74, MODELINDEX_WEAPON_M4, MODELINDEX_WEAPON_USAS12, MODELINDEX_WEAPON_MSG90A1, MODELINDEX_WEAPON_M60, MODELINDEX_WEAPON_MP5, MODELINDEX_WEAPON_RPG7, MODELINDEX_WEAPON_MM1, -1, -1 },
{ MODELINDEX_WEAPON_M590, MODELINDEX_WEAPON_MICROUZI, MODELINDEX_WEAPON_M3A1, -1, -1, -1, -1, -1, -1, - 1 },
{ MODELINDEX_WEAPON_M19, MODELINDEX_WEAPON_SOCOM, -1, -1, -1, -1, -1, -1, -1, -1 },
{ MODELINDEX_WEAPON_SMOHG92, MODELINDEX_WEAPON_M84, MODELINDEX_WEAPON_M15, MODELINDEX_WEAPON_ANM14, -1, -1, -1, -1, -1, -1 },
{ MODELINDEX_ARMOR, MODELINDEX_NIGHTVISION, MODELINDEX_THERMAL, -1, -1, -1, -1, -1, -1, -1 },
};
/*
===================
PM_StartLegsAnim
Starts a new leg animation for the given playerstate
===================
*/
static void PM_StartLegsAnim( playerState_t* ps, int anim )
{
if ( ps->pm_type >= PM_DEAD )
{
return;
}
ps->legsAnim = ( ( ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
}
/*
===================
PM_ContinueLegsAnim
Continues running the given leg animation, if its a new animation then it is started
===================
*/
void PM_ContinueLegsAnim( playerState_t* ps, int anim )
{
if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim )
{
return;
}
PM_StartLegsAnim( ps, anim );
}
/*
===================
PM_ForceLegsAnim
===================
*/
void PM_ForceLegsAnim( playerState_t* ps, int anim)
{
PM_StartLegsAnim( ps, anim );
}
/*
===================
PM_StartTorsoAnim
===================
*/
void PM_StartTorsoAnim( playerState_t* ps, int anim, int time )
{
if ( anim == -1 )
{
return;
}
if ( ps->pm_type >= PM_DEAD )
{
return;
}
ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
ps->torsoTimer = time;
}
/*
===================
PM_ContinueTorsoAnim
Continues running the given torso animation, if its a new animation then it is started
===================
*/
static void PM_ContinueTorsoAnim( playerState_t* ps, int anim )
{
if ( anim == -1 )
{
return;
}
if ( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim )
{
return;
}
PM_StartTorsoAnim( ps, anim, 0 );
}
/*
==============
PM_TorsoAnimation
Sets the current torso animation based on the given playerstate
==============
*/
void PM_TorsoAnimation( playerState_t* ps )
{
switch ( ps->weaponstate )
{
case WEAPON_SPAWNING:
case WEAPON_READY:
if ( ps->stats[STAT_USEWEAPONDROP] )
{
PM_ContinueTorsoAnim ( ps, TORSO_USE );
}
else if ( (ps->pm_flags & PMF_ZOOMED) && weaponData[ps->weapon].animIdleZoomed )
{
PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdleZoomed );
}
else
{
PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdle );
}
break;
}
}
/*
=================
BG_ParseInventory
Parses the inventory items indicated in the given group and returns
a linked list containing the results
=================
*/
TInventoryTemplate *BG_ParseInventory ( TGPGroup group )
{
TInventoryTemplate *top, *inv;
TGPGroup subGroup;
char temp[1024];
// Convienience for handling no items
if (!group)
{
return 0;
}
top = NULL;
// Parse each of the inventory items
subGroup = trap_GPG_GetSubGroups( group );
while(subGroup)
{
trap_GPG_GetName ( subGroup, temp );
// If the group name isnt item then 'Item' then its not an inventory item
if ( Q_stricmp ( temp, "Item") == 0)
{
inv = (TInventoryTemplate *)trap_VM_LocalAlloc ( sizeof(*inv));
// Name of the item
trap_GPG_FindPairValue ( subGroup, "Name||Name1", "", temp );
inv->mName = trap_VM_LocalStringAlloc ( temp );
// Bolt for the item
trap_GPG_FindPairValue ( subGroup, "Bolt", "", temp );
inv->mBolt = trap_VM_LocalStringAlloc ( temp );
trap_GPG_FindPairValue ( subGroup, "mp_onback||onback", "no", temp );
if ( !Q_stricmp ( temp, "yes" ) )
{
inv->mOnBack = qtrue;
}
inv->mNext = top;
top = inv;
}
// Move to the next group
subGroup = trap_GPG_GetNext ( subGroup );
}
return top;
}
/*
=================
BG_ParseSkins
Parses the skins contained in the given group and returns a linked
list with the results
=================
*/
TSkinTemplate *BG_ParseSkins( TCharacterTemplate* character, TGPGroup group )
{
TSkinTemplate *skin;
TIdentity *identity;
TGPGroup subGroup;
char temp[1024];
fileHandle_t f;
#ifndef SPECIAL_PRE_CACHE
int len;
#endif
qboolean validSkin;
character->mSkins = NULL;
// Parse the skin file first
trap_GPG_FindPairValue ( group, "SkinFile", "", temp );
if ( temp[0] )
{
skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) );
skin->mSkin = trap_VM_LocalStringAlloc ( temp );
skin->mNext = character->mSkins;
character->mSkins = skin;
}
// Now parse all the skin groups
subGroup = trap_GPG_GetSubGroups ( group );
while(subGroup)
{
trap_GPG_GetName ( subGroup, temp );
// If the groups name isnt 'Skin' then skip it
if ( Q_stricmp( temp, "Skin") == 0 )
{
// Allocate memory for the skin
skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) );
// Grab the skin filename
trap_GPG_FindPairValue ( subGroup, "File", "", temp );
skin->mSkin = trap_VM_LocalStringAlloc ( temp );
#ifdef SPECIAL_PRE_CACHE
f = 0;
validSkin = qtrue;
#else
Com_sprintf( temp, sizeof(temp), "models/characters/skins/%s.g2skin", skin->mSkin );
len = trap_FS_FOpenFile( temp, &f, FS_READ );
if (f != 0)
{
trap_FS_FCloseFile(f);
validSkin = qtrue;
}
else
{
validSkin = qfalse;
}
#endif
// Parse the inventory for the skin
skin->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup ( subGroup, "Inventory") );
// Link the skin into the skin linked list
skin->mNext = character->mSkins;
character->mSkins = skin;
// If the character isnt deathmatch then dont add it to the
// identity list
if ( character->mDeathmatch && validSkin)
{
// Allocate a new identity
identity = &bg_identities[bg_identityCount++];
identity->mCharacter = character;
identity->mSkin = skin;
trap_GPG_FindPairValue ( subGroup, "mp_identity", "", temp );
if ( !temp[0] )
{
identity->mName = trap_VM_LocalStringAlloc ( va("%s/%s", character->mName, skin->mSkin ) );
}
else
{
identity->mName = trap_VM_LocalStringAlloc ( temp );
}
// Team name?
trap_GPG_FindPairValue ( subGroup, "mp_team", "", temp );
if ( temp[0] )
{
identity->mTeam = trap_VM_LocalStringAlloc ( temp );
}
else
{
identity->mTeam = "";
}
}
}
// Move to the next sub group in the parsers list
subGroup = trap_GPG_GetNext ( subGroup );
}
return character->mSkins;
}
/*
=================
BG_ParseModelSounds
Parses the model sounds for the given group and returns a linked
list with the results
=================
*/
TModelSounds *BG_ParseModelSounds( TGPGroup group )
{
TModelSounds *top;
TModelSounds *sounds;
TGPGroup pairs;
char temp[1024];
// Convienience
if ( !group )
{
return NULL;
}
top = NULL;
// Now parse all the skin groups
pairs = trap_GPG_GetPairs ( group );
while(pairs)
{
// Allocate memory for the sounds
sounds = (TModelSounds*) trap_VM_LocalAlloc ( sizeof(*sounds) );
sounds->mNext = top;
top = sounds;
// Grab the sounds name
trap_GPV_GetName ( pairs, temp );
sounds->mName = trap_VM_LocalStringAlloc ( temp );
// Start with no sounds
sounds->mCount = 0;
// Should be a list
if ( trap_GPV_IsList ( pairs ) )
{
TGPValue list;
// Run through the list
list = trap_GPV_GetList ( pairs );
while ( list && sounds->mCount < MAX_MODEL_SOUNDS )
{
// Add the sound to the list
trap_GPV_GetName ( list, temp );
sounds->mSounds[sounds->mCount++] = trap_VM_LocalStringAlloc ( temp );
list = trap_GPV_GetNext ( list );
}
}
// Move to the next sound set in the parsers list
pairs = trap_GPV_GetNext ( pairs );
}
return top;
}
/*
=================
BG_ParseItemFile
Parses the item file. The item file contains a list of all of the
items that can be used as inventory items for a given skin
=================
*/
qboolean BG_ParseItemFile ( void )
{
TGPGroup *baseGroup, *subGroup;
TGPValue *pairs;
TItemTemplate *item;
TSurfaceList *surf;
char temp[1024];
TGenericParser2 ItemFile;
// Create the generic parser so the item file can be parsed
ItemFile = trap_GP_ParseFile( "ext_data/sof2.item", qtrue, qfalse );
if ( !ItemFile )
{
return qfalse;
}
baseGroup = trap_GP_GetBaseParseGroup ( ItemFile );
subGroup = trap_GPG_GetSubGroups ( baseGroup );
while(subGroup)
{
trap_GPG_GetName ( subGroup, temp );
if (Q_stricmp( temp, "item") == 0)
{
// Is this item used for deathmatch?
trap_GPG_FindPairValue ( subGroup, "Deathmatch", "yes", temp );
if (Q_stricmp( temp, "no") == 0)
{
subGroup = trap_GPG_GetNext ( subGroup );
continue;
}
// Allocate the item template and link it up to the item list
item = (TItemTemplate *) trap_VM_LocalAlloc ( sizeof(*item) );
item->mNext = bg_itemTemplates;
bg_itemTemplates = item;
// Name of the item
trap_GPG_FindPairValue ( subGroup, "Name", "", temp );
item->mName = trap_VM_LocalStringAlloc ( temp );
// Model for the item
trap_GPG_FindPairValue ( subGroup, "Model", "", temp );
if ( temp[0] )
{
item->mModel = trap_VM_LocalStringAlloc ( temp );
}
pairs = trap_GPG_GetPairs ( subGroup );
while(pairs)
{
trap_GPV_GetName ( pairs, temp );
// Surface off?
if ( Q_stricmpn ( temp, "offsurf", 7) == 0)
{
// Allocate the surface structure and link it into the list
surf = (TSurfaceList *) trap_VM_LocalAlloc ( sizeof(*surf) );
surf->mNext = item->mOffList;
item->mOffList = surf;
// Name of the surface to turn off
trap_GPV_GetTopValue ( pairs, temp );
surf->mName = trap_VM_LocalStringAlloc ( temp );
}
// Surface on?
else if ( Q_stricmpn( temp, "onsurf", 6) == 0)
{
// Allocate the surface structure and link it into the list
surf = (TSurfaceList *) trap_VM_LocalAlloc(sizeof(*surf));
surf->mNext = item->mOnList;
item->mOnList = surf;
// Name of the surface to turn off
trap_GPV_GetTopValue ( pairs, temp );
surf->mName = trap_VM_LocalStringAlloc ( temp );
}
// Next pairs
pairs = trap_GPV_GetNext ( pairs );
}
}
// Next group
subGroup = trap_GPG_GetNext ( subGroup );
}
trap_GP_Delete(&ItemFile);
return qtrue;
}
/*
=================
BG_FindCharacterTemplate
Finds a character template the matches the given name or NULL if
a match could not be found
=================
*/
TCharacterTemplate *BG_FindCharacterTemplate (const char *name)
{
TCharacterTemplate *current;
// Convienience
if (!name)
{
return NULL;
}
// Linear search through all of the parsed templates
current = bg_characterTemplates;
while(current)
{
if (Q_stricmp(name, current->mName) == 0)
{
return current;
}
current = current->mNext;
}
// None found
return NULL;
}
/*
=================
BG_FindItemTemplate
Finds an item template the matches the given name or NULL if a match
could not be found
=================
*/
TItemTemplate *BG_FindItemTemplate(const char *name)
{
TItemTemplate *current;
// Convienience
if (!name)
{
return NULL;
}
// Linear search through all of the parsed items
current = bg_itemTemplates;
while(current)
{
if (Q_stricmp(name, current->mName) == 0)
{
return current;
}
current = current->mNext;
}
return NULL;
}
/*
=================
BG_LinkTemplates
Cross links the various templates
=================
*/
static void BG_LinkTemplates(void)
{
TCharacterTemplate *current;
TInventoryTemplate *inv;
TSkinTemplate *skin;
current = bg_characterTemplates;
while(current)
{
// If this template has a parent then find it and link it up as
// its parent. Ensure that the parent doesnt link back to itself
current->mParent = BG_FindCharacterTemplate(current->mParentName);
if (current->mParent == current)
{
current->mParent = NULL;
}
// Bring over any parent items
else if ( current->mParent )
{
// No model, bring over the parents.
if ( !current->mModel )
{
current->mModel = current->mParent->mModel;
}
}
// Link up all the inventory for this character
inv = current->mInventory;
while(inv)
{
inv->mItem = BG_FindItemTemplate(inv->mName);
inv = inv->mNext;
}
// Link up all the skins for this character
skin = current->mSkins;
while(skin)
{
// Link up all the inventory items specific to the skins
inv = skin->mInventory;
while(inv)
{
inv->mItem = BG_FindItemTemplate(inv->mName);
inv = inv->mNext;
}
skin = skin->mNext;
}
// Move on to the next character template
current = current->mNext;
}
}
/*
=================
BG_ParseNPCFiles
Parses all the the .npc files in the npc directory and
stores their info into global lists.
=================
*/
qboolean BG_ParseNPCFiles ( void )
{
int i, numNPCFiles, filelen;
TGPGroup baseGroup, subGroup;
TGenericParser2 NPCFile;
const char *currentParent = 0;
TCharacterTemplate *newTemplate;
char fileName[MAX_QPATH];
char NPCFiles[4096];
char temp[1024];
char *fileptr;
// Clear the current list
bg_characterTemplates = NULL;
// Grab the list of NPC files
numNPCFiles = trap_FS_GetFileList("NPCs", ".npc", NPCFiles, 4096 );
if ( !numNPCFiles )
{
return qfalse;
}
// Parse each of the NPC files
fileptr = NPCFiles;
for( i=0; i<numNPCFiles; i++, fileptr += filelen+1 )
{
// Grab the length so we can skip this file later
filelen = strlen(fileptr);
Com_sprintf(fileName, sizeof(fileName),"NPCs/%s", fileptr );
// Create the generic parser so the item file can be parsed
NPCFile = trap_GP_ParseFile( fileName, qtrue, qfalse );
if ( !NPCFile )
{
continue;
}
baseGroup = trap_GP_GetBaseParseGroup ( NPCFile );
subGroup = trap_GPG_GetSubGroups ( baseGroup );
while(subGroup)
{
trap_GPG_GetName ( subGroup, temp );
// Look for a parent template if this is the group info
if ( Q_stricmp( temp, "GroupInfo") == 0)
{
currentParent = NULL;
// Is there a parent template?
trap_GPG_FindPairValue ( subGroup, "ParentTemplate", "", temp );
if ( temp[0] )
{
currentParent = trap_VM_LocalStringAlloc ( temp );
}
}
// A new character template
else if ( Q_stricmp( temp, "CharacterTemplate") == 0)
{
// Allocate the new template and link it into the global list.
newTemplate = (TCharacterTemplate *)trap_VM_LocalAlloc (sizeof(*newTemplate));
newTemplate->mNext = bg_characterTemplates;
bg_characterTemplates = newTemplate;
// Exclude from deathmatch?
trap_GPG_FindPairValue ( subGroup, "DeathMatch", "yes", temp );
if ( Q_stricmp( temp, "no") == 0)
{
newTemplate->mDeathmatch = qfalse;
}
else
{
newTemplate->mDeathmatch = qtrue;
}
// Template name
trap_GPG_FindPairValue ( subGroup, "Name", "", temp );
if ( temp[0] )
{
newTemplate->mName = trap_VM_LocalStringAlloc ( temp );
}
// Template formal name
trap_GPG_FindPairValue ( subGroup, "FormalName", "", temp );
if ( temp[0] )
{
newTemplate->mFormalName = trap_VM_LocalStringAlloc ( temp );
}
// Template model
trap_GPG_FindPairValue ( subGroup, "Model", "", temp );
if ( temp[0] )
{
newTemplate->mModel = trap_VM_LocalStringAlloc ( temp );
}
// Use the current parent
newTemplate->mParentName = currentParent;
// Parse inventory for this character template
newTemplate->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup( subGroup, "Inventory" ));
// Parse the skins for this character template
BG_ParseSkins( newTemplate, subGroup);
// Parse the sounds for this character template
newTemplate->mSounds = BG_ParseModelSounds ( trap_GPG_FindSubGroup ( subGroup, "MPSounds" ) );
}
// Move to the next group
subGroup = trap_GPG_GetNext ( subGroup );
}
trap_GP_Delete(&NPCFile);
}
// Parse the item file
BG_ParseItemFile();
// Link up all the templates
BG_LinkTemplates();
return qtrue;
}
/*
=================
BG_GetModelSoundsGroup
Returns the group of sounds for the given model and sound group combination, if the
sound group couldnt be found NULL is returned
=================
*/
TModelSounds* BG_GetModelSoundsGroup ( const char* Identity, const char* SoundGroup )
{
TIdentity *identity;
TCharacterTemplate *character;
TModelSounds *sounds;
// Grab the identity in question
identity = BG_FindIdentity (Identity );
if ( !identity )
{
return NULL;
}
character = identity->mCharacter;
while ( character )
{
// Run through the sounds and look for the match
sounds = character->mSounds;
while ( sounds )
{
// Match?
if ( !Q_stricmp ( sounds->mName, SoundGroup ) )
{
return sounds;
}
sounds = sounds->mNext;
}
character = character->mParent;
}
// Not found
return NULL;
}
/*
=================
BG_GetModelSoundCount
Return the number of sounds for the given model
=================
*/
int BG_GetModelSoundCount ( const char *Identity, const char *SoundGroup )
{
TModelSounds* sounds;
// Grab the sounds
sounds = BG_GetModelSoundsGroup( Identity, SoundGroup );
if ( !sounds )
{
return 0;
}
// Return the sound count
return sounds->mCount;
}
/*
=================
BG_GetModelSound
Returns the model sound for the given sound group and index. If the sound
could not be found then NULL is returned.
=================
*/
const char *BG_GetModelSound ( const char *Identity, const char *SoundGroup, int index )
{
TModelSounds *sounds;
// Grab the sounds
sounds = BG_GetModelSoundsGroup( Identity, SoundGroup );
if ( !sounds )
{
return NULL;
}
// Invalid index?
if ( index >= sounds->mCount )
{
return "";
}
// Run through the sounds and look for the match
return sounds->mSounds[index];
}
/*
=================
BG_FindIdentity
Returns the identity with the given name
=================
*/
TIdentity *BG_FindIdentity ( const char *identityName )
{
int i;
// Convienience
if (!identityName)
{
return NULL;
}
// Linear search through all of the parsed identities
for ( i = 0; i < bg_identityCount; i ++ )
{
if (Q_stricmp(identityName, bg_identities[i].mName) == 0)
{
return &bg_identities[i];
}
}
// None found
return NULL;
}
/*
=================
BG_FindTeamIdentity
returns the first identity which matches the given team name
=================
*/
#define MAX_TEAM_IDENTS 5
TIdentity* BG_FindTeamIdentity ( const char* teamName, int index )
{
TIdentity* idents[MAX_TEAM_IDENTS];
int count;
int i;
// Convienience
if ( !teamName )
{
return NULL;
}
// Linear search through all of the parsed identities
for ( i = 0, count = 0; i < bg_identityCount && count < MAX_TEAM_IDENTS; i ++ )
{
if (Q_stricmp(teamName, bg_identities[i].mTeam) == 0)
{
idents[count++] = &bg_identities[i];
}
}
if ( !count )
{
return NULL;
}
if ( index != -1 )
{
if ( index >= count )
{
index = count - 1;
}
return idents[index];
}
// None found
return idents[rand()%count];
}
/*
=================
BG_ParseSkin
Reads a skin file into a null terminated list and returns the number found
=================
*/
int BG_ParseSkin ( const char* filename, char* pairs, int pairsSize )
{
TGenericParser2 skinFile;
TGPGroup *basegroup;
TGPGroup *group;
TGPGroup *sub;
char name[MAX_QPATH];
char* end;
int numPairs;
// Open the skin file
skinFile = trap_GP_ParseFile( (char*)filename, qtrue, qfalse );
if ( !skinFile )
{
return 0;
}
numPairs = 0;
end = pairs;
*end = 0;
basegroup = trap_GP_GetBaseParseGroup ( skinFile );
group = trap_GPG_GetSubGroups ( basegroup );
while(group)
{
trap_GPG_GetName ( group, name );
// Parse the material
if ( Q_stricmp ( name, "material") == 0)
{
char matName[MAX_QPATH];
char shaderName[MAX_QPATH];
trap_GPG_FindPairValue ( group, "name", "", matName );
sub = trap_GPG_FindSubGroup ( group, "group");
if (sub)
{
trap_GPG_FindPairValue ( sub, "shader1", "", shaderName );
if (!shaderName[0])
{
trap_GPG_FindPairValue ( sub, "texture1", "", shaderName );
}
}
if (matName[0] && shaderName[0])
{
int size;
size = Com_sprintf(end, pairsSize, "%s %s ", matName, shaderName);
end += size;
pairsSize -= size;
numPairs++;
}
}
group = trap_GPG_GetNext ( group );
}
trap_GP_Delete(&skinFile);
return numPairs;
}
/*
==================
BG_SwingAngles
==================
*/
static void BG_SwingAngles (
float destination,
float swingTolerance,
float clampTolerance,
float speed,
float *angle,
qboolean *swinging,
int frameTime
)
{
float swing;
float move;
float scale;
if ( !*swinging )
{
// see if a swing should be started
swing = AngleSubtract( *angle, destination );
if ( swing > swingTolerance || swing < -swingTolerance )
{
*swinging = qtrue;
}
}
if ( !*swinging )
{
return;
}
// modify the speed depending on the delta
// so it doesn't seem so linear
swing = AngleSubtract( destination, *angle );
scale = fabs( swing );
if ( scale < swingTolerance * 0.5 )
{
scale = 0.5;
}
else if ( scale < swingTolerance )
{
scale = 1.0;
}
else
{
scale = 2.0;
}
// swing towards the destination angle
if ( swing >= 0 )
{
move = frameTime * scale * speed;
if ( move >= swing )
{
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
}
else if ( swing < 0 )
{
move = frameTime * scale * -speed;
if ( move <= swing )
{
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
}
// clamp to no more than tolerance
swing = AngleSubtract( destination, *angle );
if ( swing > clampTolerance )
{
*angle = AngleMod( destination - (clampTolerance - 1) );
}
else if ( swing < -clampTolerance )
{
*angle = AngleMod( destination + (clampTolerance - 1) );
}
}
/*
=================
CG_AddPainTwitch
=================
*/
#define PAIN_TWITCH_TIME 200
static void BG_AddPainTwitch( int painTime, int painDirection, int currentTime, vec3_t torsoAngles ) {
int t;
float f;
t = currentTime - painTime;
if ( t >= PAIN_TWITCH_TIME ) {
return;
}
f = 1.0 - (float)t / PAIN_TWITCH_TIME;
if ( painDirection ) {
torsoAngles[ROLL] += 20 * f;
} else {
torsoAngles[ROLL] -= 20 * f;
}
}
/*
=================
BG_CalculateLeanOffset
=================
*/
float BG_CalculateLeanOffset ( int leanTime )
{
return ((float)(leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET);
}
/*
=================
BG_PlayerAngles
=================
*/
void BG_PlayerAngles (
vec3_t startAngles,
vec3_t legs[3],
vec3_t legsAngles, // out
vec3_t lowerTorsoAngles,
vec3_t upperTorsoAngles,
vec3_t headAngles,
int leanOffset,
int painTime,
int painDirection,
int currentTime,
animInfo_t* torsoInfo,
animInfo_t* legsInfo,
int frameTime,
vec3_t realvelocity,
qboolean dead,
float movementDir,
void* ghoul2
)
{
float dest;
static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
float speed;
int dir;
vec3_t velocity;
VectorCopy( startAngles, headAngles );
VectorCopy ( realvelocity, velocity );
headAngles[YAW] = AngleMod( headAngles[YAW] );
VectorClear( legsAngles );
VectorClear( lowerTorsoAngles );
VectorClear( upperTorsoAngles );
// --------- yaw -------------
// allow yaw to drift a bit
if ( ( legsInfo->anim & ~ANIM_TOGGLEBIT ) != TORSO_IDLE_PISTOL )
{
// if not standing still, always point all in the same direction
torsoInfo->yawing = qtrue;
torsoInfo->pitching = qtrue;
legsInfo->yawing = qtrue;
}
speed = VectorNormalize( velocity );
// adjust legs for movement dir
if (dead)
{
dir = 0;
}
else
{
dir = movementDir;
if ( leanOffset && !speed )
{
dir = 0;
}
}
// legsAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ];
// torsoAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ];
legsAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ];
lowerTorsoAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ];
// torso
BG_SwingAngles( lowerTorsoAngles[YAW], 25, 90, 0.3f, &torsoInfo->yawAngle, &torsoInfo->yawing, frameTime );
BG_SwingAngles( legsAngles[YAW], 40, 90, 0.3f, &legsInfo->yawAngle, &legsInfo->yawing, frameTime );
if ( leanOffset )
{
legsAngles[YAW] = headAngles[YAW];
}
else
{
legsAngles[YAW] = legsInfo->yawAngle;
}
lowerTorsoAngles[YAW] = Com_Clampf ( -90, 90, AngleDelta (headAngles[YAW], legsAngles[YAW] ) );
upperTorsoAngles[YAW] = lowerTorsoAngles[YAW] / 2;
lowerTorsoAngles[YAW] = legsAngles[YAW] + lowerTorsoAngles[YAW] / 2;
headAngles[YAW] -= upperTorsoAngles[YAW];
// --------- pitch -------------
// only show a fraction of the pitch angle in the torso
if ( headAngles[PITCH] > 180 ) {
dest = (-360 + headAngles[PITCH]) * 0.75;
} else {
dest = headAngles[PITCH] * 0.75;
}
lowerTorsoAngles[PITCH] = dest;
// --------- roll -------------
// lean towards the direction of travel
// Add in leanoffset
if ( leanOffset )
{
lowerTorsoAngles[ROLL] -= ((float)leanOffset * 1.25f);
lowerTorsoAngles[YAW] -= 1.25f * ((float)leanOffset/LEAN_OFFSET) * dest;
headAngles[YAW] -= ((float)leanOffset/LEAN_OFFSET) * dest;
headAngles[ROLL] -= ((float)leanOffset * 1.25f);
}
else if ( speed )
{
vec3_t axis[3];
float side;
speed *= 0.025;
AnglesToAxis( legsAngles, axis );
side = speed * DotProduct( velocity, axis[1] );
legsAngles[ROLL] -= side;
}
// pain twitch
BG_AddPainTwitch( painTime, painDirection, currentTime, lowerTorsoAngles );
// pull the angles back out of the hierarchial chain
AnglesSubtract( headAngles, lowerTorsoAngles, headAngles );
AnglesSubtract( lowerTorsoAngles, legsAngles, lowerTorsoAngles );
if ( legs && ghoul2 )
{
AnglesToAxis( legsAngles, legs );
// Apply the rotations
trap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", upperTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime);
trap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", lowerTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime);
trap_G2API_SetBoneAngles(ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, 0,0, currentTime);
}
}
/*
======================
BG_ParseAnimationFile
Read a configuration file containing animation counts and rates
models/players/visor/animation.cfg, etc
======================
*/
qboolean BG_ParseAnimationFile ( const char *filename, animation_t* animations )
{
const char *text_p;
int len;
int i;
char *token;
float fps;
int skip;
char text[20000];
fileHandle_t f;
int animNum;
// load the file
len = trap_FS_FOpenFile( filename, &f, FS_READ );
if ( len <= 0 || len >= sizeof( text ) - 1 )
{
return qfalse;
}
trap_FS_Read( text, len, f );
trap_FS_FCloseFile( f );
// parse the text
text[len] = 0;
text_p = text;
skip = 0;
//initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100
for(i = 0; i < MAX_ANIMATIONS; i++)
{
animations[i].firstFrame = 0;
animations[i].numFrames = 0;
animations[i].loopFrames = -1;
animations[i].frameLerp = 100;
animations[i].initialLerp = 100;
}
// read information for each frame
while(1)
{
token = COM_Parse( &text_p );
if ( !token || !token[0])
{
break;
}
animNum = GetIDForString(bg_animTable, token);
if(animNum == -1)
{
//#ifndef FINAL_BUILD
#ifdef _DEBUG
Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename);
#endif
continue;
}
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
animations[animNum].firstFrame = atoi( token );
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
animations[animNum].numFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
animations[animNum].loopFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
fps = atof( token );
if ( fps == 0 )
{
fps = 1;//Don't allow divide by zero error
}
if ( fps < 0 )
{//backwards
animations[animNum].frameLerp = floor(1000.0f / fps);
}
else
{
animations[animNum].frameLerp = ceil(1000.0f / fps);
}
animations[animNum].initialLerp = ceil(1000.0f / fabs(fps));
}
return qtrue;
}
/*
========================
BG_SetAvailableOutfitting
Set the current availability table
========================
*/
void BG_SetAvailableOutfitting ( const char* available )
{
int len;
len = strlen ( available );
if ( len > WP_NUM_WEAPONS )
{
len = WP_NUM_WEAPONS;
}
// IF the availability has changed force a reload of the outfitting groups
if ( Q_strncmp ( available, bg_availableOutfitting, min(sizeof(bg_availableOutfitting),len) ) )
{
bg_outfittingCount = 0;
}
// Initialize it to all on.
memset ( &bg_availableOutfitting[0], '2', sizeof(bg_availableOutfitting) );
memcpy ( &bg_availableOutfitting[0], available, len );
}
/*
========================
BG_IsWeaponAvailableForOutfitting
Is the given weapon available for outfitting?
========================
*/
qboolean BG_IsWeaponAvailableForOutfitting ( weapon_t weapon, int level )
{
if ( bg_availableOutfitting[0] == -1 )
{
return qtrue;
}
if ( bg_availableOutfitting[weapon-1] - '0' >= level )
{
return qtrue;
}
return qfalse;
}
/*
========================
BG_DecompressOutfitting
Decompresses the given outfitting string into the outfitting structure
========================
*/
void BG_DecompressOutfitting ( const char* compressed, goutfitting_t* outfitting)
{
int group;
int origitem;
memset ( outfitting->items, 0, sizeof(outfitting->items) );
for ( group = 0; group < OUTFITTING_GROUP_MAX; group ++ )
{
int item;
if ( !*compressed )
{
item = -1;
}
else
{
item = ((*compressed++) - 'A');
}
// Valid item number?
if ( item < 0 || item >= 10 )
{
item = 0;
}
// Valid slot for the group ?
if ( bg_outfittingGroups[group][item] == -1 )
{
continue;
}
// Ok to set the item now
outfitting->items[group] = item;
// No initialized
if ( bg_availableOutfitting[0] == -1 )
{
continue;
}
// Is it available?
if ( bg_itemlist[bg_outfittingGroups[group][item]].giType == IT_WEAPON )
{
origitem = item;
while ( !BG_IsWeaponAvailableForOutfitting ( bg_itemlist[bg_outfittingGroups[group][item]].giTag, 2 ) )
{
item++;
if ( bg_outfittingGroups[group][item] == -1 )
{
item = 0;
}
if ( item == origitem )
{
//Com_Error ( ERR_FATAL, "ERROR: There must be at least one weapon available in each category" );
item = -1;
break;
}
}
}
// Ok to set the item now
outfitting->items[group] = item;
}
}
/*
========================
BG_CompressOutfitting
Compresses the given outfitting structure into the given string
========================
*/
void BG_CompressOutfitting ( goutfitting_t* outfitting, char* compressed, int size )
{
int i;
for ( i = 0; i < OUTFITTING_GROUP_MAX && size; i ++, size-- )
{
*compressed++ = outfitting->items[i] + 'A';
}
*compressed = '\0';
}
/*
========================
BG_FindOutfitting
Finds the real outfitting that matches the given outfitting
========================
*/
int BG_FindOutfitting ( goutfitting_t* outfitting )
{
int i;
int l;
// Loop through all the outfittings linearly
for ( i = 0; i < bg_outfittingCount; i ++ )
{
for ( l = 0; l < OUTFITTING_GROUP_MAX; l ++ )
{
if ( bg_outfittings[i].items[l] != outfitting->items[l] )
{
break;
}
}
// Both iterators at the end signifies a match
if ( l == OUTFITTING_GROUP_MAX )
{
return i;
}
}
return -1;
}
/*
========================
BG_ParseOutfittingTemplate
Parses a single outfitting template
========================
*/
qboolean BG_ParseOutfittingTemplate ( const char* fileName, goutfitting_t* outfitting )
{
TGPGroup baseGroup;
TGPGroup subGroup;
TGenericParser2 file;
TGPGroup pairs;
char temp[MAX_OUTFITTING_NAME];
// Initialize the outfitting
memset ( outfitting, 0, sizeof(goutfitting_t) );
// Create the generic parser so the item file can be parsed
file = trap_GP_ParseFile( (char*)fileName, qtrue, qfalse );
if ( !file )
{
return qfalse;
}
// Start at the top with the "outfitting" group
baseGroup = trap_GP_GetBaseParseGroup ( file );
subGroup = trap_GPG_FindSubGroup ( baseGroup, "outfitting" );
if ( !subGroup )
{
trap_GP_Delete(&file);
return qfalse;
}
// Get the name of the template
trap_GPG_FindPairValue ( subGroup, "displayName", fileName, outfitting->name );
// Sub group named "items"
pairs = trap_GPG_FindPair ( subGroup, "items" );
if ( pairs )
{
TGPValue list;
// Run through the list
list = trap_GPV_GetList ( pairs );
while ( list )
{
gitem_t* item;
trap_GPV_GetName ( list, temp );
item = BG_FindItem ( temp );
if ( item )
{
int index;
for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ )
{
if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) )
{
outfitting->items[item->outfittingGroup] = index;
break;
}
}
}
list = trap_GPV_GetNext ( list );
}
}
// Sub group named "weapons"
pairs = trap_GPG_FindPair ( subGroup, "weapons" );
// Run through the weapons
if ( pairs )
{
TGPValue list;
// Run through the list
list = trap_GPV_GetList ( pairs );
while ( list )
{
gitem_t* item;
int i;
trap_GPV_GetName ( list, temp );
// Lookup the weapon number
for( i = WP_NONE + 1, item=NULL; i < WP_NUM_WEAPONS; i++ )
{
if ( Q_stricmp(bg_weaponNames[i], temp ) == 0)
{
// translate the weapon index into an item index.
item = BG_FindWeaponItem ( (weapon_t) i );
break;
}
}
// If the weapon translated into an item ok then drop it
// in its appropriate slot.
if ( item )
{
int index;
// Make sure outfitting groups that have weapons that are not available
// do not show up
if ( !BG_IsWeaponAvailableForOutfitting ( item->giTag, 2 ) )
{
trap_GP_Delete(&file);
return qfalse;
}
for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ )
{
if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) )
{
outfitting->items[item->outfittingGroup] = index;
break;
}
}
}
list = trap_GPV_GetNext ( list );
}
}
trap_GP_Delete(&file);
return qtrue;
}
/*
========================
BG_ParseOutfittingTemplates
Parses the available outfitting templates
========================
*/
int BG_ParseOutfittingTemplates ( qboolean force )
{
int i;
int numOutfittingFiles;
int filelen;
char fileName[MAX_QPATH];
char outfittingFiles[4096];
char *fileptr;
// Dont reload unless forced
if ( bg_outfittingCount && !force )
{
return bg_outfittingCount;
}
// Clear the current list
bg_outfittingCount = 1;
strcpy ( bg_outfittings[0].name, "CUSTOM" );
// Grab the list of NPC files
numOutfittingFiles = trap_FS_GetFileList("scripts", ".outfitting", outfittingFiles, 4096 );
if ( !numOutfittingFiles )
{
return 0;
}
// Parse each of the NPC files
fileptr = outfittingFiles;
for( i = 0; i < numOutfittingFiles; i++, fileptr += filelen+1 )
{
// Grab the length so we can skip this file later
filelen = strlen(fileptr);
Com_sprintf ( fileName, sizeof(fileName), "scripts/%s", fileptr );
// Parse the outfitting template
if ( BG_ParseOutfittingTemplate ( fileName, &bg_outfittings[bg_outfittingCount] ) )
{
bg_outfittingCount++;
}
}
return bg_outfittingCount;
}