mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
608814c830
Fixed Tactician Gunner's flechettes going thru player bbox at point blank. Made edict_t pointer arrays static in server, default Lazarus, and missionpack DLLs due to stack size concerns. Added contact grenade mode for special monster flag for gunners in default Lazarus and missionpack DLLs.
2489 lines
62 KiB
C
2489 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;
|
|
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *hurtme;
|
|
|
|
self->solid = SOLID_TRIGGER;
|
|
// Lazarus: 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;
|
|
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *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;
|
|
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *e, *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
|