mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-27 06:22:02 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2000 lines
54 KiB
C
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);
|
|
}
|