thirtyflightsofloving/game/g_trigger.c
Knightmare66 45a81cf57b Fixed shootable func_door_secret not working in default Lazarus and missionpack DLLs.
Cleaned up strig handling functions in game DLLs.
Refactored ThrowGib(), ThrowHead() and ThrowDebris() functions in missionpack DLL.
2021-01-26 00:08:08 -05:00

2486 lines
62 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;
}
// Knightmare- same as above, but for bbox triggers
void InitTriggerBbox (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;
if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) )
{
VectorSet(self->bleft, -16, -16, -16);
VectorSet(self->tright, 16, 16, 16);
}
VectorCopy (self->bleft, self->mins);
VectorCopy (self->tright, self->maxs);
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)
{
ent->class_id = ENTITY_TRIGGER_MULTIPLE;
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);
ent->class_id = ENTITY_TRIGGER_ONCE;
}
/*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)
{
self->class_id = ENTITY_TRIGGER_RELAY;
// 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;
}
self->class_id = ENTITY_TRIGGER_KEY;
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->class_id = ENTITY_TRIGGER_COUNTER;
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)
{
ent->class_id = ENTITY_TRIGGER_ALWAYS;
// 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
#define PUSH_CUSTOM_SND 2
#define PUSH_START_OFF 4
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 & PUSH_CUSTOM_SND) {
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);
}
void trigger_push_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->solid == SOLID_TRIGGER)
self->solid = SOLID_NOT;
else // if (self->solid == SOLID_NOT)
self->solid = SOLID_TRIGGER;
gi.linkentity (self);
}
/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE CUSTOM_SOUND START_OFF
Pushes the player
"speed" defaults to 1000
"noise" (path/file.wav)
*/
void SP_trigger_push (edict_t *self)
{
self->class_id = ENTITY_TRIGGER_PUSH;
InitTrigger (self);
// DWH: Custom (or no) sound
if (self->spawnflags & PUSH_CUSTOM_SND)
{
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;
// Knightmare- switchable option
if (self->targetname)
{
self->use = trigger_push_use;
if (self->spawnflags & PUSH_START_OFF)
self->solid = SOLID_NOT;
}
else if (self->spawnflags & PUSH_START_OFF)
{
gi.dprintf ("trigger_push is START_OFF but has no targetname!\n");
self->spawnflags &= ~PUSH_START_OFF;
self->spawnflags |= PUSH_CUSTOM_SND;
if (st.noise)
self->noise_index = gi.soundindex(st.noise);
else
self->noise_index = 0;
}
// end Knightmare
gi.linkentity (self);
}
/*QUAKED trigger_push_bbox (.5 .5 .5) ? PUSH_ONCE CUSTOM_SOUND START_OFF
Pushes the player
"speed" defaults to 1000
"noise" (path/file.wav)
*/
void SP_trigger_push_bbox (edict_t *self)
{
self->class_id = ENTITY_TRIGGER_PUSH;
InitTriggerBbox (self);
// DWH: Custom (or no) sound
if (self->spawnflags & PUSH_CUSTOM_SND)
{
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;
// Knightmare- switchable option
if (self->targetname)
{
self->use = trigger_push_use;
if (self->spawnflags & PUSH_START_OFF)
self->solid = SOLID_NOT;
}
else if (self->spawnflags & PUSH_START_OFF)
{
gi.dprintf ("trigger_push is START_OFF but has no targetname!\n");
self->spawnflags &= ~PUSH_START_OFF;
self->spawnflags |= PUSH_CUSTOM_SND;
if (st.noise)
self->noise_index = gi.soundindex(st.noise);
else
self->noise_index = 0;
}
// end Knightmare
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)
{
self->class_id = ENTITY_TRIGGER_HURT;
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);
}
/*QUAKED trigger_hurt_bbox (.5 .5 .5) (-8 -8 -8) (8 8 8) START_OFF TOGGLE SILENT NO_PROTECTION SLOW
Any entity that touches this will be hurt.
Same as trigger_hurt, except it doesn't use a model.
It does dmg points of damage each server frame
SILENT supresses playing the sound
SLOW changes the damage rate to once per second
NO_PROTECTION *nothing* stops the damage
"dmg" default 5 (whole numbers only)
bleft Min b-box coords XYZ. Default = -16 -16 -16
tright Max b-box coords XYZ. Default = 16 16 16
*/
void SP_trigger_hurt_bbox (edict_t *self)
{
self->class_id = ENTITY_TRIGGER_HURT;
InitTriggerBbox (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
==============================================================================
*/
void trigger_gravity_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->solid == SOLID_TRIGGER)
self->solid = SOLID_NOT;
else // if (self->solid == SOLID_NOT)
self->solid = SOLID_TRIGGER;
gi.linkentity (self);
}
void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
other->gravity = self->gravity;
}
/*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
Changes the touching entites gravity to
the value of "gravity". 1.0 is standard
gravity for the level.
TOGGLE - Can be turned on and off
START_OFF - Starts turned off
*/
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->class_id = ENTITY_TRIGGER_GRAVITY;
self->gravity = atoi(st.gravity);
// Knightmare added
if (self->spawnflags & 1)
self->use = trigger_gravity_use;
if (self->spawnflags & 2) {
self->solid = SOLID_NOT;
self->use = trigger_gravity_use;
}
// end Knightmare
self->touch = trigger_gravity_touch;
}
/*QUAKED trigger_gravity_bbox (.5 .5 .5) ? TOGGLE START_OFF
Changes the touching entites gravity to
the value of "gravity". 1.0 is standard gravity for the level.
Same as trigger_gravity, except that it doesn't use a model.
TOGGLE - Can be turned on and off
START_OFF - Starts turned off
bleft Min b-box coords XYZ. Default = -16 -16 -16
tright Max b-box coords XYZ. Default = 16 16 16
*/
void SP_trigger_gravity_bbox (edict_t *self)
{
if (st.gravity == 0)
{
gi.dprintf("trigger_gravity_bbox without gravity set at %s\n", vtos(self->s.origin));
G_FreeEdict (self);
return;
}
InitTriggerBbox (self);
self->class_id = ENTITY_TRIGGER_GRAVITY;
self->gravity = atoi(st.gravity);
// Knightmare added
if (self->spawnflags & 1)
self->use = trigger_gravity_use;
if (self->spawnflags & 2) {
self->solid = SOLID_NOT;
self->use = trigger_gravity_use;
}
// end Knightmare
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)
{
self->class_id = ENTITY_TRIGGER_MONSTERJUMP;
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;
}
/*QUAKED trigger_monsterjump_bbox (.5 .5 .5) ?
Walking monsters that touch this will jump in the direction of the trigger's angle
Same as trigger_monsterjump, except that it doesn't use a model.
"speed" default to 200, the speed thrown forward
"height" default to 200, the speed thrown upwards
bleft Min b-box coords XYZ. Default = -16 -16 -16
tright Max b-box coords XYZ. Default = 16 16 16
*/
void SP_trigger_monsterjump_bbox (edict_t *self)
{
self->class_id = ENTITY_TRIGGER_MONSTERJUMP;
if (!self->speed)
self->speed = 200;
if (!st.height)
st.height = 200;
if (self->s.angles[YAW] == 0)
self->s.angles[YAW] = 360;
InitTriggerBbox (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)
{
ent->class_id = ENTITY_TREMOR_TRIGGER_MULTIPLE;
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)
{
self->class_id = ENTITY_TRIGGER_MASS;
// 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);
}
void SP_trigger_mass_bbox (edict_t *self)
{
self->class_id = ENTITY_TRIGGER_MASS;
// 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;
InitTriggerBbox (self);
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.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->class_id = ENTITY_TRIGGER_INSIDE;
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);
}
void SP_trigger_inside_bbox (edict_t *self)
{
vec3_t v;
VectorMA (self->mins, 0.5, self->size, v);
if (!self->target)
{
gi.dprintf("trigger_inside_bbox with no target at %s.\n",vtos(v));
G_FreeEdict(self);
return;
}
if (!self->pathtarget)
{
gi.dprintf("trigger_inside_bbox with no pathtarget at %s.\n",vtos(v));
G_FreeEdict(self);
return;
}
InitTriggerBbox (self);
self->class_id = ENTITY_TRIGGER_INSIDE;
if (!self->wait)
self->wait = 0.2;
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->class_id = ENTITY_TRIGGER_SCALES;
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);
}
void SP_trigger_scales_bbox (edict_t *self)
{
vec3_t v;
VectorMA (self->mins, 0.5, self->size, v);
if (!self->team)
{
gi.dprintf("trigger_scales_bbox with no team at %s.\n",vtos(v));
G_FreeEdict(self);
return;
}
InitTriggerBbox (self);
self->class_id = ENTITY_TRIGGER_SCALES;
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)
{
ent->class_id = ENTITY_TRIGGER_BBOX;
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)
{
InitTriggerBbox (self);
self->class_id = ENTITY_TRIGGER_LOOK;
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;
}
gi.setmodel (self, self->model);
// self->movetype = MOVETYPE_NONE;
// 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;
}
self->class_id = ENTITY_TRIGGER_SPEAKER;
if (!strstr (st.noise, ".wav"))
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
else
// strncpy (buffer, st.noise, sizeof(buffer));
Q_strncpyz (buffer, sizeof(buffer), st.noise);
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_OSPATH];
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;
size_t classSize, targetnameSize, targetSize;
e = G_Spawn();
classSize = 17;
e->classname = gi.TagMalloc(classSize, TAG_LEVEL);
Q_strncpyz (e->classname, classSize, "info_train_start");
targetnameSize = strlen(ent->targetname)+1;
e->targetname = gi.TagMalloc(targetnameSize, TAG_LEVEL);
Q_strncpyz (e->targetname, targetnameSize, ent->targetname);
targetSize = strlen(ent->target)+1;
e->target = gi.TagMalloc(targetSize, TAG_LEVEL);
Q_strncpyz (e->target, targetSize, 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->class_id = ENTITY_TRIGGER_TRANSITION;
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
gi.setmodel (self, self->model);
self->svflags = SVF_NOCLIENT;
}
void SP_trigger_transition_bbox (edict_t *self)
{
if (!self->targetname)
{
gi.dprintf("trigger_transition_bbox w/o a targetname\n");
G_FreeEdict(self);
}
self->class_id = ENTITY_TRIGGER_TRANSITION;
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
if ( (!VectorLength(self->bleft)) && (!VectorLength(self->tright)) )
{
VectorSet(self->bleft, -16, -16, -16);
VectorSet(self->tright, 16, 16, 16);
}
VectorCopy (self->bleft, self->mins);
VectorCopy (self->tright, self->maxs);
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->class_id = ENTITY_TRIGGER_DISGUISE;
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