jkxr/Projects/Android/jni/OpenJK/codemp/game/g_trigger.c
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +01:00

2000 lines
54 KiB
C

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_local.h"
#include "bg_saga.h"
int gTrigFallSound;
void InitTrigger( gentity_t *self ) {
if (!VectorCompare (self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
trap->SetBrushModel( (sharedEntity_t *)self, self->model );
self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap->SetBrushModel
self->r.svFlags = SVF_NOCLIENT;
if(self->spawnflags & 128)
{
self->flags |= FL_INACTIVE;
}
}
// the wait time has passed, so set back up for another activation
void multi_wait( gentity_t *ent ) {
ent->nextthink = 0;
}
void trigger_cleared_fire (gentity_t *self);
// the trigger was just activated
// ent->activator should be set to the activator so it can be held through a delay
// so wait for the delay time before firing
void multi_trigger_run( gentity_t *ent )
{
ent->think = 0;
G_ActivateBehavior( ent, BSET_USE );
if ( ent->soundSet && ent->soundSet[0] )
{
trap->SetConfigstring( CS_GLOBAL_AMBIENT_SET, ent->soundSet );
}
if (ent->genericValue4)
{ //we want to activate target3 for team1 or target4 for team2
if (ent->genericValue4 == SIEGETEAM_TEAM1 &&
ent->target3 && ent->target3[0])
{
G_UseTargets2(ent, ent->activator, ent->target3);
}
else if (ent->genericValue4 == SIEGETEAM_TEAM2 &&
ent->target4 && ent->target4[0])
{
G_UseTargets2(ent, ent->activator, ent->target4);
}
ent->genericValue4 = 0;
}
G_UseTargets (ent, ent->activator);
if ( ent->noise_index )
{
G_Sound( ent->activator, CHAN_AUTO, ent->noise_index );
}
if ( ent->target2 && ent->target2[0] && ent->wait >= 0 )
{
ent->think = trigger_cleared_fire;
ent->nextthink = level.time + ent->speed;
}
else if ( ent->wait > 0 )
{
if ( ent->painDebounceTime != level.time )
{//first ent to touch it this frame
//ent->e_ThinkFunc = thinkF_multi_wait;
ent->nextthink = level.time + ( ent->wait + ent->random * Q_flrand(-1.0f, 1.0f) ) * 1000;
ent->painDebounceTime = level.time;
}
}
else if ( ent->wait < 0 )
{
// we can't just remove (self) here, because this is a touch function
// called while looping through area links...
ent->r.contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me
ent->think = 0;
ent->use = 0;
//Don't remove, Icarus may barf?
//ent->nextthink = level.time + FRAMETIME;
//ent->think = G_FreeEntity;
}
if( ent->activator && ent->activator->client )
{ // mark the trigger as being touched by the player
ent->aimDebounceTime = level.time;
}
}
//determine if the class given is listed in the string using the | formatting
qboolean G_NameInTriggerClassList(char *list, char *str)
{
char cmp[MAX_STRING_CHARS];
int i = 0;
int j;
while (list[i])
{
j = 0;
while (list[i] && list[i] != '|')
{
cmp[j] = list[i];
i++;
j++;
}
cmp[j] = 0;
if (!Q_stricmp(str, cmp))
{ //found it
return qtrue;
}
if (list[i] != '|')
{ //reached the end and never found it
return qfalse;
}
i++;
}
return qfalse;
}
extern qboolean gSiegeRoundBegun;
void SiegeItemRemoveOwner(gentity_t *ent, gentity_t *carrier);
void multi_trigger( gentity_t *ent, gentity_t *activator )
{
qboolean haltTrigger = qfalse;
if ( ent->think == multi_trigger_run )
{//already triggered, just waiting to run
return;
}
if (level.gametype == GT_SIEGE &&
!gSiegeRoundBegun)
{ //nothing can be used til the round starts.
return;
}
if (level.gametype == GT_SIEGE &&
activator && activator->client &&
ent->alliedTeam &&
activator->client->sess.sessionTeam != ent->alliedTeam)
{ //this team can't activate this trigger.
return;
}
if (level.gametype == GT_SIEGE &&
ent->idealclass && ent->idealclass[0])
{ //only certain classes can activate it
if (!activator ||
!activator->client ||
activator->client->siegeClass < 0)
{ //no class
return;
}
if (!G_NameInTriggerClassList(bgSiegeClasses[activator->client->siegeClass].name, ent->idealclass))
{ //wasn't in the list
return;
}
}
if (level.gametype == GT_SIEGE && ent->genericValue1)
{
haltTrigger = qtrue;
if (activator && activator->client &&
activator->client->holdingObjectiveItem &&
ent->targetname && ent->targetname[0])
{
gentity_t *objItem = &g_entities[activator->client->holdingObjectiveItem];
if (objItem && objItem->inuse)
{
if (objItem->goaltarget && objItem->goaltarget[0] &&
!Q_stricmp(ent->targetname, objItem->goaltarget))
{
if (objItem->genericValue7 != activator->client->sess.sessionTeam)
{ //The carrier of the item is not on the team which disallows objective scoring for it
if (objItem->target3 && objItem->target3[0])
{ //if it has a target3, fire it off instead of using the trigger
G_UseTargets2(objItem, objItem, objItem->target3);
//3-24-03 - want to fire off the target too I guess, if we have one.
if (ent->targetname && ent->targetname[0])
{
haltTrigger = qfalse;
}
}
else
{
haltTrigger = qfalse;
}
//now that the item has been delivered, it can go away.
SiegeItemRemoveOwner(objItem, activator);
objItem->nextthink = 0;
objItem->neverFree = qfalse;
G_FreeEntity(objItem);
}
}
}
}
}
else if (ent->genericValue1)
{ //Never activate in non-siege gametype I guess.
return;
}
if (ent->genericValue2)
{ //has "teambalance" property
int i = 0;
int team1ClNum = 0;
int team2ClNum = 0;
int owningTeam = ent->genericValue3;
int newOwningTeam = 0;
int numEnts = 0;
int entityList[MAX_GENTITIES];
gentity_t *cl;
if (level.gametype != GT_SIEGE)
{
return;
}
if (!activator->client ||
(activator->client->sess.sessionTeam != SIEGETEAM_TEAM1 && activator->client->sess.sessionTeam != SIEGETEAM_TEAM2))
{ //activator must be a valid client to begin with
return;
}
//Count up the number of clients standing within the bounds of the trigger and the number of them on each team
numEnts = trap->EntitiesInBox( ent->r.absmin, ent->r.absmax, entityList, MAX_GENTITIES );
while (i < numEnts)
{
if (entityList[i] < MAX_CLIENTS)
{ //only care about clients
cl = &g_entities[entityList[i]];
//the client is valid
if (cl->inuse && cl->client &&
(cl->client->sess.sessionTeam == SIEGETEAM_TEAM1 || cl->client->sess.sessionTeam == SIEGETEAM_TEAM2) &&
cl->health > 0 &&
!(cl->client->ps.eFlags & EF_DEAD))
{
//See which team he's on
if (cl->client->sess.sessionTeam == SIEGETEAM_TEAM1)
{
team1ClNum++;
}
else
{
team2ClNum++;
}
}
}
i++;
}
if (!team1ClNum && !team2ClNum)
{ //no one in the box? How did we get activated? Oh well.
return;
}
if (team1ClNum == team2ClNum)
{ //if equal numbers the ownership will remain the same as it is now
return;
}
//decide who owns it now
if (team1ClNum > team2ClNum)
{
newOwningTeam = SIEGETEAM_TEAM1;
}
else
{
newOwningTeam = SIEGETEAM_TEAM2;
}
if (owningTeam == newOwningTeam)
{ //it's the same one it already was, don't care then.
return;
}
//Set the new owner and set the variable which will tell us to activate a team-specific target
ent->genericValue3 = newOwningTeam;
ent->genericValue4 = newOwningTeam;
}
if (haltTrigger)
{ //This is an objective trigger and the activator is not carrying an objective item that matches the targetname.
return;
}
if ( ent->nextthink > level.time )
{
if( ent->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in a single frame
{
if ( ent->painDebounceTime && ent->painDebounceTime != level.time )
{//this should still allow subsequent ents to fire this trigger in the current frame
return; // can't retrigger until the wait is over
}
}
else
{
return;
}
}
// if the player has already activated this trigger this frame
if( activator && activator->s.number < MAX_CLIENTS && ent->aimDebounceTime == level.time )
{
return;
}
if ( ent->flags & FL_INACTIVE )
{//Not active at this time
return;
}
ent->activator = activator;
if(ent->delay && ent->painDebounceTime < (level.time + ent->delay) )
{//delay before firing trigger
ent->think = multi_trigger_run;
ent->nextthink = level.time + ent->delay;
ent->painDebounceTime = level.time;
}
else
{
multi_trigger_run (ent);
}
}
void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
multi_trigger( ent, activator );
}
qboolean G_PointInBounds( vec3_t point, vec3_t mins, vec3_t maxs );
void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace )
{
if( !other->client )
{
return;
}
if ( self->flags & FL_INACTIVE )
{//set by target_deactivate
return;
}
if( self->alliedTeam )
{
if ( other->client->sess.sessionTeam != self->alliedTeam )
{
return;
}
}
// moved to just above multi_trigger because up here it just checks if the trigger is not being touched
// we want it to check any conditions set on the trigger, if one of those isn't met, the trigger is considered to be "cleared"
// if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire )
// {//We're waiting to fire our target2 first
// self->nextthink = level.time + self->speed;
// return;
// }
if ( self->spawnflags & 1 )
{
if ( other->s.eType == ET_NPC )
{
return;
}
}
else
{
if ( self->spawnflags & 16 )
{//NPCONLY
if ( other->NPC == NULL )
{
return;
}
}
if ( self->NPC_targetname && self->NPC_targetname[0] )
{
if ( other->script_targetname && other->script_targetname[0] )
{
if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 )
{//not the right guy to fire me off
return;
}
}
else
{
return;
}
}
}
if ( self->spawnflags & 2 )
{//FACING
vec3_t forward;
AngleVectors( other->client->ps.viewangles, forward, NULL, NULL );
if ( DotProduct( self->movedir, forward ) < 0.5 )
{//Not Within 45 degrees
return;
}
}
if ( self->spawnflags & 4 )
{//USE_BUTTON
if( !( other->client->pers.cmd.buttons & BUTTON_USE ) )
{//not pressing use button
return;
}
if ((other->client->ps.weaponTime > 0 && other->client->ps.torsoAnim != BOTH_BUTTON_HOLD && other->client->ps.torsoAnim != BOTH_CONSOLE1) || other->health < 1 ||
(other->client->ps.pm_flags & PMF_FOLLOW) || other->client->sess.sessionTeam == TEAM_SPECTATOR ||
other->client->ps.forceHandExtend != HANDEXTEND_NONE)
{ //player has to be free of other things to use.
return;
}
if (self->genericValue7)
{ //we have to be holding the use key in this trigger for x milliseconds before firing
if (level.gametype == GT_SIEGE &&
self->idealclass && self->idealclass[0])
{ //only certain classes can activate it
if (!other ||
!other->client ||
other->client->siegeClass < 0)
{ //no class
return;
}
if (!G_NameInTriggerClassList(bgSiegeClasses[other->client->siegeClass].name, self->idealclass))
{ //wasn't in the list
return;
}
}
if (!G_PointInBounds( other->client->ps.origin, self->r.absmin, self->r.absmax ))
{
return;
}
else if (other->client->isHacking != self->s.number && other->s.number < MAX_CLIENTS )
{ //start the hack
other->client->isHacking = self->s.number;
VectorCopy(other->client->ps.viewangles, other->client->hackingAngles);
other->client->ps.hackingTime = level.time + self->genericValue7;
other->client->ps.hackingBaseTime = self->genericValue7;
if (other->client->ps.hackingBaseTime > 60000)
{ //don't allow a bit overflow
other->client->ps.hackingTime = level.time + 60000;
other->client->ps.hackingBaseTime = 60000;
}
return;
}
else if (other->client->ps.hackingTime < level.time)
{ //finished with the hack, reset the hacking values and let it fall through
other->client->isHacking = 0; //can't hack a client
other->client->ps.hackingTime = 0;
}
else
{ //hack in progress
return;
}
}
}
if ( self->spawnflags & 8 )
{//FIRE_BUTTON
if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
!( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
{//not pressing fire button or altfire button
return;
}
}
if ( self->radius )
{
vec3_t eyeSpot;
//Only works if your head is in it, but we allow leaning out
//NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this
//to be reliant on the physical model the player uses.
VectorCopy(other->client->ps.origin, eyeSpot);
eyeSpot[2] += other->client->ps.viewheight;
if ( G_PointInBounds( eyeSpot, self->r.absmin, self->r.absmax ) )
{
if( !( other->client->pers.cmd.buttons & BUTTON_ATTACK ) &&
!( other->client->pers.cmd.buttons & BUTTON_ALT_ATTACK ) )
{//not attacking, so hiding bonus
/*
//FIXME: should really have sound events clear the hiddenDist
other->client->hiddenDist = self->radius;
//NOTE: movedir HAS to be normalized!
if ( VectorLength( self->movedir ) )
{//They can only be hidden from enemies looking in this direction
VectorCopy( self->movedir, other->client->hiddenDir );
}
else
{
VectorClear( other->client->hiddenDir );
}
*/
//Not using this, at least not yet.
}
}
}
if ( self->spawnflags & 4 )
{//USE_BUTTON
if (other->client->ps.torsoAnim != BOTH_BUTTON_HOLD &&
other->client->ps.torsoAnim != BOTH_CONSOLE1)
{
G_SetAnim( other, NULL, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
}
else
{
other->client->ps.torsoTimer = 500;
}
other->client->ps.weaponTime = other->client->ps.torsoTimer;
}
if ( self->think == trigger_cleared_fire )
{//We're waiting to fire our target2 first
self->nextthink = level.time + self->speed;
return;
}
multi_trigger( self, other );
}
void trigger_cleared_fire (gentity_t *self)
{
G_UseTargets2( self, self->activator, self->target2 );
self->think = 0;
// should start the wait timer now, because the trigger's just been cleared, so we must "wait" from this point
if ( self->wait > 0 )
{
self->nextthink = level.time + ( self->wait + self->random * Q_flrand(-1.0f, 1.0f) ) * 1000;
}
}
/*QUAKED trigger_multiple (.1 .5 .1) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON NPCONLY x x INACTIVE MULTIPLE
CLIENTONLY - only a player can trigger this by touch
FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
NPCONLY - only non-player NPCs can trigger this by touch
INACTIVE - Start off, has to be activated to be touchable/usable
MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
"wait" Seconds between triggerings, 0 default, number < 0 means one time only.
"random" wait variance, default is 0
"delay" how many seconds to wait to fire targets after tripped
"hiderange" As long as NPC's head is in this trigger, NPCs out of this hiderange cannot see him. If you set an angle on the trigger, they're only hidden from enemies looking in that direction. the player's crouch viewheight is 36, his standing viewheight is 54. So a trigger thast should hide you when crouched but not standing should be 48 tall.
"target2" The trigger will fire this only when the trigger has been activated and subsequently 'cleared'( once any of the conditions on the trigger have not been satisfied). This will not fire the "target" more than once until the "target2" is fired (trigger field is 'cleared')
"speed" How many seconds to wait to fire the target2, default is 1
"noise" Sound to play when the trigger fires (plays at activator's origin)
"NPC_targetname" Only the NPC with this NPC_targetname fires this trigger
Variable sized repeatable trigger. Must be targeted at one or more entities.
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
"team" - If set, only this team can trip this trigger
0 - any
1 - red
2 - blue
"soundSet" Ambient sound set to play when this trigger is activated
usetime - If specified (in milliseconds) along with the USE_BUTTON flag, will
require a client to hold the use key for x amount of ms before firing.
Applicable only during Siege gametype:
teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
siegetrig - if non-0, can only be activated by players carrying a misc_siege_item
which is associated with this trigger by the item's goaltarget value.
teambalance - if non-0, is "owned" by the last team that activated. Can only be activated
by the other team if the number of players on the other team inside the
trigger outnumber the number of players on the owning team inside the
trigger.
target3 - fire when activated by team1
target4 - fire when activated by team2
idealclass - Can only be used by this class/these classes. You can specify use by
multiple classes with the use of |, e.g.:
"Imperial Medic|Imperial Assassin|Imperial Demolitionist"
*/
void SP_trigger_multiple( gentity_t *ent )
{
char *s;
if ( G_SpawnString( "noise", "", &s ) )
{
if (s && s[0])
{
ent->noise_index = G_SoundIndex(s);
}
else
{
ent->noise_index = 0;
}
}
G_SpawnInt("usetime", "0", &ent->genericValue7);
//For siege gametype
G_SpawnInt("siegetrig", "0", &ent->genericValue1);
G_SpawnInt("teambalance", "0", &ent->genericValue2);
G_SpawnInt("delay", "0", &ent->delay);
if ( (ent->wait > 0) && (ent->random >= ent->wait) ) {
ent->random = ent->wait - FRAMETIME;
Com_Printf(S_COLOR_YELLOW"trigger_multiple has random >= wait\n");
}
ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
if ( !ent->speed && ent->target2 && ent->target2[0] )
{
ent->speed = 1000;
}
else
{
ent->speed *= 1000;
}
ent->touch = Touch_Multi;
ent->use = Use_Multi;
if ( ent->team && ent->team[0] )
{
ent->alliedTeam = atoi(ent->team);
ent->team = NULL;
}
InitTrigger( ent );
trap->LinkEntity ((sharedEntity_t *)ent);
}
/*QUAKED trigger_once (.5 1 .5) ? CLIENTONLY FACING USE_BUTTON FIRE_BUTTON x x x INACTIVE MULTIPLE
CLIENTONLY - only a player can trigger this by touch
FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions)
USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions)
FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions)
INACTIVE - Start off, has to be activated to be touchable/usable
MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
"random" wait variance, default is 0
"delay" how many seconds to wait to fire targets after tripped
Variable sized repeatable trigger. Must be targeted at one or more entities.
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
"noise" Sound to play when the trigger fires (plays at activator's origin)
"NPC_targetname" Only the NPC with this NPC_targetname fires this trigger
"team" - If set, only this team can trip this trigger
0 - any
1 - red
2 - blue
"soundSet" Ambient sound set to play when this trigger is activated
usetime - If specified (in milliseconds) along with the USE_BUTTON flag, will
require a client to hold the use key for x amount of ms before firing.
Applicable only during Siege gametype:
teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
siegetrig - if non-0, can only be activated by players carrying a misc_siege_item
which is associated with this trigger by the item's goaltarget value.
idealclass - Can only be used by this class/these classes. You can specify use by
multiple classes with the use of |, e.g.:
"Imperial Medic|Imperial Assassin|Imperial Demolitionist"
*/
void SP_trigger_once( gentity_t *ent )
{
char *s;
if ( G_SpawnString( "noise", "", &s ) )
{
if (s && s[0])
{
ent->noise_index = G_SoundIndex(s);
}
else
{
ent->noise_index = 0;
}
}
G_SpawnInt("usetime", "0", &ent->genericValue7);
//For siege gametype
G_SpawnInt("siegetrig", "0", &ent->genericValue1);
G_SpawnInt("delay", "0", &ent->delay);
ent->wait = -1;
ent->touch = Touch_Multi;
ent->use = Use_Multi;
if ( ent->team && ent->team[0] )
{
ent->alliedTeam = atoi(ent->team);
ent->team = NULL;
}
ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec
InitTrigger( ent );
trap->LinkEntity ((sharedEntity_t *)ent);
}
/*
======================================================================
trigger_lightningstrike -rww
======================================================================
*/
//lightning strike trigger lightning strike event
void Do_Strike(gentity_t *ent)
{
trace_t localTrace;
vec3_t strikeFrom;
vec3_t strikePoint;
vec3_t fxAng;
//maybe allow custom fx direction at some point?
VectorSet(fxAng, 90.0f, 0.0f, 0.0f);
//choose a random point to strike within the bounds of the trigger
strikePoint[0] = flrand(ent->r.absmin[0], ent->r.absmax[0]);
strikePoint[1] = flrand(ent->r.absmin[1], ent->r.absmax[1]);
//consider the bottom mins the ground level
strikePoint[2] = ent->r.absmin[2];
//set the from point
strikeFrom[0] = strikePoint[0];
strikeFrom[1] = strikePoint[1];
strikeFrom[2] = ent->r.absmax[2]-4.0f;
//now trace for damaging stuff, and do the effect
trap->Trace(&localTrace, strikeFrom, NULL, NULL, strikePoint, ent->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
VectorCopy(localTrace.endpos, strikePoint);
if (localTrace.startsolid || localTrace.allsolid)
{ //got a bad spot, think again next frame to try another strike
ent->nextthink = level.time;
return;
}
if (ent->radius)
{ //do a radius damage at the end pos
G_RadiusDamage(strikePoint, ent, ent->damage, ent->radius, ent, NULL, MOD_SUICIDE);
}
else
{ //only damage individuals
gentity_t *trHit = &g_entities[localTrace.entityNum];
if (trHit->inuse && trHit->takedamage)
{ //damage it then
G_Damage(trHit, ent, ent, NULL, trHit->r.currentOrigin, ent->damage, 0, MOD_SUICIDE);
}
}
G_PlayEffectID(ent->genericValue2, strikeFrom, fxAng);
}
//lightning strike trigger think loop
void Think_Strike(gentity_t *ent)
{
if (ent->genericValue1)
{ //turned off currently
return;
}
ent->nextthink = level.time + ent->wait + Q_irand(0, ent->random);
Do_Strike(ent);
}
//lightning strike trigger use event function
void Use_Strike( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
ent->genericValue1 = !ent->genericValue1;
if (!ent->genericValue1)
{ //turn it back on
ent->nextthink = level.time;
}
}
/*QUAKED trigger_lightningstrike (.1 .5 .1) ? START_OFF
START_OFF - start trigger disabled
"lightningfx" effect to use for lightning, MUST be specified
"wait" Seconds between strikes, 1000 default
"random" wait variance, default is 2000
"dmg" damage on strike (default 50)
"radius" if non-0, does a radius damage at the lightning strike
impact point (using this value as the radius). otherwise
will only do line trace damage. default 0.
use to toggle on and off
*/
void SP_trigger_lightningstrike( gentity_t *ent )
{
char *s;
ent->use = Use_Strike;
ent->think = Think_Strike;
ent->nextthink = level.time + 500;
G_SpawnString("lightningfx", "", &s);
if (!s || !s[0])
{
Com_Error(ERR_DROP, "trigger_lightningstrike with no lightningfx");
}
//get a configstring index for it
ent->genericValue2 = G_EffectIndex(s);
if (ent->spawnflags & 1)
{ //START_OFF
ent->genericValue1 = 1;
}
if (!ent->wait)
{ //default 1000
ent->wait = 1000;
}
if (!ent->random)
{ //default 2000
ent->random = 2000;
}
if (!ent->damage)
{ //default 50
ent->damage = 50;
}
InitTrigger( ent );
trap->LinkEntity ((sharedEntity_t *)ent);
}
/*
==============================================================================
trigger_always
==============================================================================
*/
void trigger_always_think( gentity_t *ent ) {
G_UseTargets(ent, ent);
G_FreeEntity( ent );
}
/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
This trigger will always fire. It is activated by the world.
*/
void SP_trigger_always (gentity_t *ent) {
// we must have some delay to make sure our use targets are present
ent->nextthink = level.time + 300;
ent->think = trigger_always_think;
}
/*
==============================================================================
trigger_push
==============================================================================
*/
//trigger_push
#define PUSH_LINEAR 4
#define PUSH_RELATIVE 16
#define PUSH_MULTIPLE 2048
//target_push
#define PUSH_CONSTANT 2
void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
if ( self->flags & FL_INACTIVE )
{//set by target_deactivate
return;
}
if ( !(self->spawnflags&PUSH_LINEAR) )
{//normal throw
if ( !other->client ) {
return;
}
BG_TouchJumpPad( &other->client->ps, &self->s );
return;
}
//linear
if( level.time < self->painDebounceTime + self->wait ) // normal 'wait' check
{
if( self->spawnflags & PUSH_MULTIPLE ) // MULTIPLE - allow multiple entities to touch this trigger in one frame
{
if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger
{
return;
}
}
else // only allowing one ent per frame to touch trigger
{
return;
}
}
/*
//???
// if the player has already activated this trigger this frame
if( other && !other->s.number && self->aimDebounceTime == level.time )
{
return;
}
*/
/*
if( self->spawnflags & PUSH_CONVEYOR )
{ // only push player if he's on the ground
if( other->s.groundEntityNum == ENTITYNUM_NONE )
{
return;
}
}
*/
/*
if ( self->spawnflags & 1 )
{//PLAYERONLY
if ( other->s.number >= MAX_CLIENTS )
{
return;
}
}
else
{
if ( self->spawnflags & 8 )
{//NPCONLY
if ( other->NPC == NULL )
{
return;
}
}
}
*/
if ( !other->client ) {
if ( other->s.pos.trType != TR_STATIONARY && other->s.pos.trType != TR_LINEAR_STOP && other->s.pos.trType != TR_NONLINEAR_STOP && VectorLengthSquared( other->s.pos.trDelta ) )
{//already moving
VectorCopy( other->r.currentOrigin, other->s.pos.trBase );
VectorCopy( self->s.origin2, other->s.pos.trDelta );
other->s.pos.trTime = level.time;
}
return;
}
if ( other->client->ps.pm_type != PM_NORMAL
&& other->client->ps.pm_type != PM_DEAD
&& other->client->ps.pm_type != PM_FREEZE )
{
return;
}
if ( (self->spawnflags&PUSH_RELATIVE) )
{//relative, dir to it * speed
vec3_t dir;
VectorSubtract( self->s.origin2, other->r.currentOrigin, dir );
if ( self->speed )
{
VectorNormalize( dir );
VectorScale( dir, self->speed, dir );
}
VectorCopy( dir, other->client->ps.velocity );
}
else if ( (self->spawnflags&PUSH_LINEAR) )
{//linear dir * speed
VectorScale( self->s.origin2, self->speed, other->client->ps.velocity );
}
else
{
VectorCopy( self->s.origin2, other->client->ps.velocity );
}
//so we don't take damage unless we land lower than we start here...
/*
other->client->ps.forceJumpZStart = 0;
other->client->ps.pm_flags |= PMF_TRIGGER_PUSHED;//pushed by a trigger
other->client->ps.jumpZStart = other->client->ps.origin[2];
*/
if ( self->wait == -1 )
{
self->touch = NULL;
}
else if ( self->wait > 0 )
{
self->painDebounceTime = level.time;
}
/*
if( other && !other->s.number )
{ // mark that the player has activated this trigger this frame
self->aimDebounceTime =level.time;
}
*/
}
/*
=================
AimAtTarget
Calculate origin2 so the target apogee will be hit
=================
*/
void AimAtTarget( gentity_t *self ) {
gentity_t *ent;
vec3_t origin;
float height, gravity, time, forward;
float dist;
VectorAdd( self->r.absmin, self->r.absmax, origin );
VectorScale ( origin, 0.5f, origin );
ent = G_PickTarget( self->target );
if ( !ent ) {
G_FreeEntity( self );
return;
}
if ( self->classname && !Q_stricmp( "trigger_push", self->classname ) )
{
if ( (self->spawnflags&PUSH_RELATIVE) )
{//relative, not an arc or linear
VectorCopy( ent->r.currentOrigin, self->s.origin2 );
return;
}
else if ( (self->spawnflags&PUSH_LINEAR) )
{//linear, not an arc
VectorSubtract( ent->r.currentOrigin, origin, self->s.origin2 );
VectorNormalize( self->s.origin2 );
return;
}
}
if ( self->classname && !Q_stricmp( "target_push", self->classname ) )
{
if( self->spawnflags & PUSH_CONSTANT )
{
VectorSubtract ( ent->s.origin, self->s.origin, self->s.origin2 );
VectorNormalize( self->s.origin2);
VectorScale (self->s.origin2, self->speed, self->s.origin2);
return;
}
}
height = ent->s.origin[2] - origin[2];
gravity = g_gravity.value;
time = sqrt( height / ( .5 * gravity ) );
if ( !time ) {
G_FreeEntity( self );
return;
}
// set s.origin2 to the push velocity
VectorSubtract ( ent->s.origin, origin, self->s.origin2 );
self->s.origin2[2] = 0;
dist = VectorNormalize( self->s.origin2);
forward = dist / time;
VectorScale( self->s.origin2, forward, self->s.origin2 );
self->s.origin2[2] = time * gravity;
}
/*QUAKED trigger_push (.5 .5 .5) ? x x LINEAR x RELATIVE x x INACTIVE MULTIPLE
Must point at a target_position, which will be the apex of the leap.
This will be client side predicted, unlike target_push
LINEAR - Instead of tossing the client at the target_position, it will push them towards it. Must set a "speed" (see below)
RELATIVE - instead of pushing you in a direction that is always from the center of the trigger to the target_position, it pushes *you* toward the target position, relative to your current location (can use with "speed"... if don't set a speed, it will use the distance from you to the target_position)
INACTIVE - not active until targeted by a target_activate
MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0
wait - how long to wait between pushes: -1 = push only once
speed - when used with the LINEAR spawnflag, pushes the client toward the position at a constant speed (default is 1000)
*/
void SP_trigger_push( gentity_t *self ) {
InitTrigger (self);
// unlike other triggers, we need to send this one to the client
self->r.svFlags &= ~SVF_NOCLIENT;
// make sure the client precaches this sound
G_SoundIndex("sound/weapons/force/jump.wav");
self->s.eType = ET_PUSH_TRIGGER;
if ( !(self->spawnflags&2) )
{//start on
self->touch = trigger_push_touch;
}
if ( self->spawnflags & 4 )
{//linear
self->speed = 1000;
}
self->think = AimAtTarget;
self->nextthink = level.time + FRAMETIME;
trap->LinkEntity ((sharedEntity_t *)self);
}
void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) {
if ( !activator->client ) {
return;
}
if ( activator->client->ps.pm_type != PM_NORMAL && activator->client->ps.pm_type != PM_FLOAT ) {
return;
}
G_ActivateBehavior(self,BSET_USE);
VectorCopy (self->s.origin2, activator->client->ps.velocity);
// play fly sound every 1.5 seconds
if ( activator->fly_sound_debounce_time < level.time ) {
activator->fly_sound_debounce_time = level.time + 1500;
if (self->noise_index)
{
G_Sound( activator, CHAN_AUTO, self->noise_index );
}
}
}
/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad CONSTANT
CONSTANT will push activator in direction of 'target' at constant 'speed'
Pushes the activator in the direction.of angle, or towards a target apex.
"speed" defaults to 1000
if "bouncepad", play bounce noise instead of none
*/
void SP_target_push( gentity_t *self ) {
if (!self->speed) {
self->speed = 1000;
}
G_SetMovedir (self->s.angles, self->s.origin2);
VectorScale (self->s.origin2, self->speed, self->s.origin2);
if ( self->spawnflags & 1 ) {
self->noise_index = G_SoundIndex("sound/weapons/force/jump.wav");
} else {
self->noise_index = 0; //G_SoundIndex("sound/misc/windfly.wav");
}
if ( self->target ) {
VectorCopy( self->s.origin, self->r.absmin );
VectorCopy( self->s.origin, self->r.absmax );
self->think = AimAtTarget;
self->nextthink = level.time + FRAMETIME;
}
self->use = Use_target_push;
}
/*
==============================================================================
trigger_teleport
==============================================================================
*/
void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
gentity_t *dest;
if ( self->flags & FL_INACTIVE )
{//set by target_deactivate
return;
}
if ( !other->client ) {
return;
}
if ( other->client->ps.pm_type == PM_DEAD ) {
return;
}
// Spectators only?
if ( ( self->spawnflags & 1 ) &&
other->client->sess.sessionTeam != TEAM_SPECTATOR ) {
return;
}
dest = G_PickTarget( self->target );
if (!dest) {
trap->Print ("Couldn't find teleporter destination\n");
return;
}
TeleportPlayer( other, dest->s.origin, dest->s.angles );
}
/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
Allows client side prediction of teleportation events.
Must point at a target_position, which will be the teleport destination.
If spectator is set, only spectators can use this teleport
Spectator teleporters are not normally placed in the editor, but are created
automatically near doors to allow spectators to move through them
*/
void SP_trigger_teleport( gentity_t *self ) {
InitTrigger (self);
// unlike other triggers, we need to send this one to the client
// unless is a spectator trigger
if ( self->spawnflags & 1 ) {
self->r.svFlags |= SVF_NOCLIENT;
} else {
self->r.svFlags &= ~SVF_NOCLIENT;
}
// make sure the client precaches this sound
G_SoundIndex("sound/weapons/force/speed.wav");
self->s.eType = ET_TELEPORT_TRIGGER;
self->touch = trigger_teleporter_touch;
trap->LinkEntity ((sharedEntity_t *)self);
}
/*
==============================================================================
trigger_hurt
==============================================================================
*/
/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF CAN_TARGET SILENT NO_PROTECTION SLOW
Any entity that touches this will be hurt.
It does dmg points of damage each server frame
Targeting the trigger will toggle its on / off state.
CAN_TARGET if you target it, it will toggle on and off
SILENT supresses playing the sound
SLOW changes the damage rate to once per second
NO_PROTECTION *nothing* stops the damage
"team" team (1 or 2) to allow hurting (if none then hurt anyone) only applicable for siege
"dmg" default 5 (whole numbers only)
If dmg is set to -1 this brush will use the fade-kill method
*/
void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
if (activator && activator->inuse && activator->client)
{
self->activator = activator;
}
else
{
self->activator = NULL;
}
G_ActivateBehavior(self,BSET_USE);
if ( self->r.linked ) {
trap->UnlinkEntity( (sharedEntity_t *)self );
} else {
trap->LinkEntity( (sharedEntity_t *)self );
}
}
void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
int dflags;
if (level.gametype == GT_SIEGE && self->team && self->team[0])
{
int team = atoi(self->team);
if (other->inuse && other->s.number < MAX_CLIENTS && other->client &&
other->client->sess.sessionTeam != team)
{ //real client don't hurt
return;
}
else if (other->inuse && other->client && other->s.eType == ET_NPC &&
other->s.NPC_class == CLASS_VEHICLE && other->s.teamowner != team)
{ //vehicle owned by team don't hurt
return;
}
}
if ( self->flags & FL_INACTIVE )
{//set by target_deactivate
return;
}
if ( !other->takedamage ) {
return;
}
if ( self->timestamp > level.time ) {
return;
}
if (self->damage == -1 && other && other->client && other->health < 1)
{
other->client->ps.fallingToDeath = 0;
ClientRespawn(other);
return;
}
if (self->damage == -1 && other && other->client && other->client->ps.fallingToDeath)
{
return;
}
if ( self->spawnflags & 16 ) {
self->timestamp = level.time + 1000;
} else {
self->timestamp = level.time + FRAMETIME;
}
// play sound
/*
if ( !(self->spawnflags & 4) && self->damage != -1 ) {
G_Sound( other, CHAN_AUTO, self->noise_index );
}
*/
if (self->spawnflags & 8)
dflags = DAMAGE_NO_PROTECTION;
else
dflags = 0;
if (self->damage == -1 && other && other->client)
{
if (other->client->ps.otherKillerTime > level.time)
{ //we're as good as dead, so if someone pushed us into this then remember them
other->client->ps.otherKillerTime = level.time + 20000;
other->client->ps.otherKillerDebounceTime = level.time + 10000;
}
other->client->ps.fallingToDeath = level.time;
//rag on the way down, this flag will automatically be cleared for us on respawn
other->client->ps.eFlags |= EF_RAG;
//make sure his jetpack is off
Jetpack_Off(other);
if (other->NPC)
{ //kill it now
vec3_t vDir;
VectorSet(vDir, 0, 1, 0);
G_Damage(other, other, other, vDir, other->client->ps.origin, Q3_INFINITE, 0, MOD_FALLING);
}
else
{
G_EntitySound(other, CHAN_VOICE, G_SoundIndex("*falling1.wav"));
}
self->timestamp = 0; //do not ignore others
}
else
{
int dmg = self->damage;
if (dmg == -1)
{ //so fall-to-blackness triggers destroy evertyhing
dmg = 99999;
self->timestamp = 0;
}
if (self->activator && self->activator->inuse && self->activator->client)
{
G_Damage (other, self->activator, self->activator, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
}
else
{
G_Damage (other, self, self, NULL, NULL, dmg, dflags|DAMAGE_NO_PROTECTION, MOD_TRIGGER_HURT);
}
}
}
void SP_trigger_hurt( gentity_t *self ) {
InitTrigger (self);
gTrigFallSound = G_SoundIndex("*falling1.wav");
self->noise_index = G_SoundIndex( "sound/weapons/force/speed.wav" );
self->touch = hurt_touch;
if ( !self->damage ) {
self->damage = 5;
}
self->r.contents = CONTENTS_TRIGGER;
if ( self->spawnflags & 2 ) {
self->use = hurt_use;
}
// link in to the world if starting active
if ( ! (self->spawnflags & 1) ) {
trap->LinkEntity ((sharedEntity_t *)self);
}
else if (self->r.linked)
{
trap->UnlinkEntity((sharedEntity_t *)self);
}
}
#define INITIAL_SUFFOCATION_DELAY 500 //.5 seconds
void space_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
if (!other || !other->inuse || !other->client )
//NOTE: we need vehicles to know this, too...
//|| other->s.number >= MAX_CLIENTS)
{
return;
}
if ( other->s.number < MAX_CLIENTS//player
&& other->client->ps.m_iVehicleNum//in a vehicle
&& other->client->ps.m_iVehicleNum >= MAX_CLIENTS )
{//a player client inside a vehicle
gentity_t *veh = &g_entities[other->client->ps.m_iVehicleNum];
if (veh->inuse && veh->client && veh->m_pVehicle &&
veh->m_pVehicle->m_pVehicleInfo->hideRider)
{ //if they are "inside" a vehicle, then let that protect them from THE HORRORS OF SPACE.
other->client->inSpaceSuffocation = 0;
other->client->inSpaceIndex = ENTITYNUM_NONE;
return;
}
}
if (!G_PointInBounds(other->client->ps.origin, self->r.absmin, self->r.absmax))
{ //his origin must be inside the trigger
return;
}
if (!other->client->inSpaceIndex ||
other->client->inSpaceIndex == ENTITYNUM_NONE)
{ //freshly entering space
other->client->inSpaceSuffocation = level.time + INITIAL_SUFFOCATION_DELAY;
}
other->client->inSpaceIndex = self->s.number;
}
/*QUAKED trigger_space (.5 .5 .5) ?
causes human clients to suffocate and have no gravity.
*/
void SP_trigger_space(gentity_t *self)
{
InitTrigger(self);
self->r.contents = CONTENTS_TRIGGER;
self->touch = space_touch;
trap->LinkEntity((sharedEntity_t *)self);
}
void shipboundary_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
gentity_t *ent;
if (!other || !other->inuse || !other->client ||
other->s.number < MAX_CLIENTS ||
!other->m_pVehicle)
{ //only let vehicles touch
return;
}
if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
{//don't interfere with hyperspacing ships
return;
}
ent = G_Find (NULL, FOFS(targetname), self->target);
if (!ent || !ent->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_shipboundary has invalid target '%s'\n", self->target );
return;
}
if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
{ //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
return;
}
//make sure this sucker is linked so the prediction knows where to go
trap->LinkEntity((sharedEntity_t *)ent);
other->client->ps.vehTurnaroundIndex = ent->s.number;
other->client->ps.vehTurnaroundTime = level.time + (self->genericValue1*2);
//keep up the detailed checks for another 2 seconds
self->genericValue7 = level.time + 2000;
}
void shipboundary_think(gentity_t *ent)
{
int iEntityList[MAX_GENTITIES];
int numListedEntities;
int i = 0;
gentity_t *listedEnt;
ent->nextthink = level.time + 100;
if (ent->genericValue7 < level.time)
{ //don't need to be doing this check, no one has touched recently
return;
}
numListedEntities = trap->EntitiesInBox( ent->r.absmin, ent->r.absmax, iEntityList, MAX_GENTITIES );
while (i < numListedEntities)
{
listedEnt = &g_entities[iEntityList[i]];
if (listedEnt->inuse && listedEnt->client && listedEnt->client->ps.m_iVehicleNum)
{
if (listedEnt->s.eType == ET_NPC &&
listedEnt->s.NPC_class == CLASS_VEHICLE)
{
Vehicle_t *pVeh = listedEnt->m_pVehicle;
if (pVeh && pVeh->m_pVehicleInfo->type == VH_FIGHTER)
{
shipboundary_touch(ent, listedEnt, NULL);
}
}
}
i++;
}
}
/*QUAKED trigger_shipboundary (.5 .5 .5) ?
causes vehicle to turn toward target and travel in that direction for a set time when hit.
"target" name of entity to turn toward (can be info_notnull, or whatever).
"traveltime" time to travel in this direction
*/
void SP_trigger_shipboundary(gentity_t *self)
{
InitTrigger(self);
self->r.contents = CONTENTS_TRIGGER;
if (!self->target || !self->target[0])
{
trap->Error( ERR_DROP, "trigger_shipboundary without a target." );
}
G_SpawnInt("traveltime", "0", &self->genericValue1);
if (!self->genericValue1)
{
trap->Error( ERR_DROP, "trigger_shipboundary without traveltime." );
}
self->think = shipboundary_think;
self->nextthink = level.time + 500;
self->touch = shipboundary_touch;
trap->LinkEntity((sharedEntity_t *)self);
}
void hyperspace_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
gentity_t *ent;
if (!other || !other->inuse || !other->client ||
other->s.number < MAX_CLIENTS ||
!other->m_pVehicle)
{ //only let vehicles touch
return;
}
if ( other->client->ps.hyperSpaceTime && level.time - other->client->ps.hyperSpaceTime < HYPERSPACE_TIME )
{//already hyperspacing, just keep us moving
if ( (other->client->ps.eFlags2&EF2_HYPERSPACE) )
{//they've started the hyperspace but haven't been teleported yet
float timeFrac = ((float)(level.time-other->client->ps.hyperSpaceTime))/HYPERSPACE_TIME;
if ( timeFrac >= HYPERSPACE_TELEPORT_FRAC )
{//half-way, now teleport them!
vec3_t diff, fwd, right, up, newOrg;
float fDiff, rDiff, uDiff;
//take off the flag so we only do this once
other->client->ps.eFlags2 &= ~EF2_HYPERSPACE;
//Get the offset from the local position
ent = G_Find (NULL, FOFS(targetname), self->target);
if (!ent || !ent->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
return;
}
VectorSubtract( other->client->ps.origin, ent->s.origin, diff );
AngleVectors( ent->s.angles, fwd, right, up );
fDiff = DotProduct( fwd, diff );
rDiff = DotProduct( right, diff );
uDiff = DotProduct( up, diff );
//Now get the base position of the destination
ent = G_Find (NULL, FOFS(targetname), self->target2);
if (!ent || !ent->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_hyperspace has invalid target2 '%s'\n", self->target2 );
return;
}
VectorCopy( ent->s.origin, newOrg );
//finally, add the offset into the new origin
AngleVectors( ent->s.angles, fwd, right, up );
VectorMA( newOrg, fDiff, fwd, newOrg );
VectorMA( newOrg, rDiff, right, newOrg );
VectorMA( newOrg, uDiff, up, newOrg );
//trap->Print("hyperspace from %s to %s\n", vtos(other->client->ps.origin), vtos(newOrg) );
//now put them in the offset position, facing the angles that position wants them to be facing
TeleportPlayer( other, newOrg, ent->s.angles );
if ( other->m_pVehicle && other->m_pVehicle->m_pPilot )
{//teleport the pilot, too
TeleportPlayer( (gentity_t*)other->m_pVehicle->m_pPilot, newOrg, ent->s.angles );
//FIXME: and the passengers?
}
//make them face the new angle
//other->client->ps.hyperSpaceIndex = ent->s.number;
VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
//sound
G_Sound( other, CHAN_LOCAL, G_SoundIndex( "sound/vehicles/common/hyperend.wav" ) );
}
}
return;
}
else
{
ent = G_Find (NULL, FOFS(targetname), self->target);
if (!ent || !ent->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
return;
}
if (!other->client->ps.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces)
{ //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up
G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
return;
}
//other->client->ps.hyperSpaceIndex = ent->s.number;
VectorCopy( ent->s.angles, other->client->ps.hyperSpaceAngles );
other->client->ps.hyperSpaceTime = level.time;
}
}
/*
void trigger_hyperspace_find_targets( gentity_t *self )
{
gentity_t *targEnt = NULL;
targEnt = G_Find (NULL, FOFS(targetname), self->target);
if (!targEnt || !targEnt->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_hyperspace has invalid target '%s'\n", self->target );
return;
}
targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
targEnt = G_Find (NULL, FOFS(targetname), self->target2);
if (!targEnt || !targEnt->inuse)
{ //this is bad
trap->Error( ERR_DROP, "trigger_hyperspace has invalid target2 '%s'\n", self->target2 );
return;
}
targEnt->r.svFlags |= SVF_BROADCAST;//crap, need to tell the cgame about the target_position
}
*/
/*QUAKED trigger_hyperspace (.5 .5 .5) ?
Ship will turn to face the angles of the first target_position then fly forward, playing the hyperspace effect, then pop out at a relative point around the target
"target" whatever position the ship teleports from in relation to the target_position specified here, that's the relative position the ship will spawn at around the target2 target_position
"target2" name of target_position to teleport the ship to (will be relative to it's origin)
*/
void SP_trigger_hyperspace(gentity_t *self)
{
//register the hyperspace end sound (start sounds are customized)
G_SoundIndex( "sound/vehicles/common/hyperend.wav" );
InitTrigger(self);
self->r.contents = CONTENTS_TRIGGER;
if (!self->target || !self->target[0])
{
trap->Error( ERR_DROP, "trigger_hyperspace without a target." );
}
if (!self->target2 || !self->target2[0])
{
trap->Error( ERR_DROP, "trigger_hyperspace without a target2." );
}
self->delay = Distance( self->r.absmax, self->r.absmin );//my size
self->touch = hyperspace_touch;
trap->LinkEntity((sharedEntity_t *)self);
//self->think = trigger_hyperspace_find_targets;
//self->nextthink = level.time + FRAMETIME;
}
/*
==============================================================================
timer
==============================================================================
*/
/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
This should be renamed trigger_timer...
Repeatedly fires its targets.
Can be turned on or off by using.
"wait" base time between triggering all targets, default is 1
"random" wait variance, default is 0
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
*/
void func_timer_think( gentity_t *self ) {
G_UseTargets (self, self->activator);
// set time before next firing
self->nextthink = level.time + 1000 * ( self->wait + Q_flrand(-1.0f, 1.0f) * self->random );
}
void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
self->activator = activator;
G_ActivateBehavior(self,BSET_USE);
// if on, turn it off
if ( self->nextthink ) {
self->nextthink = 0;
return;
}
// turn it on
func_timer_think (self);
}
void SP_func_timer( gentity_t *self ) {
G_SpawnFloat( "random", "1", &self->random);
G_SpawnFloat( "wait", "1", &self->wait );
self->use = func_timer_use;
self->think = func_timer_think;
if ( self->random >= self->wait ) {
self->random = self->wait - 1;//NOTE: was - FRAMETIME, but FRAMETIME is in msec (100) and these numbers are in *seconds*!
trap->Print( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
}
if ( self->spawnflags & 1 ) {
self->nextthink = level.time + FRAMETIME;
self->activator = self;
}
self->r.svFlags = SVF_NOCLIENT;
}
gentity_t *asteroid_pick_random_asteroid( gentity_t *self )
{
int t_count = 0, pick;
gentity_t *t = NULL;
while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
{
if (t != self)
{
t_count++;
}
}
if(!t_count)
{
return NULL;
}
if(t_count == 1)
{
return t;
}
//FIXME: need a seed
pick = Q_irand(1, t_count);
t_count = 0;
while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL )
{
if (t != self)
{
t_count++;
}
else
{
continue;
}
if(t_count == pick)
{
return t;
}
}
return NULL;
}
int asteroid_count_num_asteroids( gentity_t *self )
{
int i, count = 0;
for ( i = MAX_CLIENTS; i < ENTITYNUM_WORLD; i++ )
{
if ( !g_entities[i].inuse )
{
continue;
}
if ( g_entities[i].r.ownerNum == self->s.number )
{
count++;
}
}
return count;
}
extern void SP_func_rotating (gentity_t *ent);
extern void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration );
void asteroid_field_think(gentity_t *self)
{
int numAsteroids = asteroid_count_num_asteroids( self );
self->nextthink = level.time + 500;
if ( numAsteroids < self->count )
{
//need to spawn a new asteroid
gentity_t *newAsteroid = G_Spawn();
if ( newAsteroid )
{
vec3_t startSpot, endSpot, startAngles;
float dist, speed = flrand( self->speed * 0.25f, self->speed * 2.0f );
int capAxis, axis, time = 0;
gentity_t *copyAsteroid = asteroid_pick_random_asteroid( self );
if ( copyAsteroid )
{
newAsteroid->model = copyAsteroid->model;
newAsteroid->model2 = copyAsteroid->model2;
newAsteroid->health = copyAsteroid->health;
newAsteroid->spawnflags = copyAsteroid->spawnflags;
newAsteroid->mass = copyAsteroid->mass;
newAsteroid->damage = copyAsteroid->damage;
newAsteroid->speed = copyAsteroid->speed;
G_SetOrigin( newAsteroid, copyAsteroid->s.origin );
G_SetAngles( newAsteroid, copyAsteroid->s.angles );
newAsteroid->classname = "func_rotating";
SP_func_rotating( newAsteroid );
newAsteroid->genericValue15 = copyAsteroid->genericValue15;
newAsteroid->s.iModelScale = copyAsteroid->s.iModelScale;
newAsteroid->maxHealth = newAsteroid->health;
G_ScaleNetHealth(newAsteroid);
newAsteroid->radius = copyAsteroid->radius;
newAsteroid->material = copyAsteroid->material;
//CacheChunkEffects( self->material );
//keep track of it
newAsteroid->r.ownerNum = self->s.number;
//move it
capAxis = Q_irand( 0, 2 );
for ( axis = 0; axis < 3; axis++ )
{
if ( axis == capAxis )
{
if ( Q_irand( 0, 1 ) )
{
startSpot[axis] = self->r.mins[axis];
endSpot[axis] = self->r.maxs[axis];
}
else
{
startSpot[axis] = self->r.maxs[axis];
endSpot[axis] = self->r.mins[axis];
}
}
else
{
startSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
endSpot[axis] = self->r.mins[axis]+(flrand(0,1.0f)*(self->r.maxs[axis]-self->r.mins[axis]));
}
}
//FIXME: maybe trace from start to end to make sure nothing is in the way? How big of a trace?
G_SetOrigin( newAsteroid, startSpot );
dist = Distance( endSpot, startSpot );
time = ceil(dist/speed)*1000;
Q3_Lerp2Origin( -1, newAsteroid->s.number, endSpot, time );
//spin it
startAngles[0] = flrand( -360, 360 );
startAngles[1] = flrand( -360, 360 );
startAngles[2] = flrand( -360, 360 );
G_SetAngles( newAsteroid, startAngles );
newAsteroid->s.apos.trDelta[0] = flrand( -100, 100 );
newAsteroid->s.apos.trDelta[1] = flrand( -100, 100 );
newAsteroid->s.apos.trDelta[2] = flrand( -100, 100 );
newAsteroid->s.apos.trTime = level.time;
newAsteroid->s.apos.trType = TR_LINEAR;
//remove itself when done
newAsteroid->think = G_FreeEntity;
newAsteroid->nextthink = level.time+time;
//think again sooner if need even more
if ( numAsteroids+1 < self->count )
{//still need at least one more
//spawn it in 100ms
self->nextthink = level.time + 100;
}
}
}
}
}
/*QUAKED trigger_asteroid_field (.5 .5 .5) ?
speed - how fast, on average, the asteroid moves
count - how many asteroids, max, to have at one time
target - target this at func_rotating asteroids
*/
void SP_trigger_asteroid_field(gentity_t *self)
{
trap->SetBrushModel( (sharedEntity_t *)self, self->model );
self->r.contents = 0;
if ( !self->count )
{
self->health = 20;
}
if ( !self->speed )
{
self->speed = 10000;
}
self->think = asteroid_field_think;
self->nextthink = level.time + 100;
trap->LinkEntity((sharedEntity_t *)self);
}