586 lines
12 KiB
C++
586 lines
12 KiB
C++
#include "extdll.h"
|
|
#include "entity_state.h"
|
|
#include "pm_defs.h"
|
|
#include "pm_movevars.h"
|
|
#include "hud_iface.h"
|
|
#include "com_model.h"
|
|
#include "event_api.h"
|
|
#include "com_weapons.h"
|
|
#include "event_flags.h"
|
|
#include "Ricochet_BSPFile.h"
|
|
|
|
extern "C" playermove_t *pmove;
|
|
extern int g_runfuncs;
|
|
|
|
// Don't support more than MAX_PADS pads ( map still can load, but we'll just have some pads that don't predict. )
|
|
#define MAX_PADS 256
|
|
|
|
// We only care about two kinds of entities for now: Jump pads and their targets
|
|
// FIXME: After loading, store a pointer from pad to target instead of looking up all the time.
|
|
typedef enum
|
|
{
|
|
// Entity is a jump pad
|
|
RIC_PAD = 0,
|
|
// Entity is a target
|
|
RIC_TARGET
|
|
} ric_padtype_t;
|
|
|
|
typedef struct
|
|
{
|
|
// Type of entity
|
|
ric_padtype_t type;
|
|
|
|
// Classname
|
|
char classname[ 32 ];
|
|
|
|
// Model name
|
|
char modelname[ 32 ];
|
|
|
|
// What this entity targets
|
|
char target[ 32 ];
|
|
|
|
// If entity is a target, the name tag it uses
|
|
char targetname[ 32 ];
|
|
|
|
// Orientation of the pad
|
|
float angles[3];
|
|
|
|
// Target origin
|
|
float origin[3];
|
|
|
|
// Bounding box of the pad
|
|
float absmin[3];
|
|
float absmax[3];
|
|
|
|
// Model associated with the pad
|
|
struct model_s *model;
|
|
|
|
float height;
|
|
} ric_pad_t;
|
|
|
|
// Pad/Target entity database
|
|
static ric_pad_t s_pads[ MAX_PADS ];
|
|
static int s_num_pads = 0;
|
|
|
|
// We'll use this for playing the jump sounds locally.
|
|
static unsigned short s_usJump;
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_SetKeyValue
|
|
|
|
Fill in key/values fro the pad
|
|
==============================
|
|
*/
|
|
void Ricochet_SetKeyValue( ric_pad_t *pad, const char *key, const char *value )
|
|
{
|
|
float x, y, z;
|
|
|
|
if ( !stricmp( key, "classname" ) )
|
|
{
|
|
strcpy( pad->classname, value );
|
|
}
|
|
else if ( !stricmp( key, "target" ) )
|
|
{
|
|
strcpy( pad->target, value );
|
|
}
|
|
else if ( !stricmp( key, "targetname" ) )
|
|
{
|
|
strcpy( pad->targetname, value );
|
|
}
|
|
else if ( !stricmp( key, "model" ) )
|
|
{
|
|
strcpy( pad->modelname, value );
|
|
}
|
|
else if ( !stricmp( key, "height" ) )
|
|
{
|
|
pad->height = atof( value );
|
|
}
|
|
else if ( !stricmp( key, "angles" ) )
|
|
{
|
|
if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 )
|
|
{
|
|
pad->angles[ 0 ] = x ;
|
|
pad->angles[ 1 ] = y;
|
|
pad->angles[ 2 ] = z;
|
|
}
|
|
}
|
|
else if ( !stricmp( key, "origin" ) )
|
|
{
|
|
if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 )
|
|
{
|
|
pad->origin[ 0 ] = x;
|
|
pad->origin[ 1 ] = y;
|
|
pad->origin[ 2 ] = z;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_ParsePad
|
|
|
|
Evaluate Key/Value pairs for the entity
|
|
==============================
|
|
*/
|
|
char *Ricochet_ParsePad( char *buffer, ric_pad_t *pad, int *error )
|
|
{
|
|
char key[256];
|
|
char token[ 1024 ];
|
|
int n;
|
|
|
|
memset( pad, 0, sizeof( *pad ) );
|
|
|
|
while (1)
|
|
{
|
|
// Parse key
|
|
buffer = gEngfuncs.COM_ParseFile ( buffer, token );
|
|
if ( token[0] == '}' )
|
|
break;
|
|
|
|
// Ran out of input buffer?
|
|
if ( !buffer )
|
|
{
|
|
*error = 1;
|
|
break;
|
|
}
|
|
|
|
// Store off the key
|
|
strcpy ( key, token );
|
|
|
|
// Fix heynames with trailing spaces
|
|
n = strlen( key );
|
|
while (n && key[n-1] == ' ')
|
|
{
|
|
key[n-1] = 0;
|
|
n--;
|
|
}
|
|
|
|
// Parse value
|
|
buffer = gEngfuncs.COM_ParseFile ( buffer, token );
|
|
|
|
// Ran out of buffer?
|
|
if (!buffer)
|
|
{
|
|
*error = 1;
|
|
break;
|
|
}
|
|
|
|
// Hit the end instead of a value?
|
|
if ( token[0] == '}' )
|
|
{
|
|
*error = 1;
|
|
break;
|
|
}
|
|
|
|
// Assign k/v pair
|
|
Ricochet_SetKeyValue( pad, key, token );
|
|
}
|
|
|
|
// Return what's left in the stream
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_ProcessEnts
|
|
|
|
Parse through entity lump looking for pads or targets
|
|
==============================
|
|
*/
|
|
void Ricochet_ProcessEnts( char *buffer )
|
|
{
|
|
int i;
|
|
char token[ 1024 ];
|
|
ric_pad_t *pad = NULL;
|
|
int error = 0;
|
|
|
|
// parse entities from entity lump of .bsp file
|
|
while (1)
|
|
{
|
|
// parse the opening brace
|
|
buffer = gEngfuncs.COM_ParseFile ( buffer, token );
|
|
if (!buffer)
|
|
break;
|
|
|
|
// Didn't find opening brace?
|
|
if ( token[0] != '{' )
|
|
{
|
|
gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: found %s when expecting {\n", token );
|
|
return;
|
|
}
|
|
|
|
// Assume we're filling in this pad
|
|
pad = &s_pads[ s_num_pads ];
|
|
|
|
// Fill in data
|
|
buffer = Ricochet_ParsePad( buffer, pad, &error );
|
|
|
|
// Check for errors and abort if any
|
|
if ( error )
|
|
{
|
|
gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: error parsing entities\n" );
|
|
return;
|
|
}
|
|
|
|
// Check classname
|
|
if ( stricmp( pad->classname, "trigger_jump" ) && stricmp( pad->classname, "info_target" ) )
|
|
continue;
|
|
|
|
// Set type based on classname
|
|
if ( !stricmp( pad->classname, "trigger_jump" ) )
|
|
{
|
|
pad->type = RIC_PAD;
|
|
}
|
|
else
|
|
{
|
|
pad->type = RIC_TARGET;
|
|
}
|
|
|
|
// Load up the model
|
|
pad->model = gEngfuncs.CL_LoadModel( pad->modelname, NULL );
|
|
if ( pad->model )
|
|
{
|
|
// Fill in abs bbox
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
pad->absmin[ i ] = pad->model->mins[ i ] - 1.0;
|
|
pad->absmax[ i ] = pad->model->maxs[ i ] + 1.0;
|
|
}
|
|
}
|
|
|
|
// If we got to here, we're using the entity
|
|
s_num_pads++;
|
|
|
|
// No more room...
|
|
if ( s_num_pads >= MAX_PADS )
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_LoadEntityLump
|
|
|
|
Open the .bsp and read in the entity lump
|
|
==============================
|
|
*/
|
|
char *Ricochet_LoadEntityLump( const char *filename )
|
|
{
|
|
FILE *fp;
|
|
int i;
|
|
dheader_t header;
|
|
int size;
|
|
lump_t *curLump;
|
|
char *buffer = NULL;
|
|
|
|
fp = fopen( filename, "rb" );
|
|
if ( !fp )
|
|
return NULL;
|
|
|
|
// Read in the .bsp header
|
|
if ( fread(&header, sizeof(dheader_t), 1, fp) != 1 )
|
|
{
|
|
gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Could not read BSP header for map [%s].\n", filename);
|
|
fclose(fp);
|
|
return NULL;
|
|
}
|
|
|
|
// Check the version
|
|
i = header.version;
|
|
if ( i != 29 && i != 30)
|
|
{
|
|
fclose(fp);
|
|
gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Map [%s] has incorrect BSP version (%i should be %i).\n", filename, i, BSPVERSION);
|
|
return NULL;
|
|
}
|
|
|
|
// Get entity lump
|
|
curLump = &header.lumps[ LUMP_ENTITIES ];
|
|
// and entity lump size
|
|
size = curLump->filelen;
|
|
|
|
// Jump to it
|
|
fseek( fp, curLump->fileofs, SEEK_SET );
|
|
|
|
// Allocate sufficient memmory
|
|
buffer = (char *)malloc( size + 1 );
|
|
if ( !buffer )
|
|
{
|
|
fclose(fp);
|
|
gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Couldn't allocate %i bytes\n", size + 1 );
|
|
return NULL;
|
|
}
|
|
|
|
// Read in the entity lump
|
|
fread( buffer, size, 1, fp );
|
|
|
|
// Terminate the string
|
|
buffer[ size ] = '\0';
|
|
|
|
if ( fp )
|
|
{
|
|
fclose(fp);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_LoadJumpPads
|
|
|
|
Load in the .bsp file and process the entities
|
|
==============================
|
|
*/
|
|
void Ricochet_LoadJumpPads( const char *map )
|
|
{
|
|
char *buffer = NULL;
|
|
char filename[ 256 ];
|
|
|
|
sprintf( filename, "%s/%s", gEngfuncs.pfnGetGameDirectory(), map );
|
|
|
|
// TODO: Fix Slashes?
|
|
|
|
// Reset count
|
|
s_num_pads = 0;
|
|
|
|
// Load entity lump
|
|
buffer = Ricochet_LoadEntityLump( filename );
|
|
if ( !buffer )
|
|
return;
|
|
|
|
// Process buffer and extract pads/targets
|
|
Ricochet_ProcessEnts( buffer );
|
|
|
|
// Discard buffer
|
|
free( buffer );
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_FindTarget
|
|
|
|
Search entity list for target matching "name"
|
|
==============================
|
|
*/
|
|
ric_pad_t *Ricochet_FindTarget( const char *name, int numpads, ric_pad_t *pads )
|
|
{
|
|
int i;
|
|
ric_pad_t *target;
|
|
|
|
// Find the target
|
|
for ( i = 0; i < numpads; i++ )
|
|
{
|
|
target = &pads[ i ];
|
|
if ( !target )
|
|
continue;
|
|
|
|
if ( stricmp( target->targetname, name ) )
|
|
continue;
|
|
|
|
return target;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_PadTouched
|
|
|
|
Register impact ( impart velocity on player and if final function call, play appropriate jump sound )
|
|
==============================
|
|
*/
|
|
void Ricochet_PadTouched( int numpads, ric_pad_t *pads, ric_pad_t *pad, struct local_state_s *player )
|
|
{
|
|
int i;
|
|
ric_pad_t *target;
|
|
float origin[ 3 ];
|
|
pmtrace_t tr;
|
|
float flGravity = pmove->movevars->gravity;
|
|
|
|
float vecMidPoint[3];
|
|
float end[ 3 ];
|
|
|
|
// Ricochet jump pads use default jump height
|
|
float flHeight = 150;
|
|
|
|
float zero[ 3 ] = { 0.0, 0.0, 0.0 };
|
|
|
|
// Find the target
|
|
target = Ricochet_FindTarget( pad->target, numpads, pads );
|
|
|
|
// Target now points to target pad
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
origin[ i ] = player->playerstate.origin[ i ];
|
|
|
|
// Get a rough idea of how high to launch
|
|
vecMidPoint[ i ] = origin[ i ] + ( target->origin[ i ] - origin[ i ]) * 0.5;
|
|
end[ i ] = vecMidPoint[ i ];
|
|
}
|
|
|
|
if ( pad->height != 0.0 )
|
|
{
|
|
flHeight = pad->height;
|
|
}
|
|
|
|
// Move up by height
|
|
end[ 2 ] += flHeight;
|
|
|
|
// See if we can reach the apex from the midpoint
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( vecMidPoint, end, PM_STUDIO_BOX, -1, &tr );
|
|
|
|
// Use the end point of the trace as the midpoint of the actual toss
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
vecMidPoint[i] = tr.endpos[i];
|
|
}
|
|
|
|
// Subtract some units so we don't hit the ceiling)
|
|
vecMidPoint[2] -= 15;
|
|
|
|
// How high should we travel to reach the apex
|
|
float distance1 = fabs(vecMidPoint[2] - origin[2]);
|
|
float distance2 = fabs(vecMidPoint[2] - target->origin[2]);
|
|
|
|
// How long will it take to travel this distance
|
|
float time1 = sqrt( distance1 / (0.5 * flGravity) );
|
|
float time2 = sqrt( distance2 / (0.5 * flGravity) );
|
|
if (time1 < 0.1)
|
|
return;
|
|
|
|
// Determine how hard to launch to get there in time.
|
|
float vecTargetVel[3];
|
|
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
vecTargetVel[ i ] = (target->origin[ i ] - origin[ i ]) / (time1 + time2);
|
|
}
|
|
|
|
// Adjust upward velocity needed
|
|
vecTargetVel[ 2 ] = flGravity * time1;
|
|
|
|
// Fill in needed velocity
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
player->client.velocity[i] = vecTargetVel[i];
|
|
}
|
|
|
|
// Play sound if appropriate
|
|
if ( s_usJump && g_runfuncs )
|
|
{
|
|
gEngfuncs.pfnPlaybackEvent( FEV_NOTHOST, NULL, s_usJump, 0.0, zero, zero, 0.0, 0.0, 0, 0, 0, 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_TouchPads
|
|
|
|
See if player's resting position impacts any jump pads
|
|
==============================
|
|
*/
|
|
void Ricochet_TouchPads ( struct local_state_s *player, ric_pad_t *pads, int numpads )
|
|
{
|
|
int i, j;
|
|
ric_pad_t *pad;
|
|
float absmin[3], absmax[3];
|
|
physent_t pe;
|
|
hull_t *hull;
|
|
int num;
|
|
float test[3];
|
|
float pmins[ 3 ] = { 16, 16, 36 };
|
|
|
|
// Determine player's bbox
|
|
for ( j = 0; j < 3; j++ )
|
|
{
|
|
absmin[ j ] = player->playerstate.origin[ j ] - pmins[ j ];
|
|
absmax[ j ] = player->playerstate.origin[ j ] + pmins[ j ];
|
|
}
|
|
|
|
// Cycle through pads looking for a match
|
|
for ( i = 0; i < numpads; i++ )
|
|
{
|
|
pad = &pads[ i ];
|
|
if ( !pad )
|
|
continue;
|
|
|
|
// Target entities don't make us jump
|
|
if ( pad->type != RIC_PAD )
|
|
continue;
|
|
|
|
// Trivial reject?
|
|
if ( absmin[0] > pad->absmax[0]
|
|
|| absmin[1] > pad->absmax[1]
|
|
|| absmin[2] > pad->absmax[2]
|
|
|| absmax[0] < pad->absmin[0]
|
|
|| absmax[1] < pad->absmin[1]
|
|
|| absmax[2] < pad->absmin[2] )
|
|
continue;
|
|
|
|
// Set up physent for the test case
|
|
pe.model = pad->model;
|
|
pe.origin = pad->origin;
|
|
|
|
// Use standing player hull
|
|
pmove->usehull = 0;
|
|
|
|
// Make sure it's a brush model, of course
|
|
if ( !pe.model || (modtype_t)pmove->PM_GetModelType( pe.model ) != mod_brush )
|
|
continue;
|
|
|
|
// Get the hull
|
|
hull = (hull_t *)pmove->PM_HullForBsp( &pe, test );
|
|
num = hull->firstclipnode;
|
|
|
|
// Offset the origin by the offset appropriate for this hull.
|
|
for ( j = 0; j < 3; j++ )
|
|
{
|
|
test[ j ] = player->playerstate.origin[ j ] - test[ j ];
|
|
}
|
|
|
|
// Test the player's hull for intersection with this model
|
|
if ( pmove->PM_HullPointContents ( hull, num, test ) != CONTENTS_SOLID )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// TOUCHED!!!
|
|
Ricochet_PadTouched( numpads, pads, pad, player );
|
|
|
|
// Only touch one pad at a time
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
Ricochet_CheckJumpPads
|
|
|
|
Load data if needed, otherwise just run checks on player's final position to see if jump pad needs
|
|
to impart velocity on the player.
|
|
==============================
|
|
*/
|
|
void Ricochet_CheckJumpPads( struct local_state_s *from, struct local_state_s *to )
|
|
{
|
|
static char current_level[ 128 ];
|
|
|
|
// See if we've changed to a new map
|
|
if ( stricmp( current_level, gEngfuncs.pfnGetLevelName() ) )
|
|
{
|
|
strcpy( current_level, gEngfuncs.pfnGetLevelName() );
|
|
Ricochet_LoadJumpPads( current_level );
|
|
|
|
// Grab sound event
|
|
s_usJump = gEngfuncs.pfnPrecacheEvent( 1, "events/jump.sc" );
|
|
}
|
|
|
|
// Not while spectating
|
|
if ( to->client.iuser1 )
|
|
return;
|
|
|
|
// Run test
|
|
Ricochet_TouchPads( to, s_pads, s_num_pads );
|
|
} |