Files
quakemapping/mod_ad/my_progs/triggers_states.qc
2019-12-30 22:24:44 +01:00

365 lines
13 KiB
Plaintext

/*======================================================================
TRIGGER STATE FUNCTIONS
======================================================================*/
float TRIG_NOTOUCH = 1; // Touch functionality is disabled
float TRIG_DEVMODE = 4; // Will only trigger in dev model
float TRIG_INVIEW = 8; // player has to see trigger
float TRIG_MODCHECK = 16; // Will remove this entity if THIS mod is active
float TRIG_MONSTERS = 32; // Can be touched/triggered by monsters
// float ENT_STARTOFF = 64; // Global spawnflags setting
// Added by functions, no map entity interaction
float TRIG_NODAMAGE = 16384; // Block trigger damage functionality
float TRIG_ALWAYTOUCH = 32768; // Always allow touch function to exist
//----------------------------------------------------------------------
void() trigger_bmodel_sounds =
{
// Used by lots of different triggers for standard sounds
if (self.sounds == 1) self.noise = "misc/secret.wav";
else if (self.sounds == 2) self.noise = SOUND_TALK;
else if (self.sounds == 3) self.noise = "misc/trigger1.wav";
else if (self.sounds == 4) self.noise = SOUND_EMPTY;
else if (self.sounds == 6) self.noise = "misc/secret3.wav";
// precache any sounds that have been defined
if (self.noise != "") precache_sound(self.noise);
};
//----------------------------------------------------------------------
// Special sight trigger, Is the player *looking* at this trigger
// This sight trigger cannot be disabled or turned off atm
//----------------------------------------------------------------------
void() trigger_bmodel_inview =
{
local entity client, tself;
local float play_dist, play_range, play_angle;
// Is the trigger blocked? (trigger_once)
if (self.attack_finished > time) return;
// Checkclient needs an proper entity origin, bmodels are '0 0 0'
// Create a temporary entity to switch around too for checkclient
if (!self.attachment) {
self.attachment = spawn();
self.attachment.owner = self;
self.oldorigin = bmodel_origin(self);
setorigin(self.attachment, self.oldorigin);
}
// Swap around self/attachment so checkclient can work properly
tself = self; self = self.attachment;
client = checkclient (); // Find a client in current PVS
self = tself;
// Found a player to check sight/distance for?
if (client.flags & FL_CLIENT) {
if (visxray(client, VEC_ORIGIN, VEC_ORIGIN, FALSE)) {
// Work out vector length between entity and player
play_dist = vlen(self.oldorigin - client.origin);
// Is there a distance check active? if not make sure it will pass
if (!self.t_length) play_range = play_dist + 1;
else play_range = self.t_length;
// The angle the player is facing towards the trigger (45 = forward)
play_angle = viewangle(self.oldorigin, client.origin, 45, TRUE, client.v_angle_y);
// Check distance is correct and facing angle focused
if (play_dist < play_range && play_angle > 30 && play_angle < 60) {
self.wait = -1;
self.bmodel_act = activator;
self.estate_fire();
return;
}
}
}
// Keep checking, 0.1 standard entity timing
self.nextthink = time + 0.1;
self.think = trigger_bmodel_inview;
};
//----------------------------------------------------------------------
// the trigger was just touched/killed/used
// self.bmodel_act should be set to the activator so it can be held
// through a delay so wait for the delay time before firing
//----------------------------------------------------------------------
void() trigger_bmodel_fire =
{
// Is the trigger blocked? (trigger_once)
if (self.attack_finished > time) return;
// Check for developer mode only triggers
if (self.spawnflags & TRIG_DEVMODE && developer == 0) {
// This feature only work for ONCE/MULTIPLE triggers
if (self.classtype == CT_TRIGONCE ||
self.classtype == CT_TRIGMULTI) return;
}
//----------------------------------------------------------------------
// The Shotgun and Axe upgrades can make maps too easy, allow for
// triggers to not fire if the key is TRUE and inventory empty
//----------------------------------------------------------------------
if ( self.bmodel_act.flags & FL_CLIENT ) {
if (self.upgrade_axe && !(self.bmodel_act.moditems & IT_UPGRADE_AXE)) return;
if (self.upgrade_ssg && !(self.bmodel_act.moditems & IT_UPGRADE_SSG) ) return;
if (self.upgrade_lg && !(self.bmodel_act.moditems & IT_UPGRADE_LG) ) return;
}
// Trigger secrets only work for players
if (self.classtype == CT_TRIGSECRET) {
// If trigger_secret has NOTOUCH then check activator
if (!(self.bmodel_act.flags & FL_CLIENT)) return;
// The trigger can be reset, so double check
if (self.count > 0) {
found_secrets = found_secrets + self.count;
WriteByte (MSG_ALL, SVC_FOUNDSECRET);
self.count = 0; // Secrets only work once
}
}
// Play the sound ON the trigger, NOT the activator
if (self.noise != "") sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
//----------------------------------------------------------------------
// One of the downside to trigger_multi and trigger_once is that they
// reset the activator global variable each time they are used.
// This can causes problems with triggers that require client references
// for centerprint/sound cues (like trigger_secret)
// The way to get around this problem is to use trigger_relay as this does
// not change the activator variable
//
activator = self.bmodel_act;
SUB_UseTargets();
// Is the trigger repeatable?
if (self.wait > 0) {
self.attack_finished = time + self.wait;
self.nextthink = self.attack_finished;
self.think = self.estate_on;
}
else {
// TRIGGER_ONCE functionality
self.attack_finished = LARGE_TIMER;
self.estate_off();
}
};
//----------------------------------------------------------------------
void() trigger_bmodel_use =
{
// There is no client test here because this can come from
// multiple entity types, the activator is passed on
if (self.estate & ESTATE_BLOCK) return;
self.bmodel_act = activator;
self.estate_fire();
};
//----------------------------------------------------------------------
void() trigger_bmodel_touch =
{
// This is a client touch event, triggers that require
// other entity types need to have their own touch event
if (self.estate & ESTATE_BLOCK) return;
if ( !(self.spawnflags & TRIG_MONSTERS) && !(other.flags & FL_CLIENT) ) return;
// Did the trigger have an angle key setup?
if (CheckZeroVector(self.movedir) == FALSE) {
makevectors (other.angles);
if (v_forward * self.movedir < 0) return;
}
self.bmodel_act = other;
self.estate_fire();
};
//----------------------------------------------------------------------
void() trigger_bmodel_anytouch =
{
// This will allow anything to touch it
if (self.estate & ESTATE_BLOCK) return;
self.bmodel_act = other;
self.estate_fire();
};
//----------------------------------------------------------------------
void() trigger_bmodel_killed =
{
// The proto button setup of triggers using health
// Always reset health regardless if blocked or not
self.health = self.max_health;
if (self.estate & ESTATE_BLOCK) return;
self.takedamage = DAMAGE_NO;
self.bmodel_act = damage_attacker;
self.estate_fire();
};
//----------------------------------------------------------------------
// Setup touch/damage/bounding box functionality
//----------------------------------------------------------------------
void() trigger_bmodel_restore =
{
// Damage functionality
if (self.max_health > 0 && !(self.spawnflags & TRIG_NODAMAGE) ) {
self.health = self.max_health;
self.takedamage = DAMAGE_YES;
self.solid = SOLID_BBOX;
}
// The extra option (ALWAYSTOUCH) was added because some triggers
// have re-used spawnflag 1 for other purposes and they still need
// touch founctionality
else if ( !(self.spawnflags & TRIG_NOTOUCH) || self.spawnflags & TRIG_ALWAYTOUCH)
self.solid = SOLID_TRIGGER;
// Turn off touch functionality
else self.solid = SOLID_NOT;
// Restore bounding box (dev testing visual thing)
setsize (self, self.bbmins, self.bbmaxs);
// Check for spawning conditions (nightmare, coop)
// Needs to exist after entity has been added to work for BSPorigin
if (check_nightmare() == TRUE) return;
if (check_coop() == TRUE) return;
};
//----------------------------------------------------------------------
void() trigger_bmodel_delay =
{
if (self.estate == ESTATE_DISABLE) return;
// Remove the START OFF functionality
self.spawnflags = self.spawnflags - (self.spawnflags & ENT_STARTOFF);
// Reset USE and trigger_once conditions
self.estate_use = trigger_bmodel_use;
self.attack_finished = 0;
self.estate = ESTATE_ON;
// Check if the player needs to be inview before triggering?
// It would be weird to have this on multiple/secret triggers
if (self.classtype == CT_TRIGONCE && self.spawnflags & TRIG_INVIEW) {
// Restore bounding box (dev testing visual thing)
setsize (self, self.bbmins, self.bbmaxs);
// Inview triggers are not touchable and cannot be damaged
self.spawnflags = self.spawnflags | TRIG_NOTOUCH | TRIG_NODAMAGE;
self.estate_use = trigger_bmodel_inview;
self.estate_use();
return;
}
//----------------------------------------------------------------------
// I would have never of guessed this was used throughout the original
// ID maps and there are countless cases of trigger_multiple entities
// being used like buttons that can be damaged but not touched.
// I get the impression this was a prototype stage of development where
// everyone was waiting for func_button functionality to come online.
//
if (self.health && !(self.spawnflags & TRIG_NODAMAGE)) {
self.max_health = self.health; // save health for later
self.th_die = trigger_bmodel_killed; // damage trigger
}
else {
// Setup touch functionality
if ( !(self.spawnflags & TRIG_NOTOUCH) || self.spawnflags & TRIG_ALWAYTOUCH) {
if (!self.touch) self.touch = trigger_bmodel_touch;
}
}
// Setup touch/damage/bounding box functionality
trigger_bmodel_restore();
};
//----------------------------------------------------------------------
void() trigger_bmodel_on =
{
self.estate = ESTATE_ON;
self.movetype = MOVETYPE_NONE;
// Check for delayed/trigger_once functionality?
if (self.spawnflags & ENT_STARTOFF || self.attack_finished > time)
self.solid = SOLID_NOT;
else {
// Setup touch/damage/bounding box functionality
trigger_bmodel_restore();
}
};
//----------------------------------------------------------------------
void() trigger_bmodel_off =
{
self.estate = ESTATE_OFF;
self.movetype = MOVETYPE_NONE;
self.solid = SOLID_NOT;
setsize(self, VEC_ORIGIN, VEC_ORIGIN);
self.estate_disable();
};
//----------------------------------------------------------------------
void() trigger_bmodel_reset =
{
// Reset trigger_once conditions
self.attack_finished = 0;
self.estate_on();
};
//----------------------------------------------------------------------
void() trigger_bmodel_disable =
{
// Shutdown trigger completely
self.takedamage = DAMAGE_NO;
self.think = SUB_Null;
};
//----------------------------------------------------------------------
void() trigger_bmodel_default =
{
self.estate_on = trigger_bmodel_on;
self.estate_off = trigger_bmodel_off;
if (!self.estate_fire) self.estate_fire = trigger_bmodel_fire;
self.estate_disable = trigger_bmodel_disable;
self.estate_reset = trigger_bmodel_reset;
self.estate = ESTATE_ON;
};
//----------------------------------------------------------------------
void() trigger_bmodel_setup =
{
trigger_bmodel_default();
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trigger_bmodel_delay;
if (self.spawnflags & ENT_STARTOFF) {
self.estate_use = trigger_bmodel_delay;
self.estate_on = trigger_bmodel_delay;
self.estate_off();
}
// The original functionality of the trigger is to pass through
else self.estate_use();
};
//----------------------------------------------------------------------
// Re-direction for map hacks (not used normally)
//----------------------------------------------------------------------
void() multi_trigger = {
trigger_bmodel_default();
trigger_bmodel_fire();
};
void() multi_killed = {
trigger_bmodel_default();
trigger_bmodel_killed();
};
void() multi_use = {
trigger_bmodel_default();
trigger_bmodel_use();
};
void() multi_touch = {
trigger_bmodel_default();
trigger_bmodel_touch();
};
void() multi_wait = {
if (self.max_health) {
self.health = self.max_health;
self.takedamage = DAMAGE_YES;
self.solid = SOLID_BBOX;
}
};