mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
2086 lines
53 KiB
C
2086 lines
53 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1997-2001 Id Software, Inc.
|
|
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog
|
|
|
|
This file is part of Lazarus Quake 2 Mod source code.
|
|
|
|
Lazarus Quake 2 Mod source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Lazarus Quake 2 Mod source code 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 Lazarus Quake 2 Mod source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
|
|
#define TRIGGER_MONSTER 1
|
|
#define TRIGGER_NOT_PLAYER 2
|
|
#define TRIGGER_START_OFF 4
|
|
#define TRIGGER_NEED_USE 8
|
|
#define TRIGGER_CAMOWNER 16
|
|
#define TRIGGER_LOOKTARGET 32
|
|
#define TRIGGER_TARGET_OFF 64
|
|
|
|
void InitTrigger (edict_t *self)
|
|
{
|
|
if (!VectorCompare (self->s.angles, vec3_origin))
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
|
|
self->solid = SOLID_TRIGGER;
|
|
self->movetype = MOVETYPE_NONE;
|
|
gi.setmodel (self, self->model);
|
|
self->svflags = SVF_NOCLIENT;
|
|
}
|
|
|
|
|
|
// the wait time has passed, so set back up for another activation
|
|
void multi_wait (edict_t *ent)
|
|
{
|
|
ent->nextthink = 0;
|
|
}
|
|
|
|
|
|
// 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 (edict_t *ent)
|
|
{
|
|
if (ent->nextthink)
|
|
return; // already been triggered
|
|
|
|
G_UseTargets (ent, ent->activator);
|
|
|
|
if (ent->wait > 0)
|
|
{
|
|
ent->think = multi_wait;
|
|
ent->nextthink = level.time + ent->wait;
|
|
}
|
|
else
|
|
{ // we can't just remove (self) here, because this is a touch function
|
|
// called while looping through area links...
|
|
ent->touch = NULL;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
ent->think = G_FreeEdict;
|
|
}
|
|
}
|
|
|
|
void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator)
|
|
{
|
|
ent->activator = activator;
|
|
multi_trigger (ent);
|
|
}
|
|
|
|
void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if(other->client || (other->flags & FL_ROBOT))
|
|
{
|
|
if (self->spawnflags & TRIGGER_NOT_PLAYER)
|
|
return;
|
|
}
|
|
else if (other->svflags & SVF_MONSTER)
|
|
{
|
|
if (!(self->spawnflags & TRIGGER_MONSTER))
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
|
|
if( (self->spawnflags & TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam))
|
|
return;
|
|
|
|
if (!VectorCompare(self->movedir, vec3_origin))
|
|
{
|
|
vec3_t forward;
|
|
|
|
AngleVectors(other->s.angles, forward, NULL, NULL);
|
|
if (_DotProduct(forward, self->movedir) < 0)
|
|
return;
|
|
}
|
|
|
|
self->activator = other;
|
|
multi_trigger (self);
|
|
}
|
|
|
|
/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
|
|
Variable sized repeatable trigger. Must be targeted at one or more entities.
|
|
If "delay" is set, the trigger waits some time after activating before firing.
|
|
"wait" : Seconds between triggerings. (.2 default)
|
|
sounds
|
|
1) secret
|
|
2) beep beep
|
|
3) large switch
|
|
4)
|
|
set "message" to text string
|
|
*/
|
|
void trigger_enable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = Use_Multi;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void SP_trigger_multiple (edict_t *ent)
|
|
{
|
|
if (ent->sounds == 1)
|
|
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (ent->sounds == 2)
|
|
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (ent->sounds == 3)
|
|
// DWH - should be silent
|
|
// ent->noise_index = gi.soundindex ("misc/trigger1.wav");
|
|
ent->noise_index = -1;
|
|
|
|
if (!ent->wait)
|
|
ent->wait = 0.2;
|
|
ent->touch = Touch_Multi;
|
|
ent->movetype = MOVETYPE_NONE;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
|
|
if (ent->spawnflags & TRIGGER_CAMOWNER)
|
|
ent->svflags |= SVF_TRIGGER_CAMOWNER;
|
|
|
|
if (ent->spawnflags & TRIGGER_START_OFF)
|
|
{
|
|
ent->solid = SOLID_NOT;
|
|
ent->use = trigger_enable;
|
|
}
|
|
else
|
|
{
|
|
ent->solid = SOLID_TRIGGER;
|
|
ent->use = Use_Multi;
|
|
}
|
|
|
|
if (!VectorCompare(ent->s.angles, vec3_origin))
|
|
G_SetMovedir (ent->s.angles, ent->movedir);
|
|
|
|
gi.setmodel (ent, ent->model);
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
|
|
/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
|
|
Triggers once, then removes itself.
|
|
You must set the key "target" to the name of another object in the level that has a matching "targetname".
|
|
|
|
If TRIGGERED, this trigger must be triggered before it is live.
|
|
|
|
sounds
|
|
1) secret
|
|
2) beep beep
|
|
3) large switch
|
|
4)
|
|
|
|
"message" string to be displayed when triggered
|
|
*/
|
|
|
|
void SP_trigger_once(edict_t *ent)
|
|
{
|
|
// make old maps work because I messed up on flag assignments here
|
|
// triggered was on bit 1 when it should have been on bit 4
|
|
if (ent->spawnflags & 1)
|
|
{
|
|
vec3_t v;
|
|
|
|
VectorMA (ent->mins, 0.5, ent->size, v);
|
|
ent->spawnflags &= ~1;
|
|
ent->spawnflags |= TRIGGER_START_OFF;
|
|
gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
|
|
}
|
|
|
|
ent->wait = -1;
|
|
SP_trigger_multiple (ent);
|
|
}
|
|
|
|
/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
|
This fixed size trigger cannot be touched, it can only be fired by other events.
|
|
*/
|
|
void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->count--;
|
|
if(!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
G_UseTargets (self, activator);
|
|
}
|
|
|
|
void SP_trigger_relay (edict_t *self)
|
|
{
|
|
// DWH - gives trigger_relay same message-displaying, sound-playing capabilities
|
|
// as trigger_multiple and trigger_once
|
|
if (self->sounds == 1)
|
|
self->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (self->sounds == 2)
|
|
self->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (self->sounds == 3)
|
|
self->noise_index = -1;
|
|
if(!self->count) self->count=-1;
|
|
// end DWH
|
|
|
|
self->use = trigger_relay_use;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_key
|
|
|
|
Lazarus editions:
|
|
Spawnflags
|
|
1 = Multi-use. If set, item is required EVERY time trigger_key is targeted
|
|
2 = Keep key. If set, player doesn't give up the key when trigger_key is used.
|
|
4 = Silent. If set, neither the "You need" message and sound nor keyuse.wav
|
|
are played. This is useful for trigger_keys used only to remove items from
|
|
the player.
|
|
|
|
Lazarus also removes non-multi-use trigger_keys once used, to free up space in
|
|
the edicts array.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
|
|
A relay trigger that only fires it's targets if player has the proper key.
|
|
Use "item" to specify the required key, for example "key_data_cd"
|
|
*/
|
|
void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
int index;
|
|
|
|
if (!self->item)
|
|
return;
|
|
if (!activator->client)
|
|
return;
|
|
|
|
index = ITEM_INDEX(self->item);
|
|
if (!activator->client->pers.inventory[index])
|
|
{
|
|
if (level.time < self->touch_debounce_time)
|
|
return;
|
|
self->touch_debounce_time = level.time + 5.0;
|
|
if(!(self->spawnflags & 4))
|
|
{ // Knightmare- implement key_message
|
|
if (self->key_message && strlen(self->key_message))
|
|
safe_centerprintf (activator, "%s", self->key_message);
|
|
else
|
|
safe_centerprintf (activator, "You need the %s", self->item->pickup_name);
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(!(self->spawnflags & 4))
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0);
|
|
if (coop->value)
|
|
{
|
|
int player;
|
|
edict_t *ent;
|
|
|
|
if (strcmp(self->item->classname, "key_power_cube") == 0)
|
|
{
|
|
int cube;
|
|
|
|
for (cube = 0; cube < 8; cube++)
|
|
if (activator->client->pers.power_cubes & (1 << cube))
|
|
break;
|
|
for (player = 1; player <= game.maxclients; player++)
|
|
{
|
|
ent = &g_edicts[player];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (!ent->client)
|
|
continue;
|
|
// DWH: keep key
|
|
if (!(self->spawnflags & 2)) {
|
|
if (ent->client->pers.power_cubes & (1 << cube))
|
|
{
|
|
ent->client->pers.inventory[index]--;
|
|
ent->client->pers.power_cubes &= ~(1 << cube);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (player = 1; player <= game.maxclients; player++)
|
|
{
|
|
ent = &g_edicts[player];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (!ent->client)
|
|
continue;
|
|
// DWH: keep key
|
|
if(!(self->spawnflags & 2))
|
|
ent->client->pers.inventory[index] = 0;
|
|
}
|
|
}
|
|
}
|
|
// DWH: keep key
|
|
else if(!(self->spawnflags & 2))
|
|
{
|
|
activator->client->pers.inventory[index]--;
|
|
}
|
|
|
|
G_UseTargets (self, activator);
|
|
|
|
// DWH - multi-use
|
|
if(!(self->spawnflags & 1)) {
|
|
self->use = NULL;
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
|
|
void SP_trigger_key (edict_t *self)
|
|
{
|
|
if (!st.item)
|
|
{
|
|
gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
|
|
return;
|
|
}
|
|
self->item = FindItemByClassname (st.item);
|
|
|
|
if (!self->item)
|
|
{
|
|
gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin));
|
|
return;
|
|
}
|
|
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin));
|
|
return;
|
|
}
|
|
|
|
gi.soundindex ("misc/keytry.wav");
|
|
gi.soundindex ("misc/keyuse.wav");
|
|
|
|
self->use = trigger_key_use;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_counter
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
|
|
Acts as an intermediary for an action that takes multiple inputs.
|
|
|
|
If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
|
|
|
|
After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
|
|
*/
|
|
|
|
void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->count == 0) {
|
|
G_FreeEdict(self); // DWH
|
|
return;
|
|
}
|
|
|
|
self->count--;
|
|
|
|
if (self->count)
|
|
{
|
|
if (! (self->spawnflags & 1))
|
|
{
|
|
safe_centerprintf(activator, "%i more to go...", self->count);
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (! (self->spawnflags & 1))
|
|
{
|
|
safe_centerprintf(activator, "Sequence completed!");
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
self->activator = activator;
|
|
multi_trigger (self);
|
|
// DWH
|
|
if (self->count == 0) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_trigger_counter (edict_t *self)
|
|
{
|
|
self->wait = -1;
|
|
if (!self->count)
|
|
self->count = 2;
|
|
|
|
self->use = trigger_counter_use;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_always
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*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 (edict_t *ent)
|
|
{
|
|
// we must have some delay to make sure our use targets are present
|
|
if (ent->delay < 0.2)
|
|
ent->delay = 0.2;
|
|
G_UseTargets(ent, ent);
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_push
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#define PUSH_ONCE 1
|
|
|
|
static int windsound;
|
|
|
|
void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (strcmp(other->classname, "grenade") == 0)
|
|
{
|
|
VectorScale (self->movedir, self->speed * 10, other->velocity);
|
|
}
|
|
// Lazarus
|
|
else if (other->movetype == MOVETYPE_PUSHABLE)
|
|
{
|
|
vec3_t v;
|
|
VectorScale (self->movedir, self->speed * 2000 / (float)(other->mass), v);
|
|
VectorAdd(other->velocity,v,other->velocity);
|
|
}
|
|
else if (other->health > 0)
|
|
{
|
|
VectorScale (self->movedir, self->speed * 10, other->velocity);
|
|
if (other->client)
|
|
{
|
|
// don't take falling damage immediately from this
|
|
VectorCopy (other->velocity, other->client->oldvelocity);
|
|
if (other->fly_sound_debounce_time < level.time)
|
|
{
|
|
other->fly_sound_debounce_time = level.time + 1.5;
|
|
if(self->spawnflags & 2) {
|
|
if(self->noise_index)
|
|
gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
|
|
} else
|
|
gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
}
|
|
if (self->spawnflags & PUSH_ONCE)
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
|
|
/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE CUSTOM_SOUND
|
|
Pushes the player
|
|
"speed" defaults to 1000
|
|
*/
|
|
void SP_trigger_push (edict_t *self)
|
|
{
|
|
InitTrigger (self);
|
|
// DWH: Custom (or no) sound
|
|
if(self->spawnflags & 2) {
|
|
if(st.noise)
|
|
self->noise_index = gi.soundindex(st.noise);
|
|
else
|
|
self->noise_index = 0;
|
|
} else {
|
|
windsound = gi.soundindex ("misc/windfly.wav");
|
|
}
|
|
self->touch = trigger_push_touch;
|
|
if (!self->speed)
|
|
self->speed = 1000;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_hurt
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
|
|
Any entity that touches this will be hurt.
|
|
|
|
It does dmg points of damage each server frame
|
|
"dmg" default 5 (whole numbers only) */
|
|
|
|
#define SF_HURT_START_OFF 1
|
|
#define SF_HURT_TOGGLE 2
|
|
#define SF_HURT_SILENT 4 // supresses playing the sound
|
|
#define SF_HURT_NO_PROTECTION 8 // *nothing* stops the damage
|
|
#define SF_HURT_SLOW 16 // changes the damage rate to once per second
|
|
#define SF_HURT_NOGIB 32 // Lazarus: won't gib entity
|
|
#define SF_HURT_ENVIRONMENT 64 // Lazarus: environment suit protects from damage
|
|
|
|
void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
|
|
void hurt_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->solid == SOLID_NOT)
|
|
{
|
|
int i, num;
|
|
edict_t *touch[MAX_EDICTS], *hurtme;
|
|
|
|
self->solid = SOLID_TRIGGER;
|
|
// Lazaurs: Add check for non-moving (i.e. idle monsters) within trigger_hurt
|
|
// at first activation
|
|
num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID);
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
hurtme = touch[i];
|
|
hurt_touch (self, hurtme, NULL, NULL);
|
|
}
|
|
}
|
|
else
|
|
self->solid = SOLID_NOT;
|
|
gi.linkentity (self);
|
|
|
|
if (!(self->spawnflags & SF_HURT_TOGGLE))
|
|
self->use = NULL;
|
|
}
|
|
|
|
|
|
void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
int dflags;
|
|
|
|
if (!other->takedamage)
|
|
return;
|
|
|
|
if (self->timestamp > level.time)
|
|
return;
|
|
|
|
// DWH - don't "heal" other if he's at max_health
|
|
if ( (self->dmg < 0) && (other->health >= other->max_health))
|
|
return;
|
|
|
|
if (self->spawnflags & SF_HURT_SLOW)
|
|
self->timestamp = level.time + 1;
|
|
else
|
|
self->timestamp = level.time + FRAMETIME;
|
|
|
|
if (!(self->spawnflags & SF_HURT_SILENT))
|
|
{
|
|
// DWH - Original code would fail to play a sound for
|
|
// SF=16 unless player just HAPPENED to hit
|
|
// trigger_hurt at framenum = an integral number of
|
|
// full seconds.
|
|
if ( ((level.framenum % 10) == 0 ) || (self->spawnflags & SF_HURT_SLOW) )
|
|
gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
if (self->spawnflags & SF_HURT_NO_PROTECTION)
|
|
dflags = DAMAGE_NO_PROTECTION;
|
|
else
|
|
dflags = 0;
|
|
// Lazarus: healing, no gib, and environment suit protection
|
|
if (self->dmg > 0)
|
|
{
|
|
int damage = self->dmg;
|
|
|
|
if(self->spawnflags & SF_HURT_NOGIB)
|
|
{
|
|
if(skill->value > 0)
|
|
damage = min(damage, other->health - other->gib_health - 1);
|
|
else
|
|
damage = min(damage, 2*(other->health - other->gib_health - 1));
|
|
|
|
if(damage < 0)
|
|
damage = 0;
|
|
}
|
|
|
|
if (other->client && (self->spawnflags & SF_HURT_ENVIRONMENT) && (other->client->enviro_framenum > level.framenum))
|
|
damage = 0;
|
|
|
|
if(damage > 0)
|
|
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, damage, self->dmg, dflags, MOD_TRIGGER_HURT);
|
|
}
|
|
else
|
|
{
|
|
other->health -= self->dmg;
|
|
if(other->health > other->max_health)
|
|
other->health = other->max_health;
|
|
}
|
|
}
|
|
|
|
void SP_trigger_hurt (edict_t *self)
|
|
{
|
|
InitTrigger (self);
|
|
|
|
self->touch = hurt_touch;
|
|
|
|
if (!self->dmg)
|
|
self->dmg = 5;
|
|
|
|
// DWH - play different sound for healing
|
|
if (self->dmg > 0)
|
|
self->noise_index = gi.soundindex ("world/electro.wav");
|
|
else
|
|
self->noise_index = gi.soundindex ("items/s_health.wav");
|
|
|
|
if (self->spawnflags & SF_HURT_START_OFF)
|
|
self->solid = SOLID_NOT;
|
|
else
|
|
self->solid = SOLID_TRIGGER;
|
|
|
|
if (self->spawnflags & SF_HURT_TOGGLE)
|
|
self->use = hurt_use;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_gravity
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*QUAKED trigger_gravity (.5 .5 .5) ?
|
|
Changes the touching entites gravity to
|
|
the value of "gravity". 1.0 is standard
|
|
gravity for the level.
|
|
*/
|
|
|
|
void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
other->gravity = self->gravity;
|
|
}
|
|
|
|
void SP_trigger_gravity (edict_t *self)
|
|
{
|
|
if (st.gravity == 0)
|
|
{
|
|
gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
InitTrigger (self);
|
|
self->gravity = atoi(st.gravity);
|
|
self->touch = trigger_gravity_touch;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
trigger_monsterjump
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*QUAKED trigger_monsterjump (.5 .5 .5) ?
|
|
Walking monsters that touch this will jump in the direction of the trigger's angle
|
|
"speed" default to 200, the speed thrown forward
|
|
"height" default to 200, the speed thrown upwards
|
|
*/
|
|
|
|
void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (other->flags & (FL_FLY | FL_SWIM) )
|
|
return;
|
|
if (other->svflags & SVF_DEADMONSTER)
|
|
return;
|
|
if ( !(other->svflags & SVF_MONSTER))
|
|
return;
|
|
|
|
// set XY even if not on ground, so the jump will clear lips
|
|
other->velocity[0] = self->movedir[0] * self->speed;
|
|
other->velocity[1] = self->movedir[1] * self->speed;
|
|
|
|
if (!other->groundentity)
|
|
return;
|
|
|
|
other->groundentity = NULL;
|
|
other->velocity[2] = self->movedir[2];
|
|
}
|
|
|
|
void SP_trigger_monsterjump (edict_t *self)
|
|
{
|
|
if (!self->speed)
|
|
self->speed = 200;
|
|
if (!st.height)
|
|
st.height = 200;
|
|
if (self->s.angles[YAW] == 0)
|
|
self->s.angles[YAW] = 360;
|
|
InitTrigger (self);
|
|
self->touch = trigger_monsterjump_touch;
|
|
self->movedir[2] = st.height;
|
|
}
|
|
|
|
//===============================================================
|
|
// DWH additions
|
|
//===============================================================
|
|
|
|
/*QUAKED tremor_trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
|
|
Variable sized repeatable trigger. Must be targeted at one or more entities.
|
|
If "delay" is set, the trigger waits some time after activating before firing.
|
|
"wait" : Seconds between triggerings. (.2 default)
|
|
Same as trigger_multiple, but toggles on/off when targeted.
|
|
|
|
sounds
|
|
1) secret
|
|
2) beep beep
|
|
3) large switch
|
|
4)
|
|
set "message" to text string
|
|
*/
|
|
|
|
void tremor_trigger_enable (edict_t *self, edict_t *other, edict_t *activator);
|
|
void Use_tremor_Multi (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->count--;
|
|
if(!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
else
|
|
{
|
|
self->use = tremor_trigger_enable;
|
|
self->solid = SOLID_NOT;
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
|
|
void tremor_trigger_enable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = Use_tremor_Multi;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void SP_tremor_trigger_multiple (edict_t *ent)
|
|
{
|
|
if (ent->sounds == 1)
|
|
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (ent->sounds == 2)
|
|
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (ent->sounds == 3)
|
|
// DWH - should be silent
|
|
// ent->noise_index = gi.soundindex ("misc/trigger1.wav");
|
|
ent->noise_index = -1;
|
|
|
|
if (!ent->wait)
|
|
ent->wait = 0.2;
|
|
ent->touch = Touch_Multi;
|
|
ent->movetype = MOVETYPE_NONE;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
|
|
if (ent->spawnflags & TRIGGER_CAMOWNER)
|
|
ent->svflags |= SVF_TRIGGER_CAMOWNER;
|
|
|
|
if (ent->spawnflags & TRIGGER_START_OFF)
|
|
{
|
|
ent->solid = SOLID_NOT;
|
|
ent->use = tremor_trigger_enable;
|
|
}
|
|
else
|
|
{
|
|
ent->solid = SOLID_TRIGGER;
|
|
ent->use = Use_tremor_Multi;
|
|
}
|
|
|
|
if (!VectorCompare(ent->s.angles, vec3_origin))
|
|
G_SetMovedir (ent->s.angles, ent->movedir);
|
|
|
|
gi.setmodel (ent, ent->model);
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
//=========================================================================================
|
|
// TRIGGER_MASS - triggers its targets when touched by any entity with mass >= mass value
|
|
// of trigger
|
|
//=========================================================================================
|
|
void trigger_mass_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if(other->mass < self->mass) return;
|
|
self->activator = other;
|
|
multi_trigger (self);
|
|
}
|
|
|
|
void SP_trigger_mass (edict_t *self)
|
|
{
|
|
// Fires its target if touched by an entity weighing at least
|
|
// self->mass
|
|
if (self->sounds == 1)
|
|
self->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (self->sounds == 2)
|
|
self->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (self->sounds == 3)
|
|
// DWH - should be silent
|
|
// self->noise_index = gi.soundindex ("misc/trigger1.wav");
|
|
self->noise_index = -1;
|
|
|
|
if(!self->wait) self->wait = 0.2;
|
|
self->touch = trigger_mass_touch;
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
if(self->spawnflags & TRIGGER_START_OFF)
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->use = trigger_enable;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = Use_Multi;
|
|
}
|
|
if(!self->mass)
|
|
self->mass = 100;
|
|
gi.setmodel (self, self->model);
|
|
gi.linkentity (self);
|
|
}
|
|
//=======================================================================================
|
|
// TRIGGER_INSIDE - triggers its targets when the bounding box for its pathtarget is
|
|
// completely inside the trigger field
|
|
//=======================================================================================
|
|
void trigger_inside_think (edict_t *self)
|
|
{
|
|
int i, num;
|
|
edict_t *touch[MAX_EDICTS], *hit;
|
|
|
|
num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID);
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
hit = touch[i];
|
|
if (!hit->inuse) continue;
|
|
if (!hit->targetname) continue;
|
|
if (Q_stricmp(self->pathtarget, hit->targetname)) continue;
|
|
// must be COMPLETELY inside
|
|
if (hit->absmin[0] < self->absmin[0]) continue;
|
|
if (hit->absmin[1] < self->absmin[1]) continue;
|
|
if (hit->absmin[2] < self->absmin[2]) continue;
|
|
if (hit->absmax[0] > self->absmax[0]) continue;
|
|
if (hit->absmax[1] > self->absmax[1]) continue;
|
|
if (hit->absmax[2] > self->absmax[2]) continue;
|
|
G_UseTargets (self, hit);
|
|
if (self->wait > 0)
|
|
self->nextthink = level.time + self->wait;
|
|
else
|
|
{
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = G_FreeEdict;
|
|
}
|
|
gi.linkentity(self);
|
|
return;
|
|
}
|
|
self->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|
|
void SP_trigger_inside (edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
|
|
VectorMA (self->mins, 0.5, self->size, v);
|
|
if(!self->target)
|
|
{
|
|
gi.dprintf("trigger_inside with no target at %s.\n",vtos(v));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if(!self->pathtarget)
|
|
{
|
|
gi.dprintf("trigger_inside with no pathtarget at %s.\n",vtos(v));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_TRIGGER;
|
|
if(!self->wait) self->wait = 0.2;
|
|
gi.setmodel (self,self->model);
|
|
self->think = trigger_inside_think;
|
|
self->nextthink = level.time + 1.0;
|
|
gi.linkentity(self);
|
|
}
|
|
//==================================================================================
|
|
// TRIGGER_SCALES - coupled with target_characters, displays the weight of all
|
|
// entities that are "standing on" the trigger.
|
|
//==================================================================================
|
|
float weight_on_top(edict_t *ent)
|
|
{
|
|
float weight;
|
|
int i;
|
|
edict_t *e;
|
|
weight = 0.0;
|
|
for(i=1, e=g_edicts+i; i<globals.num_edicts; i++, e++)
|
|
{
|
|
if(!e->inuse) continue;
|
|
if(e->groundentity == ent)
|
|
{
|
|
weight += e->mass;
|
|
weight += weight_on_top(e);
|
|
}
|
|
}
|
|
return weight;
|
|
}
|
|
|
|
void trigger_scales_think (edict_t *self)
|
|
{
|
|
float f, fx, fy;
|
|
int i, num;
|
|
int weight;
|
|
edict_t *e, *touch[MAX_EDICTS], *hit;
|
|
|
|
num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID);
|
|
weight = 0;
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
hit = touch[i];
|
|
if (!hit->inuse) continue;
|
|
if (!hit->mass) continue;
|
|
fx = fy = 0.0;
|
|
if(hit->absmin[0] < self->absmin[0])
|
|
fx += (self->absmin[0] - hit->absmin[0])/hit->size[0];
|
|
if(hit->absmax[0] > self->absmax[0])
|
|
fx += (hit->absmax[0] - self->absmax[0])/hit->size[0];
|
|
if(hit->absmin[1] < self->absmin[1])
|
|
fy += (self->absmin[1] - hit->absmin[1])/hit->size[1];
|
|
if(hit->absmax[1] > self->absmax[1])
|
|
fy += (hit->absmax[1] - self->absmax[1])/hit->size[1];
|
|
f = (1.0 - fx - fy + fx*fy);
|
|
if(f > 0) weight += f * hit->mass;
|
|
weight += f*weight_on_top(hit);
|
|
}
|
|
if(weight != self->mass)
|
|
{
|
|
self->mass = weight;
|
|
for (e = self->teammaster; e; e = e->teamchain)
|
|
{
|
|
if (!e->count)
|
|
continue;
|
|
num = e->count;
|
|
if(weight < pow(10,num-1))
|
|
e->s.frame = 12;
|
|
else
|
|
e->s.frame = ( weight % (int)pow(10,num) ) / ( pow(10,num-1) );
|
|
}
|
|
}
|
|
self->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void SP_trigger_scales (edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
|
|
VectorMA (self->mins, 0.5, self->size, v);
|
|
if(!self->team)
|
|
{
|
|
gi.dprintf("trigger_scales with no team at %s.\n",vtos(v));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_TRIGGER;
|
|
gi.setmodel (self,self->model);
|
|
self->think = trigger_scales_think;
|
|
self->nextthink = level.time + 1.0;
|
|
self->mass = 0;
|
|
gi.linkentity(self);
|
|
}
|
|
//======================================================================================
|
|
// TRIGGER_BBOX - Exactly like a tremor_trigger_multiple, but uses bleft, tright fields
|
|
// to define extents of trigger field rather than a brush model. This
|
|
// helps lower the total brush model count, which in turn helps head off
|
|
// Index Overflow errors.
|
|
//======================================================================================
|
|
void trigger_bbox_reset (edict_t *self)
|
|
{
|
|
self->takedamage = DAMAGE_YES;
|
|
self->health = self->max_health;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void trigger_bbox_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
self->activator = attacker;
|
|
self->takedamage = DAMAGE_NO;
|
|
G_UseTargets (self, self->activator);
|
|
self->count--;
|
|
if(!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + self->delay + FRAMETIME;
|
|
return;
|
|
}
|
|
if (self->wait >= 0)
|
|
{
|
|
self->nextthink = level.time + self->wait;
|
|
self->think = trigger_bbox_reset;
|
|
}
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void trigger_bbox_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if(self->nextthink)
|
|
return; // already "touched" and waiting
|
|
if((other->client) && (self->spawnflags & 2))
|
|
return;
|
|
if((other->svflags & SVF_MONSTER) && !(self->spawnflags & 1))
|
|
return;
|
|
if(!other->client && !(other->svflags & SVF_MONSTER))
|
|
return;
|
|
if(other->client && other->client->spycam && !(self->svflags & SVF_TRIGGER_CAMOWNER))
|
|
return;
|
|
if((self->svflags & SVF_TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam))
|
|
return;
|
|
self->activator = other;
|
|
G_UseTargets(self,self->activator);
|
|
if(self->wait > 0)
|
|
{
|
|
self->count--;
|
|
if(!self->count)
|
|
{
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
self->think = multi_wait;
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
void trigger_bbox_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->count--;
|
|
if(!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
else
|
|
{
|
|
if(self->solid == SOLID_NOT)
|
|
{
|
|
if(self->max_health > 0)
|
|
{
|
|
self->solid = SOLID_BBOX;
|
|
self->touch = NULL;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->touch = trigger_bbox_touch;
|
|
}
|
|
}
|
|
else
|
|
self->solid = SOLID_NOT;
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
|
|
void SP_trigger_bbox (edict_t *ent)
|
|
{
|
|
if (ent->sounds == 1)
|
|
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (ent->sounds == 2)
|
|
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (ent->sounds == 3)
|
|
ent->noise_index = -1;
|
|
|
|
if (!ent->wait)
|
|
ent->wait = 0.2;
|
|
|
|
ent->movetype = MOVETYPE_NONE;
|
|
if (ent->spawnflags & TRIGGER_CAMOWNER)
|
|
ent->svflags |= SVF_TRIGGER_CAMOWNER;
|
|
|
|
if ( (!VectorLength(ent->bleft)) && (!VectorLength(ent->tright)) ) {
|
|
VectorSet(ent->bleft,-16,-16,-16);
|
|
VectorSet(ent->tright,16, 16, 16);
|
|
}
|
|
VectorCopy(ent->bleft,ent->mins);
|
|
VectorCopy(ent->tright,ent->maxs);
|
|
|
|
ent->max_health = ent->health;
|
|
if(ent->health > 0)
|
|
{
|
|
ent->svflags |= SVF_DEADMONSTER;
|
|
ent->die = trigger_bbox_die;
|
|
ent->takedamage = DAMAGE_YES;
|
|
}
|
|
else
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
|
|
if (ent->spawnflags & TRIGGER_START_OFF)
|
|
ent->solid = SOLID_NOT;
|
|
else
|
|
{
|
|
if(ent->health)
|
|
{
|
|
ent->solid = SOLID_BBOX;
|
|
ent->touch = NULL;
|
|
}
|
|
else
|
|
{
|
|
ent->solid = SOLID_TRIGGER;
|
|
ent->touch = trigger_bbox_touch;
|
|
}
|
|
}
|
|
ent->use = trigger_bbox_use;
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
//============================================================================================
|
|
// TRIGGER_LOOK
|
|
// Serves the same function as trigger_multiple, but:
|
|
// 1) Is only usable by players
|
|
// 2) Player must be looking at a point within bleft-tright of the origin of the trigger_look
|
|
// 3) If USE spawnflag (=8) is set, player must be pressing +use to trigger
|
|
|
|
void trigger_look_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
trace_t tr;
|
|
vec_t dist;
|
|
vec3_t dir, forward, left, up, end, start;
|
|
|
|
if(!other->client)
|
|
return;
|
|
|
|
if (self->nextthink)
|
|
return; // already been triggered
|
|
|
|
if( (self->spawnflags & TRIGGER_NEED_USE) && !(other->client->use))
|
|
return;
|
|
|
|
if( (self->spawnflags & TRIGGER_CAMOWNER) && !other->client->spycam)
|
|
return;
|
|
|
|
if( self->spawnflags & 32 )
|
|
{
|
|
// Then trigger only fires if looking at TARGET, not trigger bbox
|
|
edict_t *target;
|
|
int num_triggered=0;
|
|
edict_t *what;
|
|
vec3_t endpos;
|
|
|
|
target = G_Find(NULL,FOFS(targetname),self->target);
|
|
while(target && !num_triggered)
|
|
{
|
|
what = LookingAt(other,0,endpos,NULL);
|
|
if(target->inuse && (LookingAt(other,0,NULL,NULL) == target))
|
|
{
|
|
num_triggered++;
|
|
self->activator = other;
|
|
G_UseTarget (self, other, target);
|
|
}
|
|
else
|
|
target = G_Find(target,FOFS(targetname),self->target);
|
|
}
|
|
if(!num_triggered)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(other->client->spycam)
|
|
{
|
|
vec3_t f1, l1, u1;
|
|
|
|
AngleVectors(other->client->spycam->s.angles, forward, left, up);
|
|
VectorScale(forward, other->client->spycam->move_origin[0],f1);
|
|
VectorScale(left, -other->client->spycam->move_origin[1],l1);
|
|
VectorScale(up, other->client->spycam->move_origin[2],u1);
|
|
VectorAdd(other->client->spycam->s.origin,f1,start);
|
|
VectorAdd(start,l1,start);
|
|
VectorAdd(start,u1,start);
|
|
}
|
|
else
|
|
{
|
|
AngleVectors(other->client->v_angle, forward, NULL, NULL);
|
|
VectorCopy(other->s.origin,start);
|
|
start[2] += other->viewheight;
|
|
}
|
|
VectorSubtract(self->s.origin,start,dir);
|
|
dist = VectorLength(dir);
|
|
VectorMA(start,dist,forward,end);
|
|
|
|
tr = gi.trace(start,vec3_origin,vec3_origin,end,other,MASK_OPAQUE);
|
|
|
|
// See if we're looking at origin, within bleft, tright
|
|
// FIXME: The following is more or less accurate if the
|
|
// bleft-tright box is roughly a cube. If it's considerably
|
|
// longer in one direction we'll get false misses.
|
|
|
|
if(end[0] < self->s.origin[0] + self->bleft[0])
|
|
return;
|
|
if(end[1] < self->s.origin[1] + self->bleft[1])
|
|
return;
|
|
if(end[2] < self->s.origin[2] + self->bleft[2])
|
|
return;
|
|
if(end[0] > self->s.origin[0] + self->tright[0])
|
|
return;
|
|
if(end[1] > self->s.origin[1] + self->tright[1])
|
|
return;
|
|
if(end[2] > self->s.origin[2] + self->tright[2])
|
|
return;
|
|
|
|
self->activator = other;
|
|
G_UseTargets (self, other);
|
|
}
|
|
|
|
if (self->wait > 0)
|
|
{
|
|
self->think = multi_wait;
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
else
|
|
{ // we can't just remove (self) here, because this is a touch function
|
|
// called while looping through area links...
|
|
self->touch = NULL;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = G_FreeEdict;
|
|
}
|
|
|
|
}
|
|
|
|
void trigger_look_enable (edict_t *self, edict_t *other, edict_t *activator);
|
|
void trigger_look_disable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->count--;
|
|
if(!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->use = trigger_look_enable;
|
|
gi.linkentity (self);
|
|
}
|
|
}
|
|
|
|
void trigger_look_enable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = trigger_look_disable;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void SP_trigger_look (edict_t *self)
|
|
{
|
|
if (self->sounds == 1)
|
|
self->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (self->sounds == 2)
|
|
self->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (self->sounds == 3)
|
|
self->noise_index = -1;
|
|
|
|
if (!self->wait)
|
|
self->wait = 0.2;
|
|
|
|
if (self->spawnflags & TRIGGER_START_OFF)
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->use = trigger_look_enable;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = trigger_look_disable;
|
|
}
|
|
|
|
self->movetype = MOVETYPE_NONE;
|
|
gi.setmodel (self, self->model);
|
|
self->svflags = SVF_NOCLIENT;
|
|
|
|
if (self->spawnflags & TRIGGER_CAMOWNER)
|
|
self->svflags |= SVF_TRIGGER_CAMOWNER;
|
|
|
|
if( (VectorLength(self->bleft) == 0) && (VectorLength(self->tright) == 0)) {
|
|
VectorSet(self->bleft,-16,-16,-16);
|
|
VectorSet(self->tright,16,16,16);
|
|
}
|
|
self->touch = trigger_look_touch;
|
|
}
|
|
|
|
void trigger_speaker_think (edict_t *self)
|
|
{
|
|
int i;
|
|
edict_t *touching;
|
|
edict_t *player;
|
|
|
|
touching = NULL;
|
|
for (i = 1; i <= maxclients->value && !touching; i++) {
|
|
player = &g_edicts[i];
|
|
if (!player->inuse) continue;
|
|
if (player->s.origin[0] < self->s.origin[0] + self->bleft[0]) continue;
|
|
if (player->s.origin[1] < self->s.origin[1] + self->bleft[1]) continue;
|
|
if (player->s.origin[2] < self->s.origin[2] + self->bleft[2]) continue;
|
|
if (player->s.origin[0] > self->s.origin[0] + self->tright[0]) continue;
|
|
if (player->s.origin[1] > self->s.origin[1] + self->tright[1]) continue;
|
|
if (player->s.origin[2] > self->s.origin[2] + self->tright[2]) continue;
|
|
touching = player;
|
|
}
|
|
if(touching)
|
|
gi.sound (touching, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void trigger_speaker_enable (edict_t *self, edict_t *other, edict_t *activator);
|
|
void trigger_speaker_disable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->use = trigger_speaker_enable;
|
|
self->think = NULL;
|
|
self->nextthink = 0;
|
|
}
|
|
|
|
void trigger_speaker_enable (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->use = trigger_speaker_disable;
|
|
self->think = trigger_speaker_think;
|
|
self->think(self);
|
|
}
|
|
|
|
void SP_trigger_speaker (edict_t *self)
|
|
{
|
|
char buffer[MAX_QPATH];
|
|
|
|
if(!st.noise)
|
|
{
|
|
gi.dprintf("trigger_speaker with no noise set at %s\n", vtos(self->s.origin));
|
|
return;
|
|
}
|
|
if (!strstr (st.noise, ".wav"))
|
|
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
|
|
else
|
|
strncpy (buffer, st.noise, sizeof(buffer));
|
|
self->noise_index = gi.soundindex (buffer);
|
|
|
|
if(self->spawnflags & 1) {
|
|
self->use = trigger_speaker_disable;
|
|
self->think = trigger_speaker_think;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
} else {
|
|
self->use = trigger_speaker_enable;
|
|
}
|
|
|
|
if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) ) {
|
|
VectorSet(self->bleft,-16,-16,-16);
|
|
VectorSet(self->tright,16, 16, 16);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//==============================================================================
|
|
// trigger_transition is a HL-like box that defines what entities will be
|
|
// moved from one map to another when a target_changelevel with the same
|
|
// targetname is fired. Brush models may NOT be moved.
|
|
//==============================================================================
|
|
void WriteEdict (FILE *f, edict_t *ent);
|
|
|
|
qboolean HasSpawnFunction(edict_t *ent)
|
|
{
|
|
spawn_t *s;
|
|
gitem_t *item;
|
|
int i;
|
|
|
|
if(!ent->classname)
|
|
return false;
|
|
|
|
// check item spawn functions
|
|
for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
|
|
{
|
|
if (!item->classname)
|
|
continue;
|
|
if (!strcmp(item->classname, ent->classname))
|
|
return true;
|
|
}
|
|
// check normal spawn functions
|
|
for (s=spawns ; s->name ; s++)
|
|
{
|
|
if (!strcmp(s->name, ent->classname))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void WriteTransitionEdict (FILE *f, edict_t *changelevel, edict_t *ent)
|
|
{
|
|
byte *temp;
|
|
edict_t e;
|
|
field_t *field;
|
|
void *p;
|
|
|
|
memcpy(&e,ent,sizeof(edict_t));
|
|
if (!Q_stricmp(e.classname,"target_laser") ||
|
|
!Q_stricmp(e.classname,"target_blaster") )
|
|
vectoangles(e.movedir,e.s.angles);
|
|
|
|
if (!Q_stricmp(e.classname,"target_speaker"))
|
|
e.spawnflags |= 8; // indicates that "message" contains noise
|
|
|
|
if (changelevel->s.angles[YAW])
|
|
{
|
|
vec3_t angles;
|
|
vec3_t forward, right, v;
|
|
vec3_t spawn_offset;
|
|
|
|
VectorSubtract(e.s.origin,changelevel->s.origin,spawn_offset);
|
|
angles[PITCH] = angles[ROLL] = 0.;
|
|
angles[YAW] = changelevel->s.angles[YAW];
|
|
AngleVectors(angles,forward,right,NULL);
|
|
VectorNegate(right,right);
|
|
VectorCopy(spawn_offset,v);
|
|
G_ProjectSource (vec3_origin, v, forward, right, spawn_offset);
|
|
VectorCopy(spawn_offset,e.s.origin);
|
|
VectorCopy(e.velocity,v);
|
|
G_ProjectSource (vec3_origin, v, forward, right, e.velocity);
|
|
e.s.angles[YAW] += angles[YAW];
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(e.s.origin,changelevel->s.origin,e.s.origin);
|
|
}
|
|
// wipe out all edict_t and function members, since
|
|
// they won't be valid in the next map and might otherwise
|
|
// cause.... umm... big crash
|
|
temp = (byte *)&e;
|
|
for (field=fields ; field->name ; field++)
|
|
{
|
|
if((field->type == F_EDICT) || (field->type == F_FUNCTION))
|
|
{
|
|
p = (void *)(temp + field->ofs);
|
|
*(edict_t **)p = NULL;
|
|
}
|
|
}
|
|
// Clean out a few more things
|
|
e.s.number = 0;
|
|
memset (&e.moveinfo, 0,sizeof(moveinfo_t));
|
|
memset (&e.area, 0, sizeof(e.area));
|
|
e.linkcount = 0;
|
|
e.nextthink = 0;
|
|
e.groundentity_linkcount = 0;
|
|
e.s.modelindex = 0;
|
|
e.s.modelindex2 = 0;
|
|
e.s.modelindex3 = 0;
|
|
e.s.modelindex4 = 0;
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
e.s.modelindex5 = 0;
|
|
e.s.modelindex6 = 0;
|
|
#endif
|
|
e.noise_index = 0;
|
|
// If the ent is a live bad guy monster, remove him from the total
|
|
// monster count. He'll be added back in in the new map.
|
|
if((e.svflags & SVF_MONSTER) && !(e.monsterinfo.aiflags & AI_GOOD_GUY))
|
|
{
|
|
if(e.health > 0)
|
|
level.total_monsters--;
|
|
else
|
|
e.max_health = -1;
|
|
}
|
|
// Enemy isn't preserved... let's try a new flag for
|
|
// single-player only that tells monster to find
|
|
// the player again at startup
|
|
if(!coop->value && !deathmatch->value)
|
|
{
|
|
if(ent->enemy == &g_edicts[1] && ent->health > 0)
|
|
e.monsterinfo.aiflags = AI_RESPAWN_FINDPLAYER;
|
|
}
|
|
if(e.classname &&
|
|
( !Q_stricmp(e.classname,"misc_actor") || strstr(e.classname,"monster_") ) &&
|
|
(e.svflags & SVF_GIB) )
|
|
//(e.health <= e.gib_health) )
|
|
e.classname = "gibhead";
|
|
WriteEdict(f,&e);
|
|
}
|
|
|
|
entlist_t DoNotMove[] = {
|
|
{"crane_reset"},
|
|
{"func_clock"},
|
|
{"func_timer"},
|
|
{"hint_path"},
|
|
{"info_player_coop"},
|
|
{"info_player_deathmatch"},
|
|
{"info_player_intermission"},
|
|
{"info_player_start"},
|
|
{"light"},
|
|
{"light_mine1"},
|
|
{"light_mine2"},
|
|
{"misc_strogg_ship"},
|
|
{"misc_viper"},
|
|
{"misc_viper_bomb"},
|
|
{"model_train"},
|
|
{"path_corner"},
|
|
{"path_track"},
|
|
{"point_combat"},
|
|
{"target_actor"},
|
|
{"target_changelevel"},
|
|
{"target_character"},
|
|
{"target_crosslevel_target"},
|
|
{"target_crosslevel_trigger"},
|
|
{"target_goal"},
|
|
{"target_help"},
|
|
{"target_lightramp"},
|
|
{"target_locator"},
|
|
{"target_lock"},
|
|
{"target_lock_clue"},
|
|
{"target_lock_code"},
|
|
{"target_lock_digit"},
|
|
{"target_rotation"},
|
|
{"target_secret"},
|
|
{"target_string"},
|
|
{"trigger_always"},
|
|
{"trigger_counter"},
|
|
{"trigger_elevator"},
|
|
{"trigger_key"},
|
|
{"trigger_relay"},
|
|
{"turret_driver"},
|
|
{NULL}};
|
|
|
|
void trans_ent_filename (char *filename, size_t filenameSize)
|
|
{
|
|
#if defined (_M_X64) || defined (_M_AMD64) || defined (__x86_64__)
|
|
SavegameDirRelativePath("save_x64/trans.ent", filename, filenameSize);
|
|
#else
|
|
SavegameDirRelativePath("save/trans.ent", filename, filenameSize);
|
|
#endif
|
|
}
|
|
|
|
int trigger_transition_ents (edict_t *changelevel, edict_t *self)
|
|
{
|
|
char t_file[MAX_QPATH];
|
|
int i, j;
|
|
int total=0;
|
|
qboolean nogo;
|
|
edict_t *ent;
|
|
entlist_t *p;
|
|
FILE *f;
|
|
|
|
trans_ent_filename(t_file, sizeof(t_file));
|
|
f = fopen(t_file,"wb");
|
|
if (!f)
|
|
{
|
|
gi.dprintf("Error opening %s for writing\n",t_file);
|
|
return 0;
|
|
}
|
|
// First scan entities for brush models that SHOULD change levels, e.g. func_tracktrain,
|
|
// which had better have a partner train in the next map... or we'll bitch loudly
|
|
for (i=game.maxclients+1; i<globals.num_edicts; i++)
|
|
{
|
|
ent = &g_edicts[i];
|
|
if (!ent->inuse) continue;
|
|
if (ent->solid != SOLID_BSP) continue;
|
|
if (ent->s.origin[0] > self->maxs[0]) continue;
|
|
if (ent->s.origin[1] > self->maxs[1]) continue;
|
|
if (ent->s.origin[2] > self->maxs[2]) continue;
|
|
if (ent->s.origin[0] < self->mins[0]) continue;
|
|
if (ent->s.origin[1] < self->mins[1]) continue;
|
|
if (ent->s.origin[2] < self->mins[2]) continue;
|
|
if (!Q_stricmp(ent->classname,"func_tracktrain") && !(ent->spawnflags & 8) && ent->targetname)
|
|
{
|
|
edict_t *e;
|
|
|
|
e = G_Spawn();
|
|
e->classname = gi.TagMalloc(17,TAG_LEVEL);
|
|
strcpy(e->classname,"info_train_start");
|
|
e->targetname = gi.TagMalloc(strlen(ent->targetname)+1,TAG_LEVEL);
|
|
strcpy(e->targetname,ent->targetname);
|
|
e->target = gi.TagMalloc(strlen(ent->target)+1,TAG_LEVEL);
|
|
strcpy(e->target,ent->target);
|
|
e->spawnflags = ent->spawnflags;
|
|
VectorCopy(ent->s.origin,e->s.origin);
|
|
VectorCopy(ent->s.angles,e->s.angles);
|
|
VectorCopy(ent->offset, e->offset);
|
|
VectorCopy(ent->bleft, e->bleft);
|
|
VectorCopy(ent->tright, e->tright);
|
|
e->sounds = ent->sounds;
|
|
e->viewheight = ent->viewheight;
|
|
e->speed = ent->moveinfo.speed;
|
|
// misuse/abuse a couple of entries to copy moveinfo stuff:
|
|
e->count = ent->moveinfo.state;
|
|
e->radius = ent->moveinfo.distance;
|
|
e->solid = SOLID_NOT;
|
|
e->svflags |= SVF_NOCLIENT;
|
|
if (ent->owner)
|
|
e->style = ent->owner - g_edicts;
|
|
else
|
|
e->style = 0;
|
|
gi.linkentity(e);
|
|
|
|
ent->owner = NULL;
|
|
ent->spawnflags |= 24; // SF_TRACKTRAIN_OTHERMAP | SF_TRACKTRAIN_DISABLED
|
|
VectorClear(ent->velocity);
|
|
VectorClear(ent->avelocity);
|
|
ent->moveinfo.state = ent->moveinfo.prevstate = 0; // STOP
|
|
gi.linkentity(ent);
|
|
}
|
|
}
|
|
|
|
for (i=game.maxclients+1; i<globals.num_edicts; i++)
|
|
{
|
|
ent = &g_edicts[i];
|
|
ent->id = 0;
|
|
if (!ent->inuse) continue;
|
|
// Pass up owned entities not owned by the player on this pass...
|
|
// get 'em next pass so we'll know whether owner is in our list
|
|
if (ent->owner && !ent->owner->client) continue;
|
|
if (ent->movewith) continue;
|
|
if (ent->s.origin[0] > self->maxs[0]) continue;
|
|
if (ent->s.origin[1] > self->maxs[1]) continue;
|
|
if (ent->s.origin[2] > self->maxs[2]) continue;
|
|
if (ent->s.origin[0] < self->mins[0]) continue;
|
|
if (ent->s.origin[1] < self->mins[1]) continue;
|
|
if (ent->s.origin[2] < self->mins[2]) continue;
|
|
if (ent->solid == SOLID_BSP) continue;
|
|
if ((ent->solid == SOLID_TRIGGER) && !FindItemByClassname(ent->classname)) continue;
|
|
// Do not under any circumstances move these entities:
|
|
for (p=DoNotMove, nogo=false; p->name && !nogo; p++)
|
|
if (!Q_stricmp(ent->classname,p->name))
|
|
nogo = true;
|
|
if (nogo) continue;
|
|
if (!HasSpawnFunction(ent)) continue;
|
|
total++;
|
|
ent->id = total;
|
|
if (ent->owner)
|
|
ent->owner_id = -(ent->owner - g_edicts);
|
|
else
|
|
ent->owner_id = 0;
|
|
WriteTransitionEdict(f,changelevel,ent);
|
|
gi.unlinkentity(ent);
|
|
ent->inuse = false;
|
|
}
|
|
// Repeat, ONLY for ents owned by non-players
|
|
for (i=game.maxclients+1; i<globals.num_edicts; i++)
|
|
{
|
|
ent = &g_edicts[i];
|
|
if (!ent->inuse) continue;
|
|
if (!ent->owner) continue;
|
|
if (ent->owner->client) continue;
|
|
if (ent->movewith) continue;
|
|
if (ent->solid == SOLID_BSP) continue;
|
|
if ((ent->solid == SOLID_TRIGGER) && !FindItemByClassname(ent->classname)) continue;
|
|
// Do not under any circumstances move these entities:
|
|
for (p=DoNotMove, nogo=false; p->name && !nogo; p++)
|
|
if (!Q_stricmp(ent->classname,p->name))
|
|
nogo = true;
|
|
if (nogo) continue;
|
|
if (!HasSpawnFunction(ent)) continue;
|
|
if (ent->s.origin[0] > self->maxs[0]) continue;
|
|
if (ent->s.origin[1] > self->maxs[1]) continue;
|
|
if (ent->s.origin[2] > self->maxs[2]) continue;
|
|
if (ent->s.origin[0] < self->mins[0]) continue;
|
|
if (ent->s.origin[1] < self->mins[1]) continue;
|
|
if (ent->s.origin[2] < self->mins[2]) continue;
|
|
ent->owner_id = 0;
|
|
for (j=game.maxclients+1; j<globals.num_edicts && !ent->owner_id; j++)
|
|
{
|
|
if (ent->owner == &g_edicts[j])
|
|
ent->owner_id = g_edicts[j].id;
|
|
}
|
|
if (!ent->owner_id) continue;
|
|
total++;
|
|
ent->id = total;
|
|
WriteTransitionEdict(f,changelevel,ent);
|
|
gi.unlinkentity(ent);
|
|
ent->inuse = false;
|
|
}
|
|
|
|
fflush(f);
|
|
fclose(f);
|
|
return total;
|
|
}
|
|
|
|
void SP_trigger_transition (edict_t *self)
|
|
{
|
|
if (!self->targetname)
|
|
{
|
|
gi.dprintf("trigger_transition w/o a targetname\n");
|
|
G_FreeEdict(self);
|
|
}
|
|
self->solid = SOLID_NOT;
|
|
self->movetype = MOVETYPE_NONE;
|
|
gi.setmodel (self, self->model);
|
|
self->svflags = SVF_NOCLIENT;
|
|
}
|
|
|
|
/*
|
|
============================
|
|
TRIGGER_DISGUISE
|
|
============================
|
|
*/
|
|
|
|
/*QUAKED trigger_disguise (.5 .5 .5) ? Toggle StartOn Remove
|
|
A 'Notarget' effect. Allows the player to sneak around without being noticed.
|
|
Any player who touches this when it's on will have their disguise flag set.
|
|
|
|
Toggle - trigger turns on and off when fired
|
|
StartOn - trigger is on at map load
|
|
Remove - trigger removes disguise
|
|
|
|
"count" - number of times it can be used, 0 = unlimited
|
|
*/
|
|
|
|
#define TRIGGER_DISGUISE_START_ON 2
|
|
#define TRIGGER_DISGUISE_REMOVE 4
|
|
|
|
void touch_trigger_disguise (edict_t *trigger, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (!other->client) // this only works for players
|
|
return;
|
|
|
|
trigger->count--;
|
|
if (trigger->count == 0)
|
|
{
|
|
trigger->nextthink = level.time + 0.1;
|
|
trigger->think = G_FreeEdict;
|
|
}
|
|
if (trigger->spawnflags & TRIGGER_DISGUISE_REMOVE)
|
|
other->flags &= ~FL_DISGUISED;
|
|
else other->flags |= FL_DISGUISED;
|
|
}
|
|
|
|
void use_trigger_disguise (edict_t *trigger, edict_t *other, edict_t *activator)
|
|
{
|
|
if (trigger->solid == SOLID_TRIGGER)
|
|
trigger->solid = SOLID_NOT;
|
|
else // SOLID_NOT
|
|
trigger->solid = SOLID_TRIGGER;
|
|
gi.linkentity(trigger);
|
|
}
|
|
|
|
void SP_trigger_disguise (edict_t *trigger)
|
|
{
|
|
trigger->movetype = MOVETYPE_NONE;
|
|
trigger->svflags |= SVF_NOCLIENT;
|
|
gi.setmodel (trigger, trigger->model);
|
|
|
|
if (trigger->spawnflags & TRIGGER_DISGUISE_START_ON)
|
|
trigger->solid = SOLID_TRIGGER;
|
|
else
|
|
trigger->solid = SOLID_NOT;
|
|
|
|
trigger->use = use_trigger_disguise;
|
|
trigger->touch = touch_trigger_disguise;
|
|
|
|
gi.linkentity(trigger);
|
|
}
|
|
|
|
|
|
|
|
/* ============================================================================
|
|
TRIGGER_SWITCH
|
|
|
|
Identical to trigger_multiple in just about every way, except that
|
|
it only turns entities ON if they're currently OFF (default operation) or
|
|
OFF if they're currently ON and TRIGGER_TARGET_OFF spawnflag is set.
|
|
|
|
==============================================================================*/
|
|
|
|
void trigger_switch_usetargets (edict_t *ent, edict_t *activator);
|
|
void trigger_switch_delay (edict_t *ent)
|
|
{
|
|
trigger_switch_usetargets (ent, ent->activator);
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
void trigger_switch_usetargets (edict_t *ent, edict_t *activator)
|
|
{
|
|
edict_t *t;
|
|
|
|
//
|
|
// check for a delay
|
|
//
|
|
if (ent->delay)
|
|
{
|
|
// create a temp object to fire at a later time
|
|
t = G_Spawn();
|
|
t->classname = "DelayedUse";
|
|
t->nextthink = level.time + ent->delay;
|
|
t->think = trigger_switch_delay;
|
|
t->activator = activator;
|
|
if (!activator)
|
|
gi.dprintf ("Delay with no activator\n");
|
|
t->message = ent->message;
|
|
t->target = ent->target;
|
|
t->killtarget = ent->killtarget;
|
|
t->noise_index = ent->noise_index;
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// print the message
|
|
//
|
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
|
{
|
|
// Lazarus - change so that noise_index < 0 means no sound
|
|
safe_centerprintf (activator, "%s", ent->message);
|
|
if (ent->noise_index > 0)
|
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
|
|
else if (ent->noise_index == 0)
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
//
|
|
// kill killtargets
|
|
//
|
|
if (ent->killtarget)
|
|
{
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
|
|
{
|
|
// Lazarus: remove LIVE killtargeted monsters from total_monsters
|
|
if((t->svflags & SVF_MONSTER) && (t->deadflag == DEAD_NO))
|
|
{
|
|
if(!t->dmgteam || strcmp(t->dmgteam,"player"))
|
|
if(!(t->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
level.total_monsters--;
|
|
}
|
|
// and decrement secret count if target_secret is removed
|
|
else if(t->class_id == ENTITY_TARGET_SECRET)
|
|
level.total_secrets--;
|
|
// same deal with target_goal, but also turn off CD music if applicable
|
|
else if(t->class_id == ENTITY_TARGET_GOAL)
|
|
{
|
|
level.total_goals--;
|
|
if (level.found_goals >= level.total_goals)
|
|
gi.configstring (CS_CDTRACK, "0");
|
|
}
|
|
G_FreeEdict (t);
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using killtargets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// fire targets
|
|
//
|
|
if (ent->target)
|
|
{
|
|
int on;
|
|
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), ent->target)))
|
|
{
|
|
if (t == ent)
|
|
{
|
|
gi.dprintf ("WARNING: Entity used itself.\n");
|
|
}
|
|
else if(t->use)
|
|
{
|
|
on = 0;
|
|
switch(t->class_id)
|
|
{
|
|
case ENTITY_FUNC_CONVEYOR:
|
|
case ENTITY_FUNC_FORCE_WALL:
|
|
case ENTITY_FUNC_WALL:
|
|
if(t->solid == SOLID_BSP)
|
|
on=1;
|
|
break;
|
|
case ENTITY_FUNC_PENDULUM:
|
|
case ENTITY_FUNC_TRAIN:
|
|
case ENTITY_MISC_STROGG_SHIP:
|
|
case ENTITY_MISC_VIPER:
|
|
case ENTITY_MODEL_TRAIN:
|
|
case ENTITY_TARGET_ATTRACTOR:
|
|
case ENTITY_TARGET_FOG:
|
|
case ENTITY_TARGET_FOUNTAIN:
|
|
case ENTITY_TARGET_LASER:
|
|
case ENTITY_TARGET_PRECIPITATION:
|
|
if(t->spawnflags & 1)
|
|
on=1;
|
|
break;
|
|
case ENTITY_FUNC_REFLECT:
|
|
if(!(t->spawnflags & 1))
|
|
on=1;
|
|
break;
|
|
case ENTITY_FUNC_ROTATING:
|
|
on = VectorCompare (t->avelocity, vec3_origin);
|
|
break;
|
|
case ENTITY_FUNC_TIMER:
|
|
if(t->nextthink)
|
|
on=1;
|
|
break;
|
|
case ENTITY_FUNC_TRACKTRAIN:
|
|
if(!(t->spawnflags & 128))
|
|
on=1;
|
|
break;
|
|
case ENTITY_MODEL_TURRET:
|
|
case ENTITY_TURRET_BREACH:
|
|
if(t->spawnflags & 16)
|
|
on=1;
|
|
break;
|
|
case ENTITY_TARGET_EFFECT:
|
|
if(t->spawnflags & 3)
|
|
{
|
|
if(t->spawnflags & 1)
|
|
on=1;
|
|
}
|
|
else
|
|
on = -1;
|
|
break;
|
|
case ENTITY_TARGET_SPEAKER:
|
|
if(t->spawnflags & 3)
|
|
{
|
|
if(t->s.sound)
|
|
on=1;
|
|
}
|
|
else
|
|
on=-1;
|
|
break;
|
|
default:
|
|
on=-1;
|
|
}
|
|
|
|
|
|
if(ent->spawnflags & TRIGGER_TARGET_OFF)
|
|
{
|
|
// Only use target if it is currently ON
|
|
if(on==1)
|
|
t->use (t, ent, activator);
|
|
}
|
|
else if(on==0)
|
|
{
|
|
// Only use target if it is currently OFF
|
|
t->use (t, ent, activator);
|
|
}
|
|
}
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using targets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void trigger_switch (edict_t *ent)
|
|
{
|
|
if (ent->nextthink)
|
|
return; // already been triggered
|
|
|
|
trigger_switch_usetargets (ent, ent->activator);
|
|
|
|
if (ent->wait > 0)
|
|
{
|
|
ent->think = multi_wait;
|
|
ent->nextthink = level.time + ent->wait;
|
|
}
|
|
else
|
|
{ // we can't just remove (self) here, because this is a touch function
|
|
// called while looping through area links...
|
|
ent->touch = NULL;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
ent->think = G_FreeEdict;
|
|
}
|
|
}
|
|
|
|
void touch_trigger_switch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if(other->client || (other->flags & FL_ROBOT))
|
|
{
|
|
if (self->spawnflags & TRIGGER_NOT_PLAYER)
|
|
return;
|
|
}
|
|
else if (other->svflags & SVF_MONSTER)
|
|
{
|
|
if (!(self->spawnflags & TRIGGER_MONSTER))
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
|
|
if( (self->spawnflags & TRIGGER_CAMOWNER) && (!other->client || !other->client->spycam))
|
|
return;
|
|
|
|
if (!VectorCompare(self->movedir, vec3_origin))
|
|
{
|
|
vec3_t forward;
|
|
|
|
AngleVectors(other->s.angles, forward, NULL, NULL);
|
|
if (_DotProduct(forward, self->movedir) < 0)
|
|
return;
|
|
}
|
|
|
|
self->activator = other;
|
|
trigger_switch (self);
|
|
}
|
|
|
|
void use_trigger_switch (edict_t *ent, edict_t *other, edict_t *activator)
|
|
{
|
|
ent->activator = activator;
|
|
trigger_switch (ent);
|
|
}
|
|
|
|
void SP_trigger_switch (edict_t *ent)
|
|
{
|
|
ent->class_id = ENTITY_TRIGGER_SWITCH;
|
|
if (ent->sounds == 1)
|
|
ent->noise_index = gi.soundindex ("misc/secret.wav");
|
|
else if (ent->sounds == 2)
|
|
ent->noise_index = gi.soundindex ("misc/talk.wav");
|
|
else if (ent->sounds == 3)
|
|
ent->noise_index = -1;
|
|
|
|
if (!ent->wait)
|
|
ent->wait = 0.2;
|
|
ent->touch = touch_trigger_switch;
|
|
ent->movetype = MOVETYPE_NONE;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
|
|
if (ent->spawnflags & TRIGGER_CAMOWNER)
|
|
ent->svflags |= SVF_TRIGGER_CAMOWNER;
|
|
|
|
if (ent->spawnflags & TRIGGER_START_OFF)
|
|
{
|
|
ent->solid = SOLID_NOT;
|
|
ent->use = trigger_enable;
|
|
}
|
|
else
|
|
{
|
|
ent->solid = SOLID_TRIGGER;
|
|
ent->use = use_trigger_switch;
|
|
}
|
|
|
|
if (!VectorCompare(ent->s.angles, vec3_origin))
|
|
G_SetMovedir (ent->s.angles, ent->movedir);
|
|
|
|
gi.setmodel (ent, ent->model);
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
// end DWH
|