595 lines
16 KiB
C
595 lines
16 KiB
C
// Copyright (C) 2001-2002 Raven Software.
|
|
//
|
|
// g_misc.c
|
|
|
|
#include "g_local.h"
|
|
|
|
|
|
/*QUAKED func_group (0 0 0) ?
|
|
Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
|
|
*/
|
|
|
|
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
|
|
Used as a positional target for in-game calculation, like jumppad targets.
|
|
target_position does the same thing
|
|
*/
|
|
void SP_info_notnull( gentity_t *self )
|
|
{
|
|
G_SetOrigin( self, self->s.origin );
|
|
}
|
|
|
|
/*
|
|
=================================================================================
|
|
TeleportPlayer
|
|
=================================================================================
|
|
*/
|
|
void TeleportPlayer ( gentity_t *player, vec3_t origin, vec3_t angles )
|
|
{
|
|
gentity_t *tent;
|
|
|
|
// use temp events at source and destination to prevent the effect
|
|
// from getting dropped by a second player event
|
|
if ( !G_IsClientSpectating ( player->client ) )
|
|
{
|
|
tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
|
|
tent->s.clientNum = player->s.clientNum;
|
|
|
|
tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
|
|
tent->s.clientNum = player->s.clientNum;
|
|
}
|
|
|
|
// unlink to make sure it can't possibly interfere with G_KillBox
|
|
trap_UnlinkEntity (player);
|
|
|
|
VectorCopy ( origin, player->client->ps.origin );
|
|
player->client->ps.origin[2] += 1;
|
|
|
|
// spit the player out
|
|
AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
|
|
VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
|
|
player->client->ps.pm_time = 160; // hold time
|
|
player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
|
|
// toggle the teleport bit so the client knows to not lerp
|
|
player->client->ps.eFlags ^= EF_TELEPORT_BIT;
|
|
|
|
// set angles
|
|
SetClientViewAngle( player, angles );
|
|
|
|
// kill anything at the destination
|
|
if ( !G_IsClientSpectating ( player->client ) )
|
|
{
|
|
G_KillBox (player);
|
|
}
|
|
|
|
// save results of pmove
|
|
BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
|
|
|
|
// use the precise origin for linking
|
|
VectorCopy( player->client->ps.origin, player->r.currentOrigin );
|
|
|
|
if ( !G_IsClientSpectating ( player->client ) )
|
|
{
|
|
trap_LinkEntity (player);
|
|
}
|
|
}
|
|
|
|
|
|
/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
|
|
Point teleporters at these.
|
|
Now that we don't have teleport destination pads, this is just
|
|
an info_notnull
|
|
*/
|
|
void SP_misc_teleporter_dest( gentity_t *ent )
|
|
{
|
|
}
|
|
|
|
//===========================================================
|
|
|
|
/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG
|
|
this model is inserted into the bsp file
|
|
"model" arbitrary .md3 file to display
|
|
*/
|
|
void SP_misc_model( gentity_t *ent )
|
|
{
|
|
#if 0
|
|
ent->s.modelindex = G_ModelIndex( ent->model );
|
|
VectorSet (ent->mins, -16, -16, -16);
|
|
VectorSet (ent->maxs, 16, 16, 16);
|
|
trap_LinkEntity (ent);
|
|
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
VectorCopy( ent->s.angles, ent->s.apos.trBase );
|
|
#else
|
|
G_FreeEntity( ent );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16)
|
|
"model" arbitrary .glm file to display
|
|
*/
|
|
void SP_misc_G2model( gentity_t *ent ) {
|
|
|
|
#if 0
|
|
char name1[200] = "models/players/kyle/modelmp.glm";
|
|
trap_G2API_InitGhoul2Model(&ent->s, name1, G_ModelIndex( name1 ), 0, 0, 0, 0);
|
|
trap_G2API_SetBoneAnim(ent->s.ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1);
|
|
ent->s.radius = 150;
|
|
// VectorSet (ent->mins, -16, -16, -16);
|
|
// VectorSet (ent->maxs, 16, 16, 16);
|
|
trap_LinkEntity (ent);
|
|
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
VectorCopy( ent->s.angles, ent->s.apos.trBase );
|
|
#else
|
|
G_FreeEntity( ent );
|
|
#endif
|
|
}
|
|
|
|
//===========================================================
|
|
|
|
void locateCamera( gentity_t *ent )
|
|
{
|
|
vec3_t dir;
|
|
gentity_t *target;
|
|
gentity_t *owner;
|
|
|
|
owner = G_PickTarget( ent->target );
|
|
if ( !owner )
|
|
{
|
|
Com_Printf( "Couldn't find target for misc_partal_surface\n" );
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
ent->r.ownerNum = owner->s.number;
|
|
|
|
// frame holds the rotate speed
|
|
if ( owner->spawnflags & 1 )
|
|
{
|
|
ent->s.frame = 25;
|
|
}
|
|
else if ( owner->spawnflags & 2 )
|
|
{
|
|
ent->s.frame = 75;
|
|
}
|
|
|
|
// swing camera ?
|
|
if ( owner->spawnflags & 4 )
|
|
{
|
|
// set to 0 for no rotation at all
|
|
ent->s.gametypeitems = 0;
|
|
}
|
|
else
|
|
{
|
|
ent->s.gametypeitems = 1;
|
|
}
|
|
|
|
// clientNum holds the rotate offset
|
|
ent->s.clientNum = owner->s.clientNum;
|
|
|
|
VectorCopy( owner->s.origin, ent->s.origin2 );
|
|
|
|
// see if the portal_camera has a target
|
|
target = G_PickTarget( owner->target );
|
|
if ( target ) {
|
|
VectorSubtract( target->s.origin, owner->s.origin, dir );
|
|
VectorNormalize( dir );
|
|
} else {
|
|
G_SetMovedir( owner->s.angles, dir );
|
|
}
|
|
|
|
ent->s.eventParm = DirToByte( dir );
|
|
}
|
|
|
|
/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
|
|
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
|
|
This must be within 64 world units of the surface!
|
|
*/
|
|
void SP_misc_portal_surface(gentity_t *ent)
|
|
{
|
|
VectorClear( ent->r.mins );
|
|
VectorClear( ent->r.maxs );
|
|
trap_LinkEntity (ent);
|
|
|
|
ent->r.svFlags = SVF_PORTAL;
|
|
ent->s.eType = ET_PORTAL;
|
|
|
|
if ( !ent->target )
|
|
{
|
|
VectorCopy( ent->s.origin, ent->s.origin2 );
|
|
}
|
|
else
|
|
{
|
|
ent->think = locateCamera;
|
|
ent->nextthink = level.time + 100;
|
|
}
|
|
}
|
|
|
|
/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
|
|
The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
|
|
"roll" an angle modifier to orient the camera around the target vector;
|
|
*/
|
|
void SP_misc_portal_camera(gentity_t *ent)
|
|
{
|
|
float roll;
|
|
|
|
VectorClear( ent->r.mins );
|
|
VectorClear( ent->r.maxs );
|
|
trap_LinkEntity (ent);
|
|
|
|
G_SpawnFloat( "roll", "0", &roll );
|
|
|
|
ent->s.clientNum = roll/360.0 * 256;
|
|
}
|
|
|
|
/*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
|
|
"bspmodel" arbitrary .bsp file to display
|
|
*/
|
|
void SP_misc_bsp(gentity_t *ent)
|
|
{
|
|
char temp[MAX_QPATH];
|
|
char *out;
|
|
float newAngle;
|
|
int tempint;
|
|
|
|
G_SpawnFloat( "angle", "0", &newAngle );
|
|
if (newAngle != 0.0)
|
|
{
|
|
ent->s.angles[1] = newAngle;
|
|
}
|
|
// don't support rotation any other way
|
|
ent->s.angles[0] = 0.0;
|
|
ent->s.angles[2] = 0.0;
|
|
|
|
G_SpawnString("bspmodel", "", &out);
|
|
|
|
ent->s.eFlags = EF_PERMANENT;
|
|
|
|
// Mainly for debugging
|
|
G_SpawnInt( "spacing", "0", &tempint);
|
|
ent->s.time2 = tempint;
|
|
G_SpawnInt( "flatten", "0", &tempint);
|
|
ent->s.time = tempint;
|
|
|
|
Com_sprintf(temp, MAX_QPATH, "#%s", out);
|
|
trap_SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs
|
|
G_BSPIndex(temp);
|
|
|
|
level.mNumBSPInstances++;
|
|
Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
|
|
VectorCopy(ent->s.origin, level.mOriginAdjust);
|
|
level.mRotationAdjust = ent->s.angles[1];
|
|
level.mTargetAdjust = temp;
|
|
level.hasBspInstances = qtrue;
|
|
level.mBSPInstanceDepth++;
|
|
G_SpawnString("filter", "", &out);
|
|
strcpy(level.mFilter, out);
|
|
G_SpawnString("teamfilter", "", &out);
|
|
strcpy(level.mTeamFilter, out);
|
|
|
|
VectorCopy( ent->s.origin, ent->s.pos.trBase );
|
|
VectorCopy( ent->s.origin, ent->r.currentOrigin );
|
|
VectorCopy( ent->s.angles, ent->s.apos.trBase );
|
|
VectorCopy( ent->s.angles, ent->r.currentAngles );
|
|
|
|
ent->s.eType = ET_MOVER;
|
|
|
|
trap_LinkEntity (ent);
|
|
|
|
trap_SetActiveSubBSP(ent->s.modelindex);
|
|
G_SpawnEntitiesFromString(qtrue);
|
|
trap_SetActiveSubBSP(-1);
|
|
|
|
level.mBSPInstanceDepth--;
|
|
level.mFilter[0] = level.mTeamFilter[0] = 0;
|
|
|
|
if ( g_debugRMG.integer )
|
|
{
|
|
G_SpawnDebugCylinder ( ent->s.origin, ent->s.time2, &g_entities[0], 2000, COLOR_WHITE );
|
|
|
|
if ( ent->s.time )
|
|
{
|
|
G_SpawnDebugCylinder ( ent->s.origin, ent->s.time, &g_entities[0], 2000, COLOR_RED );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*QUAKED terrain (1.0 1.0 1.0) ?
|
|
Terrain entity
|
|
It will stretch to the full height of the brush
|
|
|
|
numPatches - integer number of patches to split the terrain brush into (default 200)
|
|
terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
|
|
seed - integer seed for random terrain generation (default 0)
|
|
textureScale - float scale of texture (default 0.005)
|
|
heightMap - name of heightmap data image to use
|
|
terrainDef - defines how the game textures the terrain (file is base/ext_data/*.terrain - default is grassyhills)
|
|
instanceDef - defines which bsp instances appear
|
|
miscentDef - defines which client models spawn on the terrain (file is base/ext_data/*.miscents)
|
|
densityMap - how dense the client models are packed
|
|
|
|
*/
|
|
void SP_terrain(gentity_t *ent)
|
|
{
|
|
char temp[MAX_INFO_STRING];
|
|
char final[MAX_QPATH];
|
|
char seed[MAX_QPATH];
|
|
char missionType[MAX_QPATH];
|
|
char soundSet[MAX_QPATH];
|
|
int shaderNum, i;
|
|
char *value;
|
|
int terrainID;
|
|
|
|
VectorClear (ent->s.angles);
|
|
trap_SetBrushModel( ent, ent->model );
|
|
|
|
// Get the shader from the top of the brush
|
|
// shaderNum = gi.CM_GetShaderNum(s.modelindex);
|
|
shaderNum = 0;
|
|
|
|
if (RMG.integer)
|
|
{
|
|
// Grab the default terrain file from the RMG cvar
|
|
trap_Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH);
|
|
Com_sprintf(final, MAX_QPATH, "%s", temp);
|
|
AddSpawnField("terrainDef", temp);
|
|
|
|
trap_Cvar_VariableStringBuffer("RMG_instances", temp, MAX_QPATH);
|
|
Com_sprintf(final, MAX_QPATH, "%s", temp);
|
|
AddSpawnField("instanceDef", temp);
|
|
|
|
trap_Cvar_VariableStringBuffer("RMG_miscents", temp, MAX_QPATH);
|
|
Com_sprintf(final, MAX_QPATH, "%s", temp);
|
|
AddSpawnField("miscentDef", temp);
|
|
|
|
trap_Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH);
|
|
trap_Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH);
|
|
|
|
trap_Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH);
|
|
trap_SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet );
|
|
}
|
|
|
|
// Arbitrary (but sane) limits to the number of terxels
|
|
// if((mTerxels < MIN_TERXELS) || (mTerxels > MAX_TERXELS))
|
|
{
|
|
// Com_printf("G_Terrain: terxels out of range - defaulting to 4\n");
|
|
// mTerxels = 4;
|
|
}
|
|
|
|
// Get info required for the common init
|
|
temp[0] = 0;
|
|
G_SpawnString("heightmap", "", &value);
|
|
Info_SetValueForKey(temp, "heightMap", value);
|
|
|
|
G_SpawnString("numpatches", "400", &value);
|
|
Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value)));
|
|
|
|
G_SpawnString("terxels", "4", &value);
|
|
Info_SetValueForKey(temp, "terxels", va("%d", atoi(value)));
|
|
|
|
Info_SetValueForKey(temp, "seed", seed);
|
|
Info_SetValueForKey(temp, "minx", va("%f", ent->r.mins[0]));
|
|
Info_SetValueForKey(temp, "miny", va("%f", ent->r.mins[1]));
|
|
Info_SetValueForKey(temp, "minz", va("%f", ent->r.mins[2]));
|
|
Info_SetValueForKey(temp, "maxx", va("%f", ent->r.maxs[0]));
|
|
Info_SetValueForKey(temp, "maxy", va("%f", ent->r.maxs[1]));
|
|
Info_SetValueForKey(temp, "maxz", va("%f", ent->r.maxs[2]));
|
|
|
|
Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex));
|
|
|
|
G_SpawnString("terraindef", "grassyhills", &value);
|
|
Info_SetValueForKey(temp, "terrainDef", value);
|
|
|
|
G_SpawnString("instancedef", "", &value);
|
|
Info_SetValueForKey(temp, "instanceDef", value);
|
|
|
|
G_SpawnString("miscentdef", "", &value);
|
|
Info_SetValueForKey(temp, "miscentDef", value);
|
|
|
|
Info_SetValueForKey(temp, "missionType", missionType);
|
|
|
|
for(i = 0; i < MAX_INSTANCE_TYPES; i++)
|
|
{
|
|
trap_Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH);
|
|
if(strlen(final))
|
|
{
|
|
Info_SetValueForKey(temp, va("inst%d", i), final);
|
|
}
|
|
}
|
|
|
|
// Set additional data required on the client only
|
|
G_SpawnString("densitymap", "", &value);
|
|
Info_SetValueForKey(temp, "densityMap", value);
|
|
|
|
Info_SetValueForKey(temp, "shader", va("%d", shaderNum));
|
|
G_SpawnString("texturescale", "0.005", &value);
|
|
Info_SetValueForKey(temp, "texturescale", va("%f", atof(value)));
|
|
|
|
// Initialise the common aspects of the terrain
|
|
terrainID = trap_CM_RegisterTerrain(temp);
|
|
// SetCommon(common);
|
|
|
|
Info_SetValueForKey(temp, "terrainId", va("%d", terrainID));
|
|
|
|
// Let the entity know if it is random generated or not
|
|
// SetIsRandom(common->GetIsRandom());
|
|
|
|
// Let the game remember everything
|
|
level.landScapes[terrainID] = ent;
|
|
|
|
// Send all the data down to the client
|
|
trap_SetConfigstring(CS_TERRAINS + terrainID, temp);
|
|
|
|
// Make sure the contents are properly set
|
|
ent->r.contents = CONTENTS_TERRAIN;
|
|
ent->r.svFlags = SVF_NOCLIENT;
|
|
ent->s.eFlags = EF_PERMANENT;
|
|
ent->s.eType = ET_TERRAIN;
|
|
|
|
// Hook into the world so physics will work
|
|
trap_LinkEntity(ent);
|
|
|
|
// If running RMG then initialize the terrain and handle team skins
|
|
if ( RMG.integer )
|
|
{
|
|
trap_RMG_Init(terrainID);
|
|
|
|
if ( level.gametypeData->teams )
|
|
{
|
|
char temp[MAX_QPATH];
|
|
|
|
// Red team change from RMG ?
|
|
trap_GetConfigstring ( CS_GAMETYPE_REDTEAM, temp, MAX_QPATH );
|
|
if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_RED] ) )
|
|
{
|
|
level.gametypeTeam[TEAM_RED] = trap_VM_LocalStringAlloc ( temp );
|
|
}
|
|
|
|
// Blue team change from RMG ?
|
|
trap_GetConfigstring ( CS_GAMETYPE_BLUETEAM, temp, MAX_QPATH );
|
|
if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_BLUE] ) )
|
|
{
|
|
level.gametypeTeam[TEAM_BLUE] = trap_VM_LocalStringAlloc ( temp );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================================================================================
|
|
G_DebugCylinderThink
|
|
=================================================================================
|
|
*/
|
|
void G_DebugCylinderThink ( gentity_t* ent )
|
|
{
|
|
vec3_t vec1;
|
|
vec3_t vec2;
|
|
|
|
ent->nextthink = level.time + 1000;
|
|
|
|
VectorCopy ( ent->parent->client->ps.origin, vec1 );
|
|
VectorCopy ( ent->r.currentOrigin, vec2 );
|
|
|
|
vec1[2] = 0;
|
|
vec2[2] = 0;
|
|
|
|
// IF we are too far away then kill it
|
|
if ( Distance ( vec1, vec2 ) > ent->speed )
|
|
{
|
|
trap_UnlinkEntity ( ent );
|
|
return;
|
|
}
|
|
|
|
trap_LinkEntity ( ent );
|
|
}
|
|
|
|
/*
|
|
=================================================================================
|
|
G_SpawnDebugCylinder
|
|
=================================================================================
|
|
*/
|
|
void G_SpawnDebugCylinder ( vec3_t origin, float radius, gentity_t* clientent, float viewRadius, int colorIndex )
|
|
{
|
|
gentity_t* ent;
|
|
|
|
if ( !g_cheats.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent = G_Spawn ( );
|
|
ent->s.eType = ET_DEBUG_CYLINDER;
|
|
ent->s.time = colorIndex;
|
|
ent->s.time2 = (int) radius;
|
|
ent->parent = clientent;
|
|
ent->speed = viewRadius;
|
|
|
|
VectorCopy ( origin, ent->s.origin );
|
|
VectorCopy ( origin, ent->r.currentOrigin );
|
|
|
|
ent->nextthink = level.time + 1000;
|
|
ent->think = G_DebugCylinderThink;
|
|
|
|
trap_LinkEntity ( ent );
|
|
}
|
|
|
|
|
|
void fx_think( gentity_t *ent )
|
|
{
|
|
int time;
|
|
|
|
// play fx
|
|
G_PlayEffect( ent->health, ent->s.origin, ent->pos1 );
|
|
|
|
if( ent->count >= 0 ) // stops it from rolling over, yeah kinda lame...
|
|
{
|
|
ent->count--;
|
|
}
|
|
|
|
if( !ent->count ) // effect is suppose to play mCount times then dissapear
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
// calc next play time
|
|
time = (ent->wait + flrand(0.0f, ent->random)) * 1000; // need it in milliseconds
|
|
|
|
ent->think = fx_think;
|
|
ent->nextthink = level.time + time;
|
|
}
|
|
|
|
|
|
/*QUAKED fx_play_effect (.2 .5 .8) (-8 -8 -8) (8 8 8) START_OFF
|
|
Plays specified effect file
|
|
|
|
"effect" name of .efx file
|
|
"wait" seconds between triggerings, default 0.3
|
|
"random" wait variance in seconds, default 0
|
|
"target" direction of effect, default up
|
|
"count" plays effect this many times then deletes itself, default -1 = infinite
|
|
|
|
|
|
START_OFF fx starts off
|
|
|
|
|
|
*/
|
|
|
|
void SP_fx_play_effect(gentity_t *ent)
|
|
{
|
|
gentity_t *target;
|
|
char *fxName;
|
|
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
G_SpawnString("effect", "", &fxName);
|
|
ent->health = G_EffectIndex(fxName);
|
|
|
|
if (ent->wait == 0.0)
|
|
{
|
|
ent->wait = 0.3;
|
|
}
|
|
|
|
target = G_Find(0, FOFS(targetname), ent->target);
|
|
if (target)
|
|
{
|
|
VectorSubtract( target->s.origin, ent->s.origin, ent->pos1 );
|
|
VectorNormalize( ent->pos1 );
|
|
// find angles
|
|
vectoangles( ent->pos1, ent->r.currentAngles );
|
|
// copy over to other angles
|
|
VectorCopy( ent->r.currentAngles, ent->s.angles );
|
|
VectorCopy( ent->r.currentAngles, ent->s.apos.trBase );
|
|
}
|
|
else
|
|
{
|
|
ent->pos1[0] = ent->pos1[1] = 0.0;
|
|
ent->pos1[2] = 1.0;
|
|
}
|
|
|
|
ent->think = fx_think;
|
|
ent->nextthink = level.time + 100;
|
|
}
|
|
|
|
|