jkxr/Projects/Android/jni/OpenJK/codemp/game/g_misc.c

3648 lines
91 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/>.
===========================================================================
*/
// g_misc.c
#include "g_local.h"
#include "ghoul2/G2.h"
#include "ai_main.h" //for the g2animents
#define HOLOCRON_RESPAWN_TIME 30000
#define MAX_AMMO_GIVE 2
#define STATION_RECHARGE_TIME 100
void HolocronThink(gentity_t *ent);
/*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_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_camp( gentity_t *self ) {
G_SetOrigin( self, self->s.origin );
}
/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_null( gentity_t *self ) {
G_FreeEntity( self );
}
/*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 );
}
/*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
"light" overrides the default 300 intensity.
Nonlinear checkbox gives inverse square falloff instead of linear
Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
*/
/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
Non-displayed light.
"light" overrides the default 300 intensity. - affects size
a negative "light" will subtract the light's color
'Linear' checkbox gives linear falloff instead of inverse square
'noIncidence' checkbox makes lighting smoother
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
"scale" multiplier for the light intensity - does not affect size (default 1)
greater than 1 is brighter, between 0 and 1 is dimmer.
"color" sets the light's color
"targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag)
"style" to specify a specify light style, even for switchable lights!
"style_off" light style to use when switched off (Only for switchable lights)
1 FLICKER (first variety)
2 SLOW STRONG PULSE
3 CANDLE (first variety)
4 FAST STROBE
5 GENTLE PULSE 1
6 FLICKER (second variety)
7 CANDLE (second variety)
8 CANDLE (third variety)
9 SLOW STROBE (fourth variety)
10 FLUORESCENT FLICKER
11 SLOW PULSE NOT FADE TO BLACK
12 FAST PULSE FOR JEREMY
13 Test Blending
*/
static void misc_lightstyle_set ( gentity_t *ent)
{
const int mLightStyle = ent->count;
const int mLightSwitchStyle = ent->bounceCount;
const int mLightOffStyle = ent->fly_sound_debounce_time;
if (!ent->alt_fire)
{ //turn off
if (mLightOffStyle) //i have a light style i'd like to use when off
{
char lightstyle[32];
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
}else
{
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
}
}
else
{ //Turn myself on now
if (mLightSwitchStyle) //i have a light style i'd like to use when on
{
char lightstyle[32];
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
trap->GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
}
else
{
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
trap->SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
}
}
}
void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
G_ActivateBehavior(ent,BSET_USE);
ent->alt_fire = !ent->alt_fire; //toggle
misc_lightstyle_set (ent);
}
void SP_light( gentity_t *self ) {
if (!self->targetname )
{//if i don't have a light style switch, the i go away
G_FreeEntity( self );
return;
}
G_SpawnInt( "style", "0", &self->count );
G_SpawnInt( "switch_style", "0", &self->bounceCount );
G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
G_SetOrigin( self, self->s.origin );
trap->LinkEntity( (sharedEntity_t *)self );
self->use = misc_dlight_use;
self->s.eType = ET_GENERAL;
self->alt_fire = qfalse;
self->r.svFlags |= SVF_NOCLIENT;
if ( !(self->spawnflags & 4) )
{ //turn myself on now
self->alt_fire = qtrue;
}
misc_lightstyle_set (self);
}
/*
=================================================================================
TELEPORTERS
=================================================================================
*/
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
gentity_t *tent;
qboolean isNPC = qfalse;
qboolean noAngles;
if (player->s.eType == ET_NPC)
{
isNPC = qtrue;
}
noAngles = (angles[0] > 999999.0) ? qtrue : qfalse;
// use temp events at source and destination to prevent the effect
// from getting dropped by a second player event
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
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 ((sharedEntity_t *)player);
VectorCopy ( origin, player->client->ps.origin );
player->client->ps.origin[2] += 1;
// spit the player out
if ( !noAngles ) {
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;
// set angles
SetClientViewAngle( player, angles );
}
// toggle the teleport bit so the client knows to not lerp
player->client->ps.eFlags ^= EF_TELEPORT_BIT;
// kill anything at the destination
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
G_KillBox (player);
}
// save results of pmove
BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
if (isNPC)
{
player->s.eType = ET_NPC;
}
// use the precise origin for linking
VectorCopy( player->client->ps.origin, player->r.currentOrigin );
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
trap->LinkEntity ((sharedEntity_t *)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)
"model" arbitrary .md3 or .ase file to display
turns into map triangles - not solid
*/
void SP_misc_model( gentity_t *ent ) {
#if 0
ent->s.modelindex = G_ModelIndex( ent->model );
VectorSet (ent->r.mins, -16, -16, -16);
VectorSet (ent->r.maxs, 16, 16, 16);
trap->LinkEntity ((sharedEntity_t *)ent);
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
#else
G_FreeEntity( ent );
#endif
}
/*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
"model" arbitrary .md3 file to display
"zoffset" units to offset vertical culling position by, can be
negative or positive. This does not affect the actual
position of the model, only the culling position. Use
it for models with stupid origins that go below the
ground and whatnot.
"modelscale" scale on all axis
"modelscale_vec" scale difference axis
loaded as a model in the renderer - does not take up precious
bsp space!
*/
void SP_misc_model_static(gentity_t *ent)
{
G_FreeEntity( ent );
}
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
AUTOANIMATE - Will cycle it's anim
DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
PLAYER_USE - Player can use it with the use button
NO_EXPLOSION - By default, will explode when it dies...this is your override.
"model" arbitrary .md3 file to display
"health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
"targetname" when used, dies and displays damagemodel, if any (if not, removes itself)
"target" What to use when it dies
"target2" What to use when it's repaired
"target3" What to use when it's used while it's broken
"paintarget" target to fire when hit (but not destroyed)
"count" the amount of armor/health/ammo given (default 50)
"gravity" if set to 1, this will be affected by gravity
"radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
Damage: default is none
"splashDamage" - damage to do (will make it explode on death)
"splashRadius" - radius for above damage
"team" - This cannot take damage from members of this team:
"player"
"neutral"
"enemy"
"material" - default is "8 - MAT_NONE" - choose from this list:
0 = MAT_METAL (grey metal)
1 = MAT_GLASS
2 = MAT_ELECTRICAL (sparks only)
3 = MAT_ELEC_METAL (METAL chunks and sparks)
4 = MAT_DRK_STONE (brown stone chunks)
5 = MAT_LT_STONE (tan stone chunks)
6 = MAT_GLASS_METAL (glass and METAL chunks)
7 = MAT_METAL2 (blue/grey metal)
8 = MAT_NONE (no chunks-DEFAULT)
9 = MAT_GREY_STONE (grey colored stone)
10 = MAT_METAL3 (METAL and METAL2 chunk combo)
11 = MAT_CRATE1 (yellow multi-colored crate chunks)
12 = MAT_GRATE1 (grate chunks--looks horrible right now)
13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
14 = MAT_CRATE2 (red multi-colored crate chunks)
15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
FIXME/TODO:
set size better?
multiple damage models?
custom explosion effect/sound?
*/
void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor );
void misc_model_breakable_init( gentity_t *ent );
void SP_misc_model_breakable( gentity_t *ent )
{
float grav;
G_SpawnInt( "material", "8", (int*)&ent->material );
G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
misc_model_breakable_init( ent );
if ( !ent->r.mins[0] && !ent->r.mins[1] && !ent->r.mins[2] )
{
VectorSet (ent->r.mins, -16, -16, -16);
}
if ( !ent->r.maxs[0] && !ent->r.maxs[1] && !ent->r.maxs[2] )
{
VectorSet (ent->r.maxs, 16, 16, 16);
}
G_SetOrigin( ent, ent->s.origin );
G_SetAngles( ent, ent->s.angles );
trap->LinkEntity ((sharedEntity_t *)ent);
if ( ent->spawnflags & 128 )
{//Can be used by the player's BUTTON_USE
ent->r.svFlags |= SVF_PLAYER_USABLE;
}
ent->s.teamowner = 0;
G_SpawnFloat( "gravity", "0", &grav );
if ( grav )
{//affected by gravity
G_SetAngles( ent, ent->s.angles );
G_SetOrigin( ent, ent->r.currentOrigin );
misc_model_breakable_gravity_init( ent, qtrue );
}
}
void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
{
trace_t tr;
vec3_t top, bottom;
ent->s.eType = ET_GENERAL;
//ent->s.eFlags |= EF_BOUNCE_HALF; // FIXME
ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
ent->physicsBounce = ent->mass = VectorLength( ent->r.maxs ) + VectorLength( ent->r.mins );
//drop to floor
if ( dropToFloor )
{
VectorCopy( ent->r.currentOrigin, top );
top[2] += 1;
VectorCopy( ent->r.currentOrigin, bottom );
bottom[2] = MIN_WORLD_COORD;
trap->Trace( &tr, top, ent->r.mins, ent->r.maxs, bottom, ent->s.number, MASK_NPCSOLID, qfalse, 0, 0 );
if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
{
G_SetOrigin( ent, tr.endpos );
trap->LinkEntity( (sharedEntity_t *)ent );
}
}
else
{
G_SetOrigin( ent, ent->r.currentOrigin );
trap->LinkEntity( (sharedEntity_t *)ent );
}
//set up for object thinking
if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
{//not moving
ent->s.pos.trType = TR_STATIONARY;
}
else
{
ent->s.pos.trType = TR_GRAVITY;
}
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
VectorClear( ent->s.pos.trDelta );
ent->s.pos.trTime = level.time;
if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
{//not moving
ent->s.apos.trType = TR_STATIONARY;
}
else
{
ent->s.apos.trType = TR_LINEAR;
}
VectorCopy( ent->r.currentAngles, ent->s.apos.trBase );
VectorClear( ent->s.apos.trDelta );
ent->s.apos.trTime = level.time;
}
void misc_model_breakable_init( gentity_t *ent )
{
if (!ent->model) {
trap->Error( ERR_DROP, "no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2] );
}
//Main model
ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
if ( ent->spawnflags & 1 )
{//Blocks movement
ent->r.contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
}
else if ( ent->health )
{//Can only be shot
ent->r.contents = CONTENTS_SHOTCLIP;
}
// TODO: fix using
// TODO: fix health/dying funcs
}
/*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->r.mins, -16, -16, -16);
// VectorSet (ent->r.maxs, 16, 16, 16);
trap->LinkEntity ((sharedEntity_t *)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 ) {
trap->Print( "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.powerups = 0;
}
else {
ent->s.powerups = 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 ((sharedEntity_t *)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 ((sharedEntity_t *)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( (sharedEntity_t *)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; //rww - also not referenced anywhere.
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 ((sharedEntity_t *)ent);
trap->SetActiveSubBSP(ent->s.modelindex);
G_SpawnEntitiesFromString(qtrue);
trap->SetActiveSubBSP(-1);
level.mBSPInstanceDepth--;
//level.mFilter[0] = level.mTeamFilter[0] = 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) ? NOVEHDMG
NOVEHDMG - don't damage vehicles upon impact with this terrain
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, located in heightmaps/*.png. (must be PNG format)
terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/*.terrain - default is grassyhills)
instanceDef - defines which bsp instances appear
miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/*.miscents)
densityMap - how dense the client models are packed
*/
void AddSpawnField(char *field, char *value);
#define MAX_INSTANCE_TYPES 16
void SP_terrain(gentity_t *ent)
{
G_FreeEntity (ent);
}
//rww - Called by skyportal entities. This will check through entities and flag them
//as portal ents if they are in the same pvs as a skyportal entity and pass
//a direct point trace check between origins. I really wanted to use an eFlag for
//flagging portal entities, but too many entities like to reset their eFlags.
//Note that this was not part of the original wolf sky portal stuff.
void G_PortalifyEntities(gentity_t *ent)
{
int i = 0;
gentity_t *scan = NULL;
while (i < MAX_GENTITIES)
{
scan = &g_entities[i];
if (scan && scan->inuse && scan->s.number != ent->s.number && trap->InPVS(ent->s.origin, scan->r.currentOrigin))
{
trace_t tr;
trap->Trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->r.currentOrigin, ent->s.number, CONTENTS_SOLID, qfalse, 0, 0);
if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
{
if (!scan->client || scan->s.eType == ET_NPC)
{ //making a client a portal entity would be bad.
scan->s.isPortalEnt = qtrue; //he's flagged now
}
}
}
i++;
}
ent->think = G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
ent->nextthink = level.time;
}
/*QUAKED misc_skyportal_orient (.6 .7 .7) (-8 -8 0) (8 8 16)
point from which to orient the sky portal cam in relation
to the regular view position.
"modelscale" the scale at which to scale positions
*/
void SP_misc_skyportal_orient (gentity_t *ent)
{
G_FreeEntity(ent);
}
/*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
"fov" for the skybox default is 80
To have the portal sky fogged, enter any of the following values:
"onlyfoghere" if non-0 allows you to set a global fog, but will only use that fog within this sky portal.
Also note that entities in the same PVS and visible (via point trace) from this
object will be flagged as portal entities. This means they will be sent and
updated from the server for every client every update regardless of where
they are, and they will essentially be added to the scene twice if the client
is in the same PVS as them (only once otherwise, but still once no matter
where the client is). In other words, don't go overboard with it or everything
will explode.
*/
void SP_misc_skyportal (gentity_t *ent)
{
char *fov;
vec3_t fogv; //----(SA)
int fogn; //----(SA)
int fogf; //----(SA)
int isfog = 0; // (SA)
float fov_x;
G_SpawnString ("fov", "80", &fov);
fov_x = atof (fov);
isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
isfog += G_SpawnInt ("fognear", "0", &fogn);
isfog += G_SpawnInt ("fogfar", "300", &fogf);
trap->SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %.1f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], fov_x, (int)isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
ent->think = G_PortalifyEntities;
ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
}
/*QUAKED misc_holocron (0 0 1) (-8 -8 -8) (8 8 8)
count Set to type of holocron (based on force power value)
HEAL = 0
JUMP = 1
SPEED = 2
PUSH = 3
PULL = 4
TELEPATHY = 5
GRIP = 6
LIGHTNING = 7
RAGE = 8
PROTECT = 9
ABSORB = 10
TEAM HEAL = 11
TEAM FORCE = 12
DRAIN = 13
SEE = 14
SABERATTACK = 15
SABERDEFEND = 16
SABERTHROW = 17
*/
/*char *holocronTypeModels[] = {
"models/chunks/rock/rock_big.md3",//FP_HEAL,
"models/chunks/rock/rock_big.md3",//FP_LEVITATION,
"models/chunks/rock/rock_big.md3",//FP_SPEED,
"models/chunks/rock/rock_big.md3",//FP_PUSH,
"models/chunks/rock/rock_big.md3",//FP_PULL,
"models/chunks/rock/rock_big.md3",//FP_TELEPATHY,
"models/chunks/rock/rock_big.md3",//FP_GRIP,
"models/chunks/rock/rock_big.md3",//FP_LIGHTNING,
"models/chunks/rock/rock_big.md3",//FP_RAGE,
"models/chunks/rock/rock_big.md3",//FP_PROTECT,
"models/chunks/rock/rock_big.md3",//FP_ABSORB,
"models/chunks/rock/rock_big.md3",//FP_TEAM_HEAL,
"models/chunks/rock/rock_big.md3",//FP_TEAM_FORCE,
"models/chunks/rock/rock_big.md3",//FP_DRAIN,
"models/chunks/rock/rock_big.md3",//FP_SEE
"models/chunks/rock/rock_big.md3",//FP_SABER_OFFENSE
"models/chunks/rock/rock_big.md3",//FP_SABER_DEFENSE
"models/chunks/rock/rock_big.md3"//FP_SABERTHROW
};*/
void HolocronRespawn(gentity_t *self)
{
self->s.modelindex = (self->count - 128);
}
void HolocronPopOut(gentity_t *self)
{
if (Q_irand(1, 10) < 5)
{
self->s.pos.trDelta[0] = 150 + Q_irand(1, 100);
}
else
{
self->s.pos.trDelta[0] = -150 - Q_irand(1, 100);
}
if (Q_irand(1, 10) < 5)
{
self->s.pos.trDelta[1] = 150 + Q_irand(1, 100);
}
else
{
self->s.pos.trDelta[1] = -150 - Q_irand(1, 100);
}
self->s.pos.trDelta[2] = 150 + Q_irand(1, 100);
}
void HolocronTouch(gentity_t *self, gentity_t *other, trace_t *trace)
{
int i = 0;
int othercarrying = 0;
float time_lowest = 0;
int index_lowest = -1;
int hasall = 1;
int forceReselect = WP_NONE;
if (trace)
{
self->s.groundEntityNum = trace->entityNum;
}
if (!other || !other->client || other->health < 1)
{
return;
}
if (!self->s.modelindex)
{
return;
}
if (self->enemy)
{
return;
}
if (other->client->ps.holocronsCarried[self->count])
{
return;
}
if (other->client->ps.holocronCantTouch == self->s.number && other->client->ps.holocronCantTouchTime > level.time)
{
return;
}
while (i < NUM_FORCE_POWERS)
{
if (other->client->ps.holocronsCarried[i])
{
othercarrying++;
if (index_lowest == -1 || other->client->ps.holocronsCarried[i] < time_lowest)
{
index_lowest = i;
time_lowest = other->client->ps.holocronsCarried[i];
}
}
else if (i != self->count)
{
hasall = 0;
}
i++;
}
if (hasall)
{ //once we pick up this holocron we'll have all of them, so give us super special best prize!
//trap->Print("You deserve a pat on the back.\n");
}
if (!(other->client->ps.fd.forcePowersActive & (1 << other->client->ps.fd.forcePowerSelected)))
{ //If the player isn't using his currently selected force power, select this one
if (self->count != FP_SABER_OFFENSE && self->count != FP_SABER_DEFENSE && self->count != FP_SABERTHROW && self->count != FP_LEVITATION)
{
other->client->ps.fd.forcePowerSelected = self->count;
}
}
if (g_maxHolocronCarry.integer && othercarrying >= g_maxHolocronCarry.integer)
{ //make the oldest holocron carried by the player pop out to make room for this one
other->client->ps.holocronsCarried[index_lowest] = 0;
/*
if (index_lowest == FP_SABER_OFFENSE && !HasSetSaberOnly())
{ //you lost your saberattack holocron, so no more saber for you
other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON);
other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
if (other->client->ps.weapon == WP_SABER)
{
forceReselect = WP_SABER;
}
}
*/
//NOTE: No longer valid as we are now always giving a force level 1 saber attack level in holocron
}
//G_Sound(other, CHAN_AUTO, G_SoundIndex("sound/weapons/w_pkup.wav"));
G_AddEvent( other, EV_ITEM_PICKUP, self->s.number );
other->client->ps.holocronsCarried[self->count] = level.time;
self->s.modelindex = 0;
self->enemy = other;
self->pos2[0] = 1;
self->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
/*
if (self->count == FP_SABER_OFFENSE && !HasSetSaberOnly())
{ //player gets a saber
other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_SABER);
other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_STUN_BATON);
if (other->client->ps.weapon == WP_STUN_BATON)
{
forceReselect = WP_STUN_BATON;
}
}
*/
if (forceReselect != WP_NONE)
{
G_AddEvent(other, EV_NOAMMO, forceReselect);
}
//trap->Print("DON'T TOUCH ME\n");
}
void HolocronThink(gentity_t *ent)
{
if (ent->pos2[0] && (!ent->enemy || !ent->enemy->client || ent->enemy->health < 1))
{
if (ent->enemy && ent->enemy->client)
{
HolocronRespawn(ent);
VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
//copy to person carrying's origin before popping out of them
HolocronPopOut(ent);
ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
ent->enemy = NULL;
goto justthink;
}
}
else if (ent->pos2[0] && ent->enemy && ent->enemy->client)
{
ent->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
}
if (ent->enemy && ent->enemy->client)
{
if (!ent->enemy->client->ps.holocronsCarried[ent->count])
{
ent->enemy->client->ps.holocronCantTouch = ent->s.number;
ent->enemy->client->ps.holocronCantTouchTime = level.time + 5000;
HolocronRespawn(ent);
VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
//copy to person carrying's origin before popping out of them
HolocronPopOut(ent);
ent->enemy = NULL;
goto justthink;
}
if (!ent->enemy->inuse || (ent->enemy->client && ent->enemy->client->ps.fallingToDeath))
{
if (ent->enemy->inuse && ent->enemy->client)
{
ent->enemy->client->ps.holocronBits &= ~(1 << ent->count);
ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
}
ent->enemy = NULL;
HolocronRespawn(ent);
VectorCopy(ent->s.origin2, ent->s.pos.trBase);
VectorCopy(ent->s.origin2, ent->s.origin);
VectorCopy(ent->s.origin2, ent->r.currentOrigin);
ent->s.pos.trTime = level.time;
ent->pos2[0] = 0;
trap->LinkEntity((sharedEntity_t *)ent);
goto justthink;
}
}
if (ent->pos2[0] && ent->pos2[1] < level.time)
{ //isn't in original place and has been there for (HOLOCRON_RESPAWN_TIME) seconds without being picked up, so respawn
VectorCopy(ent->s.origin2, ent->s.pos.trBase);
VectorCopy(ent->s.origin2, ent->s.origin);
VectorCopy(ent->s.origin2, ent->r.currentOrigin);
ent->s.pos.trTime = level.time;
ent->pos2[0] = 0;
trap->LinkEntity((sharedEntity_t *)ent);
}
justthink:
ent->nextthink = level.time + 50;
if (ent->s.pos.trDelta[0] || ent->s.pos.trDelta[1] || ent->s.pos.trDelta[2])
{
G_RunObject(ent);
}
}
void SP_misc_holocron(gentity_t *ent)
{
vec3_t dest;
trace_t tr;
if (level.gametype != GT_HOLOCRON)
{
G_FreeEntity(ent);
return;
}
if (HasSetSaberOnly())
{
if (ent->count == FP_SABER_OFFENSE ||
ent->count == FP_SABER_DEFENSE ||
ent->count == FP_SABERTHROW)
{ //having saber holocrons in saber only mode is pointless
G_FreeEntity(ent);
return;
}
}
ent->s.isJediMaster = qtrue;
VectorSet( ent->r.maxs, 8, 8, 8 );
VectorSet( ent->r.mins, -8, -8, -8 );
ent->s.origin[2] += 0.1f;
ent->r.maxs[2] -= 0.1f;
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
trap->Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID, qfalse, 0, 0 );
if ( tr.startsolid )
{
trap->Print ("SP_misc_holocron: misc_holocron startsolid at %s\n", vtos(ent->s.origin));
G_FreeEntity( ent );
return;
}
//add the 0.1 back after the trace
ent->r.maxs[2] += 0.1f;
// allow to ride movers
// ent->s.groundEntityNum = tr.entityNum;
G_SetOrigin( ent, tr.endpos );
if (ent->count < 0)
{
ent->count = 0;
}
if (ent->count >= NUM_FORCE_POWERS)
{
ent->count = NUM_FORCE_POWERS-1;
}
/*
if (g_forcePowerDisable.integer &&
(g_forcePowerDisable.integer & (1 << ent->count)))
{
G_FreeEntity(ent);
return;
}
*/
//No longer doing this, causing too many complaints about accidentally setting no force powers at all
//and starting a holocron game (making it basically just FFA)
ent->enemy = NULL;
ent->flags = FL_BOUNCE_HALF;
ent->s.modelindex = (ent->count - 128);//G_ModelIndex(holocronTypeModels[ent->count]);
ent->s.eType = ET_HOLOCRON;
ent->s.pos.trType = TR_GRAVITY;
ent->s.pos.trTime = level.time;
ent->r.contents = CONTENTS_TRIGGER;
ent->clipmask = MASK_SOLID;
ent->s.trickedentindex4 = ent->count;
if (forcePowerDarkLight[ent->count] == FORCE_DARKSIDE)
{
ent->s.trickedentindex3 = 1;
}
else if (forcePowerDarkLight[ent->count] == FORCE_LIGHTSIDE)
{
ent->s.trickedentindex3 = 2;
}
else
{
ent->s.trickedentindex3 = 3;
}
ent->physicsObject = qtrue;
VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot
ent->touch = HolocronTouch;
trap->LinkEntity((sharedEntity_t *)ent);
ent->think = HolocronThink;
ent->nextthink = level.time + 50;
}
/*
======================================================================
SHOOTERS
======================================================================
*/
void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
vec3_t dir;
float deg;
vec3_t up, right;
// see if we have a target
if ( ent->enemy ) {
VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
VectorNormalize( dir );
} else {
VectorCopy( ent->movedir, dir );
}
// randomize a bit
PerpendicularVector( up, dir );
CrossProduct( up, dir, right );
deg = Q_flrand(-1.0f, 1.0f) * ent->random;
VectorMA( dir, deg, up, dir );
deg = Q_flrand(-1.0f, 1.0f) * ent->random;
VectorMA( dir, deg, right, dir );
VectorNormalize( dir );
switch ( ent->s.weapon ) {
case WP_BLASTER:
WP_FireBlasterMissile( ent, ent->s.origin, dir, qfalse );
break;
}
G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
}
static void InitShooter_Finish( gentity_t *ent ) {
ent->enemy = G_PickTarget( ent->target );
ent->think = 0;
ent->nextthink = 0;
}
void InitShooter( gentity_t *ent, int weapon ) {
ent->use = Use_Shooter;
ent->s.weapon = weapon;
RegisterItem( BG_FindItemForWeapon( weapon ) );
G_SetMovedir( ent->s.angles, ent->movedir );
if ( !ent->random ) {
ent->random = 1.0;
}
ent->random = sin( M_PI * ent->random / 180 );
// target might be a moving object, so we can't set movedir for it
if ( ent->target ) {
ent->think = InitShooter_Finish;
ent->nextthink = level.time + 500;
}
trap->LinkEntity( (sharedEntity_t *)ent );
}
/*QUAKED shooter_blaster (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_blaster( gentity_t *ent ) {
InitShooter( ent, WP_BLASTER);
}
void check_recharge(gentity_t *ent)
{
if (ent->fly_sound_debounce_time < level.time ||
!ent->activator ||
!ent->activator->client ||
!(ent->activator->client->pers.cmd.buttons & BUTTON_USE))
{
if (ent->activator)
{
G_Sound(ent, CHAN_AUTO, ent->genericValue7);
}
ent->s.loopSound = 0;
ent->s.loopIsSoundset = qfalse;
ent->activator = NULL;
ent->fly_sound_debounce_time = 0;
}
if (!ent->activator)
{ //don't recharge during use
if (ent->genericValue8 < level.time)
{
if (ent->count < ent->genericValue4)
{
ent->count++;
}
ent->genericValue8 = level.time + ent->genericValue5;
}
}
ent->s.health = ent->count; //the "health bar" is gonna be how full we are
ent->nextthink = level.time;
}
/*
================
EnergyShieldStationSettings
================
*/
void EnergyShieldStationSettings(gentity_t *ent)
{
G_SpawnInt( "count", "200", &ent->count );
G_SpawnInt("chargerate", "0", &ent->genericValue5);
if (!ent->genericValue5)
{
ent->genericValue5 = STATION_RECHARGE_TIME;
}
}
/*
================
shield_power_converter_use
================
*/
void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
int dif,add;
int stop = 1;
if (!activator || !activator->client)
{
return;
}
if ( level.gametype == GT_SIEGE
&& other
&& other->client
&& other->client->siegeClass )
{
if ( !bgSiegeClasses[other->client->siegeClass].maxarmor )
{//can't use it!
G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
return;
}
}
if (self->setTime < level.time)
{
int maxArmor;
if (!self->s.loopSound)
{
self->s.loopSound = G_SoundIndex("sound/interface/shieldcon_run");
self->s.loopIsSoundset = qfalse;
}
self->setTime = level.time + 100;
if ( level.gametype == GT_SIEGE
&& other
&& other->client
&& other->client->siegeClass != -1 )
{
maxArmor = bgSiegeClasses[other->client->siegeClass].maxarmor;
}
else
{
maxArmor = activator->client->ps.stats[STAT_MAX_HEALTH];
}
dif = maxArmor - activator->client->ps.stats[STAT_ARMOR];
if (dif > 0) // Already at full armor?
{
if (dif >MAX_AMMO_GIVE)
{
add = MAX_AMMO_GIVE;
}
else
{
add = dif;
}
if (self->count<add)
{
add = self->count;
}
if (!self->genericValue12)
{
self->count -= add;
}
if (self->count <= 0)
{
self->setTime = 0;
}
stop = 0;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
activator->client->ps.stats[STAT_ARMOR] += add;
}
}
if (stop || self->count <= 0)
{
if (self->s.loopSound && self->setTime < level.time)
{
if (self->count <= 0)
{
G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
}
else
{
G_Sound(self, CHAN_AUTO, self->genericValue7);
}
}
self->s.loopSound = 0;
self->s.loopIsSoundset = qfalse;
if (self->setTime < level.time)
{
self->setTime = level.time + self->genericValue5+100;
}
}
}
//dispense generic ammo
void ammo_generic_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
int /*dif,*/ add;
//int ammoType;
int stop = 1;
if (!activator || !activator->client)
{
return;
}
if (self->setTime < level.time)
{
qboolean gaveSome = qfalse;
/*
while (i < 3)
{
if (!self->s.loopSound)
{
self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
self->s.loopIsSoundset = qfalse;
}
self->setTime = level.time + 100;
//dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->client->ps.stats[STAT_ARMOR];
switch (i)
{ //don't give rockets I guess
case 0:
ammoType = AMMO_BLASTER;
break;
case 1:
ammoType = AMMO_POWERCELL;
break;
case 2:
ammoType = AMMO_METAL_BOLTS;
break;
default:
ammoType = -1;
break;
}
if (ammoType != -1)
{
dif = ammoData[ammoType].max - activator->client->ps.ammo[ammoType];
}
else
{
dif = 0;
}
if (dif > 0)
{ //only give if not full
if (dif > MAX_AMMO_GIVE)
{
add = MAX_AMMO_GIVE;
}
else
{
add = dif;
}
if (self->count<add)
{
add = self->count;
}
self->count -= add;
if (self->count <= 0)
{
self->setTime = 0;
break;
}
stop = 0;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
activator->client->ps.ammo[ammoType] += add;
}
i++;
}
*/
int i = AMMO_BLASTER;
if (!self->s.loopSound)
{
self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
self->s.loopIsSoundset = qfalse;
}
//self->setTime = level.time + 100;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
while (i < AMMO_MAX)
{
add = ammoData[i].max*0.05;
if (add < 1)
{
add = 1;
}
if ( ( (activator->client->ps.eFlags & EF_DOUBLE_AMMO) && (activator->client->ps.ammo[i] < ammoData[i].max*2)) ||
( activator->client->ps.ammo[i] < ammoData[i].max ) )
{
gaveSome = qtrue;
if ( level.gametype == GT_SIEGE && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
{ //this stuff is already a freaking mess, so..
gaveSome = qfalse;
}
activator->client->ps.ammo[i] += add;
if ( level.gametype == GT_SIEGE && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
{ // fixme - this should SERIOUSLY be externed.
activator->client->ps.ammo[i] = 10;
}
else if ( activator->client->ps.eFlags & EF_DOUBLE_AMMO )
{
if (activator->client->ps.ammo[i] >= ammoData[i].max * 2)
{ // yuck.
activator->client->ps.ammo[i] = ammoData[i].max * 2;
}
else
{
stop = 0;
}
}
else
{
if (activator->client->ps.ammo[i] >= ammoData[i].max)
{
activator->client->ps.ammo[i] = ammoData[i].max;
}
else
{
stop = 0;
}
}
}
i++;
if (!self->genericValue12 && gaveSome)
{
int sub = (add*0.2);
if (sub < 1)
{
sub = 1;
}
self->count -= sub;
if (self->count <= 0)
{
self->count = 0;
stop = 1;
break;
}
}
}
}
if (stop || self->count <= 0)
{
if (self->s.loopSound && self->setTime < level.time)
{
if (self->count <= 0)
{
G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/ammocon_empty"));
}
else
{
G_Sound(self, CHAN_AUTO, self->genericValue7);
}
}
self->s.loopSound = 0;
self->s.loopIsSoundset = qfalse;
if (self->setTime < level.time)
{
self->setTime = level.time + self->genericValue5+100;
}
}
}
/*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
model="/models/items/a_pwr_converter.md3"
Gives generic ammo when used
"count" - max charge value (default 200)
"chargerate" - rechage 1 point every this many milliseconds (default 2000)
"nodrain" - don't drain power from station if 1
*/
void SP_misc_ammo_floor_unit(gentity_t *ent)
{
vec3_t dest;
trace_t tr;
VectorSet( ent->r.mins, -16, -16, 0 );
VectorSet( ent->r.maxs, 16, 16, 40 );
ent->s.origin[2] += 0.1f;
ent->r.maxs[2] -= 0.1f;
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
trap->Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID, qfalse, 0, 0 );
if ( tr.startsolid )
{
trap->Print ("SP_misc_ammo_floor_unit: misc_ammo_floor_unit startsolid at %s\n", vtos(ent->s.origin));
G_FreeEntity( ent );
return;
}
//add the 0.1 back after the trace
ent->r.maxs[2] += 0.1f;
// allow to ride movers
ent->s.groundEntityNum = tr.entityNum;
G_SetOrigin( ent, tr.endpos );
if (!ent->health)
{
ent->health = 60;
}
if (!ent->model || !ent->model[0])
{
ent->model = "/models/items/a_pwr_converter.md3";
}
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eFlags = 0;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->r.contents = CONTENTS_SOLID;
ent->clipmask = MASK_SOLID;
EnergyShieldStationSettings(ent);
ent->genericValue4 = ent->count; //initial value
ent->think = check_recharge;
G_SpawnInt("nodrain", "0", &ent->genericValue12);
if (!ent->genericValue12)
{
ent->s.maxhealth = ent->s.health = ent->count;
}
ent->s.shouldtarget = qtrue;
ent->s.teamowner = 0;
ent->s.owner = ENTITYNUM_NONE;
ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
ent->use = ammo_generic_power_converter_use;
VectorCopy( ent->s.angles, ent->s.apos.trBase );
trap->LinkEntity ((sharedEntity_t *)ent);
G_SoundIndex("sound/interface/ammocon_run");
ent->genericValue7 = G_SoundIndex("sound/interface/ammocon_done");
G_SoundIndex("sound/interface/ammocon_empty");
if (level.gametype == GT_SIEGE)
{ //show on radar from everywhere
ent->r.svFlags |= SVF_BROADCAST;
ent->s.eFlags |= EF_RADAROBJECT;
ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/weapon_recharge");
}
}
/*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
model="/models/items/a_shield_converter.md3"
Gives shield energy when used.
"count" - max charge value (default 50)
"chargerate" - rechage 1 point every this many milliseconds (default 3000)
"nodrain" - don't drain power from me
*/
void SP_misc_shield_floor_unit( gentity_t *ent )
{
vec3_t dest;
trace_t tr;
if (level.gametype != GT_CTF &&
level.gametype != GT_CTY &&
level.gametype != GT_SIEGE)
{
G_FreeEntity( ent );
return;
}
VectorSet( ent->r.mins, -16, -16, 0 );
VectorSet( ent->r.maxs, 16, 16, 40 );
ent->s.origin[2] += 0.1f;
ent->r.maxs[2] -= 0.1f;
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
trap->Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID, qfalse, 0, 0 );
if ( tr.startsolid )
{
trap->Print ("SP_misc_shield_floor_unit: misc_shield_floor_unit startsolid at %s\n", vtos(ent->s.origin));
G_FreeEntity( ent );
return;
}
//add the 0.1 back after the trace
ent->r.maxs[2] += 0.1f;
// allow to ride movers
ent->s.groundEntityNum = tr.entityNum;
G_SetOrigin( ent, tr.endpos );
if (!ent->health)
{
ent->health = 60;
}
if (!ent->model || !ent->model[0])
{
ent->model = "/models/items/a_shield_converter.md3";
}
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eFlags = 0;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->r.contents = CONTENTS_SOLID;
ent->clipmask = MASK_SOLID;
EnergyShieldStationSettings(ent);
ent->genericValue4 = ent->count; //initial value
ent->think = check_recharge;
G_SpawnInt("nodrain", "0", &ent->genericValue12);
if (!ent->genericValue12)
{
ent->s.maxhealth = ent->s.health = ent->count;
}
ent->s.shouldtarget = qtrue;
ent->s.teamowner = 0;
ent->s.owner = ENTITYNUM_NONE;
ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
ent->use = shield_power_converter_use;
VectorCopy( ent->s.angles, ent->s.apos.trBase );
trap->LinkEntity ((sharedEntity_t *)ent);
G_SoundIndex("sound/interface/shieldcon_run");
ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
G_SoundIndex("sound/interface/shieldcon_empty");
if (level.gametype == GT_SIEGE)
{ //show on radar from everywhere
ent->r.svFlags |= SVF_BROADCAST;
ent->s.eFlags |= EF_RADAROBJECT;
ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/shield_recharge");
}
}
/*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
model="models/items/psd_big.md3"
Gives shield energy when used.
"count" - the amount of ammo given when used (default 200)
*/
//------------------------------------------------------------
void SP_misc_model_shield_power_converter( gentity_t *ent )
{
if (!ent->health)
{
ent->health = 60;
}
VectorSet (ent->r.mins, -16, -16, -16);
VectorSet (ent->r.maxs, 16, 16, 16);
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eFlags = 0;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->r.contents = CONTENTS_SOLID;
ent->clipmask = MASK_SOLID;
EnergyShieldStationSettings(ent);
ent->genericValue4 = ent->count; //initial value
ent->think = check_recharge;
ent->s.maxhealth = ent->s.health = ent->count;
ent->s.shouldtarget = qtrue;
ent->s.teamowner = 0;
ent->s.owner = ENTITYNUM_NONE;
ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
ent->use = shield_power_converter_use;
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
trap->LinkEntity ((sharedEntity_t *)ent);
//G_SoundIndex("sound/movers/objects/useshieldstation.wav");
ent->s.modelindex2 = G_ModelIndex("/models/items/psd_big.md3"); // Precache model
}
/*
================
EnergyAmmoShieldStationSettings
================
*/
void EnergyAmmoStationSettings(gentity_t *ent)
{
G_SpawnInt( "count", "200", &ent->count );
}
/*
================
ammo_power_converter_use
================
*/
void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
int add = 0.0f;//,highest;
// int difBlaster,difPowerCell,difMetalBolts;
int stop = 1;
if (!activator || !activator->client)
{
return;
}
if (self->setTime < level.time)
{
if (!self->s.loopSound)
{
self->s.loopSound = G_SoundIndex("sound/player/pickupshield.wav");
}
self->setTime = level.time + 100;
if (self->count) // Has it got any power left?
{
int i = AMMO_BLASTER;
while (i < AMMO_MAX)
{
add = ammoData[i].max*0.1;
if (add < 1)
{
add = 1;
}
if (activator->client->ps.ammo[i] < ammoData[i].max)
{
activator->client->ps.ammo[i] += add;
if (activator->client->ps.ammo[i] > ammoData[i].max)
{
activator->client->ps.ammo[i] = ammoData[i].max;
}
}
i++;
}
if (!self->genericValue12)
{
self->count -= add;
}
stop = 0;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
/*
if (self->count > MAX_AMMO_GIVE)
{
add = MAX_AMMO_GIVE;
}
else if (self->count<0)
{
add = 0;
}
else
{
add = self->count;
}
activator->client->ps.ammo[AMMO_BLASTER] += add;
activator->client->ps.ammo[AMMO_POWERCELL] += add;
activator->client->ps.ammo[AMMO_METAL_BOLTS] += add;
self->count -= add;
stop = 0;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
difBlaster = activator->client->ps.ammo[AMMO_BLASTER] - ammoData[AMMO_BLASTER].max;
difPowerCell = activator->client->ps.ammo[AMMO_POWERCELL] - ammoData[AMMO_POWERCELL].max;
difMetalBolts = activator->client->ps.ammo[AMMO_METAL_BOLTS] - ammoData[AMMO_METAL_BOLTS].max;
// Find the highest one
highest = difBlaster;
if (difPowerCell>difBlaster)
{
highest = difPowerCell;
}
if (difMetalBolts > highest)
{
highest = difMetalBolts;
}
*/
}
}
if (stop)
{
self->s.loopSound = 0;
self->s.loopIsSoundset = qfalse;
}
}
/*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
model="models/items/power_converter.md3"
Gives ammo energy when used.
"count" - the amount of ammo given when used (default 200)
"nodrain" - don't drain power from me
*/
//------------------------------------------------------------
void SP_misc_model_ammo_power_converter( gentity_t *ent )
{
if (!ent->health)
{
ent->health = 60;
}
VectorSet (ent->r.mins, -16, -16, -16);
VectorSet (ent->r.maxs, 16, 16, 16);
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eFlags = 0;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->r.contents = CONTENTS_SOLID;
ent->clipmask = MASK_SOLID;
G_SpawnInt("nodrain", "0", &ent->genericValue12);
ent->use = ammo_power_converter_use;
EnergyAmmoStationSettings(ent);
ent->genericValue4 = ent->count; //initial value
ent->think = check_recharge;
if (!ent->genericValue12)
{
ent->s.maxhealth = ent->s.health = ent->count;
}
ent->s.shouldtarget = qtrue;
ent->s.teamowner = 0;
ent->s.owner = ENTITYNUM_NONE;
ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
trap->LinkEntity ((sharedEntity_t *)ent);
//G_SoundIndex("sound/movers/objects/useshieldstation.wav");
}
/*
================
EnergyHealthStationSettings
================
*/
void EnergyHealthStationSettings(gentity_t *ent)
{
G_SpawnInt( "count", "200", &ent->count );
}
/*
================
health_power_converter_use
================
*/
void health_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
int dif,add;
int stop = 1;
if (!activator || !activator->client)
{
return;
}
if (self->setTime < level.time)
{
if (!self->s.loopSound)
{
self->s.loopSound = G_SoundIndex("sound/player/pickuphealth.wav");
}
self->setTime = level.time + 100;
dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->health;
if (dif > 0) // Already at full armor?
{
if (dif >/*MAX_AMMO_GIVE*/5)
{
add = 5;//MAX_AMMO_GIVE;
}
else
{
add = dif;
}
if (self->count<add)
{
add = self->count;
}
//self->count -= add;
stop = 0;
self->fly_sound_debounce_time = level.time + 500;
self->activator = activator;
activator->health += add;
}
}
if (stop)
{
self->s.loopSound = 0;
self->s.loopIsSoundset = qfalse;
}
}
/*QUAKED misc_model_health_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
model="models/items/power_converter.md3"
Gives ammo energy when used.
"count" - the amount of ammo given when used (default 200)
*/
//------------------------------------------------------------
void SP_misc_model_health_power_converter( gentity_t *ent )
{
if (!ent->health)
{
ent->health = 60;
}
VectorSet (ent->r.mins, -16, -16, -16);
VectorSet (ent->r.maxs, 16, 16, 16);
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eFlags = 0;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->r.contents = CONTENTS_SOLID;
ent->clipmask = MASK_SOLID;
ent->use = health_power_converter_use;
EnergyHealthStationSettings(ent);
ent->genericValue4 = ent->count; //initial value
ent->think = check_recharge;
//ent->s.maxhealth = ent->s.health = ent->count;
ent->s.shouldtarget = qtrue;
ent->s.teamowner = 0;
ent->s.owner = ENTITYNUM_NONE;
ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
trap->LinkEntity ((sharedEntity_t *)ent);
//G_SoundIndex("sound/movers/objects/useshieldstation.wav");
G_SoundIndex("sound/player/pickuphealth.wav");
ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
if (level.gametype == GT_SIEGE)
{ //show on radar from everywhere
ent->r.svFlags |= SVF_BROADCAST;
ent->s.eFlags |= EF_RADAROBJECT;
ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/bacta");
}
}
#if 0 //damage box stuff
void DmgBoxHit( gentity_t *self, gentity_t *other, trace_t *trace )
{
return;
}
void DmgBoxUpdateSelf(gentity_t *self)
{
gentity_t *owner = &g_entities[self->r.ownerNum];
if (!owner || !owner->client || !owner->inuse)
{
goto killMe;
}
if (self->damageRedirect == DAMAGEREDIRECT_HEAD &&
owner->client->damageBoxHandle_Head != self->s.number)
{
goto killMe;
}
if (self->damageRedirect == DAMAGEREDIRECT_RLEG &&
owner->client->damageBoxHandle_RLeg != self->s.number)
{
goto killMe;
}
if (self->damageRedirect == DAMAGEREDIRECT_LLEG &&
owner->client->damageBoxHandle_LLeg != self->s.number)
{
goto killMe;
}
if (owner->health < 1)
{
goto killMe;
}
//G_TestLine(self->r.currentOrigin, owner->client->ps.origin, 0x0000ff, 100);
trap->LinkEntity((sharedEntity_t *)self);
self->nextthink = level.time;
return;
killMe:
self->think = G_FreeEntity;
self->nextthink = level.time;
}
void DmgBoxAbsorb_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
{
self->health = 1;
}
void DmgBoxAbsorb_Pain(gentity_t *self, gentity_t *attacker, int damage)
{
self->health = 1;
}
gentity_t *CreateNewDamageBox( gentity_t *ent )
{
gentity_t *dmgBox;
//We do not want the client to have any real knowledge of the entity whatsoever. It will only
//ever be used on the server.
dmgBox = G_Spawn();
dmgBox->classname = "dmg_box";
dmgBox->r.svFlags = SVF_USE_CURRENT_ORIGIN;
dmgBox->r.ownerNum = ent->s.number;
dmgBox->clipmask = 0;
dmgBox->r.contents = MASK_PLAYERSOLID;
dmgBox->mass = 5000;
dmgBox->s.eFlags |= EF_NODRAW;
dmgBox->r.svFlags |= SVF_NOCLIENT;
dmgBox->touch = DmgBoxHit;
dmgBox->takedamage = qtrue;
dmgBox->health = 1;
dmgBox->pain = DmgBoxAbsorb_Pain;
dmgBox->die = DmgBoxAbsorb_Die;
dmgBox->think = DmgBoxUpdateSelf;
dmgBox->nextthink = level.time + 50;
return dmgBox;
}
void ATST_ManageDamageBoxes(gentity_t *ent)
{
vec3_t headOrg, lLegOrg, rLegOrg;
vec3_t fwd, right, up, flatAngle;
if (!ent->client->damageBoxHandle_Head)
{
gentity_t *dmgBox = CreateNewDamageBox(ent);
if (dmgBox)
{
VectorSet( dmgBox->r.mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
VectorSet( dmgBox->r.maxs, ATST_MAXS0, ATST_MAXS1, ATST_HEADSIZE );
ent->client->damageBoxHandle_Head = dmgBox->s.number;
dmgBox->damageRedirect = DAMAGEREDIRECT_HEAD;
dmgBox->damageRedirectTo = ent->s.number;
}
}
if (!ent->client->damageBoxHandle_RLeg)
{
gentity_t *dmgBox = CreateNewDamageBox(ent);
if (dmgBox)
{
VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
ent->client->damageBoxHandle_RLeg = dmgBox->s.number;
dmgBox->damageRedirect = DAMAGEREDIRECT_RLEG;
dmgBox->damageRedirectTo = ent->s.number;
}
}
if (!ent->client->damageBoxHandle_LLeg)
{
gentity_t *dmgBox = CreateNewDamageBox(ent);
if (dmgBox)
{
VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
ent->client->damageBoxHandle_LLeg = dmgBox->s.number;
dmgBox->damageRedirect = DAMAGEREDIRECT_LLEG;
dmgBox->damageRedirectTo = ent->s.number;
}
}
if (!ent->client->damageBoxHandle_Head ||
!ent->client->damageBoxHandle_LLeg ||
!ent->client->damageBoxHandle_RLeg)
{
return;
}
VectorCopy(ent->client->ps.origin, headOrg);
headOrg[2] += (ATST_MAXS2-ATST_HEADSIZE);
VectorCopy(ent->client->ps.viewangles, flatAngle);
flatAngle[PITCH] = 0;
flatAngle[ROLL] = 0;
AngleVectors(flatAngle, fwd, right, up);
VectorCopy(ent->client->ps.origin, lLegOrg);
VectorCopy(ent->client->ps.origin, rLegOrg);
lLegOrg[0] -= right[0]*32;
lLegOrg[1] -= right[1]*32;
lLegOrg[2] -= right[2]*32;
rLegOrg[0] += right[0]*32;
rLegOrg[1] += right[1]*32;
rLegOrg[2] += right[2]*32;
G_SetOrigin(&g_entities[ent->client->damageBoxHandle_Head], headOrg);
G_SetOrigin(&g_entities[ent->client->damageBoxHandle_LLeg], lLegOrg);
G_SetOrigin(&g_entities[ent->client->damageBoxHandle_RLeg], rLegOrg);
}
int G_PlayerBecomeATST(gentity_t *ent)
{
if (!ent || !ent->client)
{
return 0;
}
if (ent->client->ps.weaponTime > 0)
{
return 0;
}
if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
{
return 0;
}
if (ent->client->ps.zoomMode)
{
return 0;
}
if (ent->client->ps.usingATST)
{
ent->client->ps.usingATST = qfalse;
ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY;
}
else
{
ent->client->ps.usingATST = qtrue;
}
ent->client->ps.weaponTime = 1000;
return 1;
}
#endif
//----------------------------------------------------------
/*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE
Runs the specified effect, can also be targeted at an info_notnull to orient the effect
STARTOFF - effect starts off, toggles on/off when used
ONESHOT - effect fires only when used
DAMAGE - does radius damage around effect every "delay" milliseonds
"fxFile" - name of the effect file to play
"target" - direction to aim the effect in, otherwise defaults to up
"target2" - uses its target2 when the fx gets triggered
"delay" - how often to call the effect, don't over-do this ( default 200 )
"random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms )
"splashRadius" - only works when damage is checked ( default 16 )
"splashDamage" - only works when damage is checked ( default 5 )
"soundset" - bmodel set to use, plays start sound when toggled on, loop sound while on ( doesn't play on a oneshot), and a stop sound when turned off
*/
#define FX_RUNNER_RESERVED 0x800000
#define FX_ENT_RADIUS 32
extern int BMS_START;
extern int BMS_MID;
extern int BMS_END;
//----------------------------------------------------------
void fx_runner_think( gentity_t *ent )
{
BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
// call the effect with the desired position and orientation
if (ent->s.isPortalEnt)
{
// G_AddEvent( ent, EV_PLAY_PORTAL_EFFECT_ID, ent->genericValue5 );
}
else
{
// G_AddEvent( ent, EV_PLAY_EFFECT_ID, ent->genericValue5 );
}
// start the fx on the client (continuous)
ent->s.modelindex2 = FX_STATE_CONTINUOUS;
VectorCopy(ent->r.currentAngles, ent->s.angles);
VectorCopy(ent->r.currentOrigin, ent->s.origin);
ent->nextthink = level.time + ent->delay + Q_flrand(0.0f, 1.0f) * ent->random;
if ( ent->spawnflags & 4 ) // damage
{
G_RadiusDamage( ent->r.currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, ent, MOD_UNKNOWN );
}
if ( ent->target2 && ent->target2[0] )
{
// let our target know that we have spawned an effect
G_UseTargets2( ent, ent, ent->target2 );
}
if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do
{
if ( ent->soundSet && ent->soundSet[0] )
{
ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
ent->s.loopIsSoundset = qtrue;
ent->s.loopSound = BMS_MID;
}
}
}
//----------------------------------------------------------
void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
if (self->s.isPortalEnt)
{ //rww - mark it as broadcast upon first use if it's within the area of a skyportal
self->r.svFlags |= SVF_BROADCAST;
}
if ( self->spawnflags & 2 ) // ONESHOT
{
// call the effect with the desired position and orientation, as a safety thing,
// make sure we aren't thinking at all.
int saveState = self->s.modelindex2 + 1;
fx_runner_think( self );
self->nextthink = -1;
// one shot indicator
self->s.modelindex2 = saveState;
if (self->s.modelindex2 > FX_STATE_ONE_SHOT_LIMIT)
{
self->s.modelindex2 = FX_STATE_ONE_SHOT;
}
if ( self->target2 )
{
// let our target know that we have spawned an effect
G_UseTargets2( self, self, self->target2 );
}
if ( self->soundSet && self->soundSet[0] )
{
self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
}
}
else
{
// ensure we are working with the right think function
self->think = fx_runner_think;
// toggle our state
if ( self->nextthink == -1 )
{
// NOTE: we fire the effect immediately on use, the fx_runner_think func will set
// up the nextthink time.
fx_runner_think( self );
if ( self->soundSet && self->soundSet[0] )
{
self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
self->s.loopSound = BMS_MID;
self->s.loopIsSoundset = qtrue;
}
}
else
{
// turn off for now
self->nextthink = -1;
// turn off fx on client
self->s.modelindex2 = FX_STATE_OFF;
if ( self->soundSet && self->soundSet[0] )
{
self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
G_AddEvent( self, EV_BMODEL_SOUND, BMS_END );
self->s.loopSound = 0;
self->s.loopIsSoundset = qfalse;
}
}
}
}
//----------------------------------------------------------
void fx_runner_link( gentity_t *ent )
{
vec3_t dir;
if ( ent->target && ent->target[0] )
{
// try to use the target to override the orientation
gentity_t *target = NULL;
target = G_Find( target, FOFS(targetname), ent->target );
if ( !target )
{
// Bah, no good, dump a warning, but continue on and use the UP vector
Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target );
Com_Printf( " -assuming UP orientation.\n" );
}
else
{
// Our target is valid so let's override the default UP vector
VectorSubtract( target->s.origin, ent->s.origin, dir );
VectorNormalize( dir );
vectoangles( dir, ent->s.angles );
}
}
// don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus
if ( ent->target2 && ent->target2[0] )
{
gentity_t *target = NULL;
target = G_Find( target, FOFS(targetname), ent->target2 );
if ( !target )
{
// Target2 is bogus, but we can still continue
Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 );
}
}
G_SetAngles( ent, ent->s.angles );
if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT
{
// We won't even consider thinking until we are used
ent->nextthink = -1;
}
else
{
if ( ent->soundSet && ent->soundSet[0] )
{
ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
ent->s.loopSound = BMS_MID;
ent->s.loopIsSoundset = qtrue;
}
// Let's get to work right now!
ent->think = fx_runner_think;
ent->nextthink = level.time + 200; // wait a small bit, then start working
}
// make us useable if we can be targeted
if ( ent->targetname && ent->targetname[0] )
{
ent->use = fx_runner_use;
}
}
//----------------------------------------------------------
void SP_fx_runner( gentity_t *ent )
{
char *fxFile;
G_SpawnString( "fxFile", "", &fxFile );
// Get our defaults
G_SpawnInt( "delay", "200", &ent->delay );
G_SpawnFloat( "random", "0", &ent->random );
G_SpawnInt( "splashRadius", "16", &ent->splashRadius );
G_SpawnInt( "splashDamage", "5", &ent->splashDamage );
if (!ent->s.angles[0] && !ent->s.angles[1] && !ent->s.angles[2])
{
// didn't have angles, so give us the default of up
VectorSet( ent->s.angles, -90, 0, 0 );
}
if ( !fxFile || !fxFile[0] )
{
Com_Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) );
G_FreeEntity( ent );
return;
}
// Try and associate an effect file, unfortunately we won't know if this worked or not
// until the cgame trys to register it...
ent->s.modelindex = G_EffectIndex( fxFile );
// important info transmitted
ent->s.eType = ET_FX;
ent->s.speed = ent->delay;
ent->s.time = ent->random;
ent->s.modelindex2 = FX_STATE_OFF;
// Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
ent->think = fx_runner_link;
ent->nextthink = level.time + 400;
// Save our position and link us up!
G_SetOrigin( ent, ent->s.origin );
VectorSet( ent->r.maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
VectorScale( ent->r.maxs, -1, ent->r.mins );
trap->LinkEntity( (sharedEntity_t *)ent );
}
/*QUAKED fx_wind (0 .5 .8) (-16 -16 -16) (16 16 16) NORMAL CONSTANT GUSTING SWIRLING x FOG LIGHT_FOG
Generates global wind forces
NORMAL creates a random light global wind
CONSTANT forces all wind to go in a specified direction
GUSTING causes random gusts of wind
SWIRLING causes random swirls of wind
"angles" the direction for constant wind
"speed" the speed for constant wind
*/
void SP_CreateWind( gentity_t *ent )
{
char temp[256];
// Normal Wind
//-------------
if ( ent->spawnflags & 1 )
{
G_EffectIndex( "*wind" );
}
// Constant Wind
//---------------
if ( ent->spawnflags & 2 )
{
vec3_t windDir;
AngleVectors( ent->s.angles, windDir, 0, 0 );
G_SpawnFloat( "speed", "500", &ent->speed );
VectorScale( windDir, ent->speed, windDir );
Com_sprintf( temp, sizeof(temp), "*constantwind ( %f %f %f )", windDir[0], windDir[1], windDir[2] );
G_EffectIndex( temp );
}
// Gusting Wind
//--------------
if ( ent->spawnflags & 4 )
{
G_EffectIndex( "*gustingwind" );
}
// Swirling Wind
//---------------
/*if ( ent->spawnflags & 8 )
{
G_EffectIndex( "*swirlingwind" );
}*/
// MISTY FOG
//===========
if ( ent->spawnflags & 32 )
{
G_EffectIndex( "*fog" );
}
// MISTY FOG
//===========
if ( ent->spawnflags & 64 )
{
G_EffectIndex( "*light_fog" );
}
}
/*QUAKED fx_spacedust (1 0 0) (-16 -16 -16) (16 16 16)
This world effect will spawn space dust globally into the level.
"count" the number of snow particles (default of 1000)
*/
//----------------------------------------------------------
void SP_CreateSpaceDust( gentity_t *ent )
{
G_EffectIndex(va("*spacedust %i", ent->count));
//G_EffectIndex("*constantwind ( 10 -10 0 )");
}
/*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16)
This world effect will spawn snow globally into the level.
"count" the number of snow particles (default of 1000)
*/
//----------------------------------------------------------
void SP_CreateSnow( gentity_t *ent )
{
G_EffectIndex("*snow");
G_EffectIndex("*fog");
G_EffectIndex("*constantwind ( 100 100 -100 )");
}
/*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY ACID x MISTY_FOG
This world effect will spawn rain globally into the level.
LIGHT create light drizzle
MEDIUM create average medium rain
HEAVY create heavy downpour (with fog)
ACID create acid rain
MISTY_FOG causes clouds of misty fog to float through the level
*/
//----------------------------------------------------------
void SP_CreateRain( gentity_t *ent )
{
if ( ent->spawnflags == 0 )
{
G_EffectIndex( "*rain" );
return;
}
// Different Types Of Rain
//-------------------------
if ( ent->spawnflags & 1 )
{
G_EffectIndex( "*lightrain" );
}
else if ( ent->spawnflags & 2 )
{
G_EffectIndex( "*rain" );
}
else if ( ent->spawnflags & 4 )
{
G_EffectIndex( "*heavyrain" );
// Automatically Get Heavy Fog
//-----------------------------
G_EffectIndex( "*heavyrainfog" );
}
else if ( ent->spawnflags & 8 )
{
G_EffectIndex( "world/acid_fizz" );
G_EffectIndex( "*acidrain" );
}
// MISTY FOG
//===========
if ( ent->spawnflags & 32 )
{
G_EffectIndex( "*fog" );
}
}
qboolean gEscaping = qfalse;
int gEscapeTime = 0;
void Use_Target_Screenshake( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
qboolean bGlobal = qfalse;
if (ent->genericValue6)
{
bGlobal = qtrue;
}
G_ScreenShake(ent->s.origin, NULL, ent->speed, ent->genericValue5, bGlobal);
}
void SP_target_screenshake(gentity_t *ent)
{
G_SpawnFloat( "intensity", "10", &ent->speed );
//intensity of the shake
G_SpawnInt( "duration", "800", &ent->genericValue5 );
//duration of the shake
G_SpawnInt( "globalshake", "1", &ent->genericValue6 );
//non-0 if shake should be global (all clients). Otherwise, only in the PVS.
ent->use = Use_Target_Screenshake;
}
void LogExit( const char *string );
void Use_Target_Escapetrig( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
if (!ent->genericValue6)
{
gEscaping = qtrue;
gEscapeTime = level.time + ent->genericValue5;
}
else if (gEscaping)
{
int i = 0;
gEscaping = qfalse;
while (i < MAX_CLIENTS)
{ //all of the survivors get 100 points!
if (g_entities[i].inuse && g_entities[i].client && g_entities[i].health > 0 &&
g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR &&
!(g_entities[i].client->ps.pm_flags & PMF_FOLLOW))
{
AddScore(&g_entities[i], g_entities[i].client->ps.origin, 100);
}
i++;
}
if (activator && activator->inuse && activator->client)
{ //the one who escaped gets 500
AddScore(activator, activator->client->ps.origin, 500);
}
LogExit("Escaped!");
}
}
void SP_target_escapetrig(gentity_t *ent)
{
if (level.gametype != GT_SINGLE_PLAYER)
{
G_FreeEntity(ent);
return;
}
G_SpawnInt( "escapetime", "60000", &ent->genericValue5);
//time given (in ms) for the escape
G_SpawnInt( "escapegoal", "0", &ent->genericValue6);
//if non-0, when used, will end an ongoing escape instead of start it
ent->use = Use_Target_Escapetrig;
}
/*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
Place facing a door (using the angle, not a targetname) and it will lock that door. Can only be destroyed by lightsaber and will automatically unlock the door it's attached to
NOTE: place these half-way in the door to make it flush with the door's surface.
"target" thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
"health" default is 10
*/
void maglock_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
//unlock our door if we're the last lock pointed at the door
if ( self->activator )
{
self->activator->lockCount--;
if ( !self->activator->lockCount )
{
self->activator->flags &= ~FL_INACTIVE;
}
}
//use targets
G_UseTargets( self, attacker );
//die
//rwwFIXMEFIXME - weap expl func
// WP_Explode( self );
}
void maglock_link( gentity_t *self );
gentity_t *G_FindDoorTrigger( gentity_t *ent );
void SP_misc_maglock ( gentity_t *self )
{
//NOTE: May have to make these only work on doors that are either untargeted
// or are targeted by a trigger, not doors fired off by scripts, counters
// or other such things?
self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
self->genericValue1 = G_EffectIndex( "maglock/explosion" );
G_SetOrigin( self, self->s.origin );
self->think = maglock_link;
//FIXME: for some reason, when you re-load a level, these fail to find their doors...? Random? Testing an additional 200ms after the START_TIME_FIND_LINKS
self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first!
}
void maglock_link( gentity_t *self )
{
//find what we're supposed to be attached to
vec3_t forward, start, end;
trace_t trace;
gentity_t *traceEnt;
AngleVectors( self->s.angles, forward, NULL, NULL );
VectorMA( self->s.origin, 128, forward, end );
VectorMA( self->s.origin, -4, forward, start );
trap->Trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, qfalse, 0, 0 );
if ( trace.allsolid || trace.startsolid )
{
Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
G_FreeEntity( self );
return;
}
if ( trace.fraction == 1.0 )
{
self->think = maglock_link;
self->nextthink = level.time + 100;
/*
Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
G_FreeEntity( self );
*/
return;
}
traceEnt = &g_entities[trace.entityNum];
if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
{
self->think = maglock_link;
self->nextthink = level.time + 100;
//Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
//G_FreeEntity( self );
return;
}
//check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
//find the trigger for the door
self->activator = G_FindDoorTrigger( traceEnt );
if ( !self->activator )
{
self->activator = traceEnt;
}
self->activator->lockCount++;
self->activator->flags |= FL_INACTIVE;
//now position and orient it
vectoangles( trace.plane.normal, end );
G_SetOrigin( self, trace.endpos );
G_SetAngles( self, end );
//make it hittable
//FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
//self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
VectorSet( self->r.mins, -8, -8, -8 );
VectorSet( self->r.maxs, 8, 8, 8 );
self->r.contents = CONTENTS_CORPSE;
//make it destroyable
self->flags |= FL_SHIELDED;//only damagable by lightsabers
self->takedamage = qtrue;
self->health = 10;
self->die = maglock_die;
//self->fxID = G_EffectIndex( "maglock/explosion" );
trap->LinkEntity( (sharedEntity_t *)self );
}
void faller_touch(gentity_t *self, gentity_t *other, trace_t *trace)
{
if (self->epVelocity[2] < -100 && self->genericValue7 < level.time)
{
int r = Q_irand(1, 3);
if (r == 1)
{
self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain25");
}
else if (r == 2)
{
self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain50");
}
else
{
self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain75");
}
G_EntitySound(self, CHAN_VOICE, self->genericValue11);
G_EntitySound(self, CHAN_AUTO, self->genericValue10);
self->genericValue6 = level.time + 3000;
self->genericValue7 = level.time + 200;
}
}
void faller_think(gentity_t *ent)
{
float gravity = 3.0f;
float mass = 0.09f;
float bounce = 1.1f;
if (ent->genericValue6 < level.time)
{
ent->think = G_FreeEntity;
ent->nextthink = level.time;
return;
}
if (ent->epVelocity[2] < -100)
{
if (!ent->genericValue8)
{
G_EntitySound(ent, CHAN_VOICE, ent->genericValue9);
ent->genericValue8 = 1;
}
}
else
{
ent->genericValue8 = 0;
}
G_RunExPhys(ent, gravity, mass, bounce, qtrue, NULL, 0);
VectorScale(ent->epVelocity, 10.0f, ent->s.pos.trDelta);
ent->nextthink = level.time + 25;
}
void misc_faller_create( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
gentity_t *faller = G_Spawn();
faller->genericValue10 = G_SoundIndex("sound/player/fallsplat");
faller->genericValue9 = G_SoundIndex("sound/chars/stofficer1/misc/falling1");
faller->genericValue8 = 0;
faller->genericValue7 = 0;
faller->genericValue6 = level.time + 15000;
G_SetOrigin(faller, ent->s.origin);
faller->s.modelGhoul2 = 1;
faller->s.modelindex = G_ModelIndex("models/players/stormtrooper/model.glm");
faller->s.g2radius = 100;
faller->s.customRGBA[0]=Q_irand(1,255);
faller->s.customRGBA[1]=Q_irand(1,255);
faller->s.customRGBA[2]=Q_irand(1,255);
faller->s.customRGBA[3]=255;
VectorSet(faller->r.mins, -15, -15, DEFAULT_MINS_2);
VectorSet(faller->r.maxs, 15, 15, DEFAULT_MAXS_2);
faller->clipmask = MASK_PLAYERSOLID;
faller->r.contents = MASK_PLAYERSOLID;
faller->s.eFlags = (EF_RAG|EF_CLIENTSMOOTH);
faller->think = faller_think;
faller->nextthink = level.time;
faller->touch = faller_touch;
faller->epVelocity[0] = flrand(-256.0f, 256.0f);
faller->epVelocity[1] = flrand(-256.0f, 256.0f);
trap->LinkEntity((sharedEntity_t *)faller);
}
void misc_faller_think(gentity_t *ent)
{
misc_faller_create(ent, ent, ent);
ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
}
/*QUAKED misc_faller (1 0 0) (-8 -8 -8) (8 8 8)
Falling stormtrooper - spawned every interval+random fudgefactor,
or if specified, when used.
targetname - if specified, will only spawn when used
interval - spawn every so often (milliseconds)
fudgefactor - milliseconds between 0 and this number randomly added to interval
*/
void SP_misc_faller(gentity_t *ent)
{
G_ModelIndex("models/players/stormtrooper/model.glm");
G_SoundIndex("sound/chars/stofficer1/misc/pain25");
G_SoundIndex("sound/chars/stofficer1/misc/pain50");
G_SoundIndex("sound/chars/stofficer1/misc/pain75");
G_SoundIndex("sound/chars/stofficer1/misc/falling1");
G_SoundIndex("sound/player/fallsplat");
G_SpawnInt("interval", "500", &ent->genericValue1);
G_SpawnInt("fudgefactor", "0", &ent->genericValue2);
if (!ent->targetname || !ent->targetname[0])
{
ent->think = misc_faller_think;
ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
}
else
{
ent->use = misc_faller_create;
}
}
//rww - ref tag stuff ported from SP (and C-ified)
#define TAG_GENERIC_NAME "__WORLD__" //If a designer chooses this name, cut a finger off as an example to the others
//MAX_TAG_OWNERS is 16 for now in order to not use too much VM memory.
//Each tag owner has preallocated space for tags up to MAX_TAGS.
//As is this means 16*256 sizeof(reference_tag_t)'s in addition to name+inuse*16.
#define MAX_TAGS 256
#define MAX_TAG_OWNERS 16
//Maybe I should use my trap->TrueMalloc/trap->TrueFree stuff with this.
//But I am not yet confident that it can be used without exploding at some point.
typedef struct tagOwner_s
{
char name[MAX_REFNAME];
reference_tag_t tags[MAX_TAGS];
qboolean inuse;
} tagOwner_t;
tagOwner_t refTagOwnerMap[MAX_TAG_OWNERS];
tagOwner_t *FirstFreeTagOwner(void)
{
int i = 0;
while (i < MAX_TAG_OWNERS)
{
if (!refTagOwnerMap[i].inuse)
{
return &refTagOwnerMap[i];
}
i++;
}
Com_Printf("WARNING: MAX_TAG_OWNERS (%i) REF TAG LIMIT HIT\n", MAX_TAG_OWNERS);
return NULL;
}
reference_tag_t *FirstFreeRefTag(tagOwner_t *tagOwner)
{
int i = 0;
assert(tagOwner);
while (i < MAX_TAGS)
{
if (!tagOwner->tags[i].inuse)
{
return &tagOwner->tags[i];
}
i++;
}
Com_Printf("WARNING: MAX_TAGS (%i) REF TAG LIMIT HIT\n", MAX_TAGS);
return NULL;
}
/*
-------------------------
TAG_Init
-------------------------
*/
void TAG_Init( void )
{
int i = 0;
int x = 0;
while (i < MAX_TAG_OWNERS)
{
while (x < MAX_TAGS)
{
memset(&refTagOwnerMap[i].tags[x], 0, sizeof(refTagOwnerMap[i].tags[x]));
x++;
}
memset(&refTagOwnerMap[i], 0, sizeof(refTagOwnerMap[i]));
i++;
}
}
/*
-------------------------
TAG_FindOwner
-------------------------
*/
tagOwner_t *TAG_FindOwner( const char *owner )
{
int i = 0;
while (i < MAX_TAG_OWNERS)
{
if (refTagOwnerMap[i].inuse && !Q_stricmp(refTagOwnerMap[i].name, owner))
{
return &refTagOwnerMap[i];
}
i++;
}
return NULL;
}
/*
-------------------------
TAG_Find
-------------------------
*/
reference_tag_t *TAG_Find( const char *owner, const char *name )
{
tagOwner_t *tagOwner = NULL;
int i = 0;
if (owner && owner[0])
{
tagOwner = TAG_FindOwner(owner);
}
if (!tagOwner)
{
tagOwner = TAG_FindOwner(TAG_GENERIC_NAME);
}
//Not found...
if (!tagOwner)
{
tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
if (!tagOwner)
{
return NULL;
}
}
while (i < MAX_TAGS)
{
if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
{
return &tagOwner->tags[i];
}
i++;
}
//Try the generic owner instead
tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
if (!tagOwner)
{
return NULL;
}
i = 0;
while (i < MAX_TAGS)
{
if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
{
return &tagOwner->tags[i];
}
i++;
}
return NULL;
}
/*
-------------------------
TAG_Add
-------------------------
*/
reference_tag_t *TAG_Add( const char *name, const char *owner, vec3_t origin, vec3_t angles, int radius, int flags )
{
reference_tag_t *tag = NULL;
tagOwner_t *tagOwner = NULL;
//Make sure this tag's name isn't alread in use
if ( TAG_Find( owner, name ) )
{
Com_Printf(S_COLOR_RED"Duplicate tag name \"%s\"\n", name );
return NULL;
}
//Attempt to add this to the owner's list
if ( !owner || !owner[0] )
{
//If the owner isn't found, use the generic world name
owner = TAG_GENERIC_NAME;
}
tagOwner = TAG_FindOwner( owner );
if (!tagOwner)
{
//Create a new owner list
tagOwner = FirstFreeTagOwner();//new tagOwner_t;
if (!tagOwner)
{
assert(0);
return 0;
}
}
//This is actually reverse order of how SP does it because of the way we're storing/allocating.
//Now that we have the owner, we want to get the first free reftag on the owner itself.
tag = FirstFreeRefTag(tagOwner);
if (!tag)
{
assert(0);
return NULL;
}
//Copy the information
VectorCopy( origin, tag->origin );
VectorCopy( angles, tag->angles );
tag->radius = radius;
tag->flags = flags;
if ( !name || !name[0] )
{
Com_Printf(S_COLOR_RED"ERROR: Nameless ref_tag found at (%i %i %i)\n", (int)origin[0], (int)origin[1], (int)origin[2]);
return NULL;
}
//Copy the name
Q_strncpyz( (char *) tagOwner->name, owner, MAX_REFNAME );
Q_strlwr( (char *) tagOwner->name ); //NOTENOTE: For case insensitive searches on a map
//Copy the name
Q_strncpyz( (char *) tag->name, name, MAX_REFNAME );
Q_strlwr( (char *) tag->name ); //NOTENOTE: For case insensitive searches on a map
tagOwner->inuse = qtrue;
tag->inuse = qtrue;
return tag;
}
/*
-------------------------
TAG_GetOrigin
-------------------------
*/
int TAG_GetOrigin( const char *owner, const char *name, vec3_t origin )
{
reference_tag_t *tag = TAG_Find( owner, name );
if (!tag)
{
VectorClear(origin);
return 0;
}
VectorCopy( tag->origin, origin );
return 1;
}
/*
-------------------------
TAG_GetOrigin2
Had to get rid of that damn assert for dev
-------------------------
*/
int TAG_GetOrigin2( const char *owner, const char *name, vec3_t origin )
{
reference_tag_t *tag = TAG_Find( owner, name );
if( tag == NULL )
{
return 0;
}
VectorCopy( tag->origin, origin );
return 1;
}
/*
-------------------------
TAG_GetAngles
-------------------------
*/
int TAG_GetAngles( const char *owner, const char *name, vec3_t angles )
{
reference_tag_t *tag = TAG_Find( owner, name );
if (!tag)
{
assert(0);
return 0;
}
VectorCopy( tag->angles, angles );
return 1;
}
/*
-------------------------
TAG_GetRadius
-------------------------
*/
int TAG_GetRadius( const char *owner, const char *name )
{
reference_tag_t *tag = TAG_Find( owner, name );
if (!tag)
{
assert(0);
return 0;
}
return tag->radius;
}
/*
-------------------------
TAG_GetFlags
-------------------------
*/
int TAG_GetFlags( const char *owner, const char *name )
{
reference_tag_t *tag = TAG_Find( owner, name );
if (!tag)
{
assert(0);
return 0;
}
return tag->flags;
}
/*
==============================================================================
Spawn functions
==============================================================================
*/
/*QUAKED ref_tag_huge (0.5 0.5 1) (-128 -128 -128) (128 128 128)
SAME AS ref_tag, JUST BIGGER SO YOU CAN SEE THEM IN EDITOR ON HUGE MAPS!
Reference tags which can be positioned throughout the level.
These tags can later be refered to by the scripting system
so that their origins and angles can be referred to.
If you set angles on the tag, these will be retained.
If you target a ref_tag at an entity, that will set the ref_tag's
angles toward that entity.
If you set the ref_tag's ownername to the ownername of an entity,
it makes that entity is the owner of the ref_tag. This means
that the owner, and only the owner, may refer to that tag.
Tags may not have the same name as another tag with the same
owner. However, tags with different owners may have the same
name as one another. In this way, scripts can generically
refer to tags by name, and their owners will automatically
specifiy which tag is being referred to.
targetname - the name of this tag
ownername - the owner of this tag
target - use to point the tag at something for angles
*/
/*QUAKED ref_tag (0.5 0.5 1) (-8 -8 -8) (8 8 8)
Reference tags which can be positioned throughout the level.
These tags can later be refered to by the scripting system
so that their origins and angles can be referred to.
If you set angles on the tag, these will be retained.
If you target a ref_tag at an entity, that will set the ref_tag's
angles toward that entity.
If you set the ref_tag's ownername to the ownername of an entity,
it makes that entity is the owner of the ref_tag. This means
that the owner, and only the owner, may refer to that tag.
Tags may not have the same name as another tag with the same
owner. However, tags with different owners may have the same
name as one another. In this way, scripts can generically
refer to tags by name, and their owners will automatically
specifiy which tag is being referred to.
targetname - the name of this tag
ownername - the owner of this tag
target - use to point the tag at something for angles
*/
void ref_link ( gentity_t *ent )
{
if ( ent->target )
{
//TODO: Find the target and set our angles to that direction
gentity_t *target = G_Find( NULL, FOFS(targetname), ent->target );
vec3_t dir;
if ( target )
{
//Find the direction to the target
VectorSubtract( target->s.origin, ent->s.origin, dir );
VectorNormalize( dir );
vectoangles( dir, ent->s.angles );
//FIXME: Does pitch get flipped?
}
else
{
Com_Printf( S_COLOR_RED"ERROR: ref_tag (%s) has invalid target (%s)\n", ent->targetname, ent->target );
}
}
//Add the tag
TAG_Add( ent->targetname, ent->ownername, ent->s.origin, ent->s.angles, 16, 0 );
//Delete immediately, cannot be refered to as an entity again
//NOTE: this means if you wanted to link them in a chain for, say, a path, you can't
G_FreeEntity( ent );
}
void SP_reference_tag ( gentity_t *ent )
{
if ( ent->target )
{
//Init cannot occur until all entities have been spawned
ent->think = ref_link;
ent->nextthink = level.time + START_TIME_LINK_ENTS;
}
else
{
ref_link( ent );
}
}
/*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
ALTFIRE - fire the alt-fire of the chosen weapon
TOGGLE - keep firing until used again (fires at intervals of "wait")
"wait" - debounce time between refires (defaults to 500)
"target" - what to aim at (will update aim every frame if it's a moving target)
"weapon" - specify the weapon to use (default is WP_BLASTER)
WP_BRYAR_PISTOL
WP_BLASTER
WP_DISRUPTOR
WP_BOWCASTER
WP_REPEATER
WP_DEMP2
WP_FLECHETTE
WP_ROCKET_LAUNCHER
WP_THERMAL
WP_TRIP_MINE
WP_DET_PACK
WP_STUN_BATON
WP_EMPLACED_GUN
WP_BOT_LASER
WP_TURRET
WP_ATST_MAIN
WP_ATST_SIDE
WP_TIE_FIGHTER
WP_RAPID_FIRE_CONC
WP_BLASTER_PISTOL
*/
//kind of hacky, but we have to do this with no dynamic allocation
#define MAX_SHOOTERS 16
typedef struct shooterClient_s
{
gclient_t cl;
qboolean inuse;
} shooterClient_t;
static shooterClient_t g_shooterClients[MAX_SHOOTERS];
static qboolean g_shooterClientInit = qfalse;
gclient_t *G_ClientForShooter(void)
{
int i = 0;
if (!g_shooterClientInit)
{ //in theory it should be initialized to 0 on the stack, but just in case.
memset(g_shooterClients, 0, sizeof(shooterClient_t)*MAX_SHOOTERS);
g_shooterClientInit = qtrue;
}
while (i < MAX_SHOOTERS)
{
if (!g_shooterClients[i].inuse)
{
return &g_shooterClients[i].cl;
}
i++;
}
Com_Error(ERR_DROP, "No free shooter clients - hit MAX_SHOOTERS");
return NULL;
}
void G_FreeClientForShooter(gclient_t *cl)
{
int i = 0;
while (i < MAX_SHOOTERS)
{
if (&g_shooterClients[i].cl == cl)
{
g_shooterClients[i].inuse = qfalse;
return;
}
i++;
}
}
void misc_weapon_shooter_fire( gentity_t *self )
{
FireWeapon( self, (self->spawnflags&1) );
if ( (self->spawnflags&2) )
{//repeat
self->think = misc_weapon_shooter_fire;
self->nextthink = level.time + self->wait;
}
}
void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
{
if ( self->think == misc_weapon_shooter_fire )
{//repeating fire, stop
/*
G_FreeClientForShooter(self->client);
self->think = G_FreeEntity;
self->nextthink = level.time;
*/
self->nextthink = 0;
return;
}
//otherwise, fire
misc_weapon_shooter_fire( self );
}
void misc_weapon_shooter_aim( gentity_t *self )
{
//update my aim
if ( self->target )
{
gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
if ( targ )
{
self->enemy = targ;
VectorSubtract( targ->r.currentOrigin, self->r.currentOrigin, self->pos1 );
VectorCopy( targ->r.currentOrigin, self->pos1 );
vectoangles( self->pos1, self->client->ps.viewangles );
SetClientViewAngle( self, self->client->ps.viewangles );
//FIXME: don't keep doing this unless target is a moving target?
self->nextthink = level.time + FRAMETIME;
}
else
{
self->enemy = NULL;
}
}
}
extern stringID_table_t WPTable[];
void SP_misc_weapon_shooter( gentity_t *self )
{
char *s;
//alloc a client just for the weapon code to use
self->client = G_ClientForShooter();//(gclient_s *)trap->Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue);
G_SpawnString("weapon", "", &s);
//set weapon
self->s.weapon = self->client->ps.weapon = WP_BLASTER;
if ( s && s[0] )
{//use a different weapon
self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, s );
}
RegisterItem(BG_FindItemForWeapon(self->s.weapon));
//set where our muzzle is
VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
//permanently updated (don't need for MP)
//self->client->renderInfo.mPCalcTime = Q3_INFINITE;
//set up to link
if ( self->target )
{
self->think = misc_weapon_shooter_aim;
self->nextthink = level.time + START_TIME_LINK_ENTS;
}
else
{//just set aim angles
VectorCopy( self->s.angles, self->client->ps.viewangles );
AngleVectors( self->s.angles, self->pos1, NULL, NULL );
}
//set up to fire when used
self->use = misc_weapon_shooter_use;
if ( !self->wait )
{
self->wait = 500;
}
}
/*QUAKED misc_weather_zone (0 .5 .8) ?
Determines a region to check for weather contents - will significantly reduce load time
*/
void SP_misc_weather_zone( gentity_t *ent )
{
G_FreeEntity(ent);
}
void SP_misc_cubemap( gentity_t *ent )
{
G_FreeEntity( ent );
}