//----------------------------------------------------------------------------- // // $Logfile:: /Quake 2 Engine/Sin/code/game/trigger.cpp $ // $Revision:: 126 $ // $Author:: Jimdose $ // $Date:: 12/09/98 8:06p $ // // Copyright (C) 1997 by Ritual Entertainment, Inc. // All rights reserved. // // This source is may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // $Log:: /Quake 2 Engine/Sin/code/game/trigger.cpp $ // // 126 12/09/98 8:06p Jimdose // added triggerbox // // 125 11/18/98 3:37a Jimdose // added hack to get rid of tirggers marked with "secret" message // // 124 11/17/98 2:51a Markd // put in special case for oilrig trigger_once we don't want // // 123 11/16/98 4:37a Markd // put in better ToggleSound code // // 122 11/15/98 3:50a Jimdose // changed TriggerStuff so that if the activator is the world, it only runs the // thread if the command came from script. Grrrr... // // 121 11/13/98 6:02p Jimdose // TriggerStuff was only calling the thread if other was Sentient. Changed it // so that it calls it for all entities except when the world touches // something. // // 120 11/12/98 9:01p Markd // pre-cached all "NOISE"'s // // 119 11/07/98 10:15p Markd // put in forcemusic support // // 118 10/27/98 6:20a Aldie // Bulletproof cameras // // 117 10/26/98 3:51a Aldie // Made trigger_hurt ignore armor and skill // // 116 10/25/98 9:11p Markd // put in null other protection for activate targets // // 115 10/25/98 5:49p Markd // Fixed random speakers, multi-triggering // // 114 10/24/98 6:05p Jimdose // added changethread to TriggerChangeLevel // made waitForPlayer work in change level threads // // 113 10/24/98 2:17p Markd // Put in success music when hit trigger_exit // // 112 10/24/98 12:42a Markd // changed origins to worldorigins where appropriate // // 111 10/22/98 5:04p Jimdose // TriggerStuff now checks trigger_time at the begining of the function // // 110 10/19/98 11:52p Aldie // Force set the touchfield to SOLID_TRIGGER // // 109 10/19/98 12:07a Jimdose // made all code use fast checks for inheritance (no text lookups when // possible) // isSubclassOf no longer requires ::_classinfo() // // 108 10/17/98 8:13p Jimdose // Changed Damage to DamgeEvent in DamageThreshold // Changed ownernum to EntityPtr owner in TouchField // // 107 10/16/98 7:19p Markd // made trigger_changelevel default to LevelComplete as its default thread name // // 106 10/15/98 10:08p Markd // Made all sounds synched by default // // 105 10/15/98 3:37p Markd // Moved STartThread code after triggers again, made sure trigger_changelevel // executes script before changing levels // // 104 10/14/98 7:53p Markd // Added trigger_exit // // 103 10/14/98 12:33p Markd // Made TriggerOnce be able to turn off the touch ability // // 102 10/13/98 11:13p Markd // Put in trigger_mutate // // 101 10/13/98 9:44p Jimdose // TriggerChangeLevel no longer errors out when the map hasn't been set and // loading savegames // // 100 10/12/98 12:20p Markd // Replaced StartThreads with ExecuteThreads // // 99 10/10/98 1:29a Jimdose // TriggerSecret no longer increments the secret count during savegames // // 98 10/08/98 7:40p Jimdose // changed noexit to a dmflag // // 97 10/05/98 6:42p Markd // Made Trigger Speakers get sent over at least once // // 96 10/01/98 11:47a Markd // Added Toggle flag to trigger_speakers // // 95 9/30/98 4:55p Markd // bullet-proofed camera choosing code so that non-camera would be properly // detected // // 94 9/30/98 3:42p Markd // put in bulletproofing for start thread functions // // 93 9/29/98 5:59p Markd // Added dialog_needed stuff for keys on triggers // // 92 9/21/98 4:21p Markd // Put in archive functions and rewrote all archive routines // // 91 9/20/98 7:11p Aldie // Added flags to particles // // 90 9/20/98 3:13p Markd // made channel voice the default for trigger_speakers // // 89 9/17/98 5:52p Jimdose // Made total_secrets and found_secrets available to script // Made trigger_secret's thread default to specific script. // // 88 9/15/98 7:01p Markd // Added setdamage ability to trigger_hurt's // // 87 9/15/98 6:45p Aldie // Removed some sound references // // 86 9/14/98 6:16p Markd // Added documentation to trigger_once // // 85 9/14/98 3:10p Markd // fixed trigger_speaker toggling // // 84 9/11/98 5:45p Aldie // Updated some flags for beam // // 83 9/09/98 3:56p Aldie // New lightning effect // // 82 8/30/98 7:01p Markd // Fixed touchable triggers for trigger_particle and trigger_randomparticle // // 81 8/29/98 9:46p Jimdose // Changed BeginIntermission to G_BeginIntermission // // 80 8/29/98 5:27p Markd // added specialfx, replaced misc with specialfx where appropriate // // 79 8/27/98 6:47p Markd // Added NOSOUND capability to trigger_particles // // 78 8/24/98 4:55p Markd // Added G_CalculateImpulse to TriggerPush // // 77 8/24/98 11:32a Markd // Added Start method to threads, repladed all ProcessEvent( // EV_ScriptThread_execute) with thread->Start( -1 ) // // 76 8/19/98 2:28p Aldie // Added trigger_pushany which is basically a trigger_push with angles support // // 75 8/18/98 11:53p Jimdose // Changed TriggerStuff so that it only accepts world triggers on EV_Activate // // 74 8/18/98 11:08p Markd // Added new Alias System // // 73 8/17/98 6:19p Markd // Added trigger_camerause // // 72 8/15/98 10:12p Jimdose // Made trigger_use solid only when visible // // 71 8/15/98 5:36p Jimdose // Trigger thread was starting the thread in TriggerStuff and in Trigger // Effect, effectively, calling it twice // // 70 8/14/98 8:18p Markd // Fixed silly str thing to get rid of weird messages. // // 69 8/10/98 7:26p Markd // fixed a thread issue with triggers being triggered by world // // 68 8/05/98 2:52p Markd // Added thread support to triggers // // 67 7/23/98 6:17p Aldie // Updated damage system and fixed some damage related bugs. Also put tracers // back to the way they were, and added gib event to funcscriptmodels // // 66 7/21/98 4:23p Aldie // Make icons work for locks // // 65 7/21/98 1:10p Aldie // Added meansofdeath to obituaries // // 64 7/18/98 3:56p Markd // Added SetKey command // // 63 7/18/98 1:57p Aldie // Changed functionality of trigger_push. // // 62 7/16/98 5:45p Markd // Added particlestyle to Trigger_randomparticles // // 61 7/15/98 9:58p Markd // changed syntax of TempDLight // // 60 7/13/98 5:56p Markd // Added TriggerDamageTargetsFixed and TriggerThread // // 59 7/10/98 4:17p Markd // Made trigger_damagetargets not walkthroughable // // 58 7/10/98 4:10p Markd // made damage_threshold have accumulative flag // // 57 7/09/98 12:07a Jimdose // killtargeting posts a remove instead of processing it. // // 56 6/27/98 2:16p Aldie // Updated to new centerprintf method // // 55 6/25/98 8:47p Markd // Added keyed items for Triggers, Rewrote Item class, rewrote every pickup // method // // 54 6/18/98 8:48p Jimdose // Added better event error handling // Added source info to events // // 53 6/17/98 7:24p Markd // Updated documentation on Trigger_speakers // // 52 6/10/98 2:38p Aldie // Updated the trigger_push. // // 51 6/10/98 2:10p Aldie // Updated damage function. // // 50 6/09/98 6:42p Markd // made ambients default to ATTN_IDLE instead of ATTN_STATIC // // 49 6/08/98 2:36p Markd // re-did some trigger_speaker stuff to support attenuation on ambients // // 48 5/25/98 5:40p Markd // Added TriggerParticles and TriggerRandomParticles // // 47 5/24/98 8:46p Jimdose // Made a lot of functions more str-friendly. // Got rid of a lot of char * based strings // Cleaned up get spawn arg functions and sound functions // sound functions now use consistant syntax // // 46 5/24/98 5:25p Jimdose // Changed char *'s to const char *'s // // 45 5/22/98 9:39p Jimdose // Changed check for "Monster" to "Actor" // // 44 5/20/98 1:51p Markd // Forgot to get rid of showmodel for TriggerDamageTargets-SOLID // // 43 5/20/98 1:33p Markd // Changed Trigger_damagetargets a bit // // 42 5/20/98 11:11a Markd // removed char * dependency // // 41 5/18/98 6:13p Jimdose // Added health to trigger // // 40 5/16/98 5:00p Markd // Added Trigger_DamageTargets // // 39 5/08/98 7:54p Markd // Fixed default trigger sound // // 38 5/06/98 11:01a Markd // Added warning on play_sound_triggered // // 37 5/02/98 12:01a Jimdose // Trigger Hurt now defaults to hurt monsters // // 36 5/01/98 6:23p Jimdose // Trigger hurt no longer hurts dead people // // 35 5/01/98 5:07p Jimdose // TriggerStuff now responds to messages from world regardless of its // spawnflags // // 34 4/30/98 12:18p Markd // capitalized some trigger_speaker stuff // // 33 4/30/98 12:14p Markd // Added SINED clarifications // // 32 4/30/98 12:00p Markd // Fixed trigger_speaker, it now spawns properly // // 31 4/29/98 10:45p Markd // Added TriggerSpeaker and TriggerRandomSpeaker and TriggerPlaySound was // modified // // 30 4/20/98 1:40p Jimdose // Added respondto in TouchField::Setup // // 29 4/18/98 2:36p Jimdose // Removed tesselating from DamageThreshold // Changed DamageThreshold to behave more like a Trigger (using // EV_Trigger_Effect) // // 28 4/14/98 7:29p Markd // Increased size of tesselation for damage_threshold // // 27 4/14/98 6:56p Markd // Added thickness to tesselation // // 26 4/10/98 1:24a Markd // Made DamageThreshold behave with new FL_TESSELATE flags // // 25 4/05/98 10:43p Markd // Moved Tesselate to Entity // // 24 4/05/98 6:16p Markd // Changed TE_TESSELATE a bit // // 23 4/04/98 6:10p Jimdose // Added TriggerHurt // Added EV_Trigger_Effect so that subclasses of Trigger can easily change the // effect of the trigger without loses its default behaviour // Added respondto variable that determines wether a trigger responds to // players, monsters, or projectiles // // 22 3/29/98 9:40p Jimdose // Changed killed and pain to events // // 21 3/27/98 5:38p Jimdose // Made TriggerChangeLevel work // // 20 3/24/98 4:55p Jimdose // Changed usage of GetToken to GetString so that script variables can be used // // 19 3/23/98 1:31p Jimdose // Revamped event and command system // // 18 3/11/98 2:22p Jimdose // Added support for area portals in damage threshold. // // 17 3/02/98 8:49p Jimdose // Changed the classid parameter of CLASS_DECLARATION to a quoted string so // that you could have a NULL classid. // // 16 2/17/98 6:59p Jimdose // no longer pass script into interpretCommand // // 15 2/16/98 2:11p Jimdose // Fixed bug in ActivateTargets where entnum was being overwritten // // 14 2/06/98 5:41p Jimdose // Fixed Sined header for trigger_multiple. // Removed use of touch function in DamageThreshold. // // 13 2/03/98 10:49a Jimdose // Updated to work with Quake 2 engine // Changed from think functions to events // // 11 12/06/97 4:51p Markd // Added intepretCommand for Trigger and TriggerMonsterJump // // 10 11/10/97 8:36p Jimdose // Removed getMapTitle from World, so had to change trigger_changelevel // // 9 10/27/97 3:30p Jimdose // Removed dependency on quakedef.h // // 8 10/08/97 6:03p Jimdose // Began vehicle support. // // 7 10/06/97 6:31p Markd // changed triangulate size // // 6 10/06/97 4:01p Markd // added multiple parameters to the triangulate function. Added lightmap // lookups at each vert. Added subtri filter that would cull some the created // triangles // // 5 10/05/97 6:09p Markd // Changed tesselation factor back // // 4 10/05/97 5:53p Markd // Changed tesselation factor for triangulate // // 3 10/04/97 5:22p Markd // Added Triangulate calls // // 2 9/26/97 5:23p Jimdose // Added standard Ritual headers // // DESCRIPTION: // Environment based triggers. // #include "g_local.h" #include "entity.h" #include "trigger.h" #include "scriptmaster.h" #include "areaportal.h" #include "worldspawn.h" #include "misc.h" #include "specialfx.h" #include "sentient.h" #include "item.h" #include "player.h" #include "camera.h" #include "actor.h" #include "g_utils.h" Event EV_Trigger_ActivateTargets( "activatetrigger" ); Event EV_Trigger_SetWait( "wait" ); Event EV_Trigger_SetDelay( "delay" ); Event EV_Trigger_SetCount( "cnt" ); Event EV_Trigger_SetMessage( "message" ); Event EV_Trigger_SetNoise( "noise" ); Event EV_Trigger_Effect( "triggereffect" ); Event EV_Trigger_StartThread( "triggerthread" ); Event EV_Trigger_SetKey( "key" ); #define MULTI_ACTIVATE 1 #define INVISIBLE 2 #define VISIBLE 1 #define TRIGGER_PLAYERS 4 #define TRIGGER_MONSTERS 8 #define TRIGGER_PROJECTILES 16 /*****************************************************************************/ /*SINED trigger_multiple (.5 .5 .5) ? x x NOT_PLAYERS MONSTERS PROJECTILES Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. If "delay" is set, the trigger waits some time after activating before firing. "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. "wait" : Seconds between triggerings. (.2 default) "cnt" how many times it can be triggered (infinite default) If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) set "message" to text string /*****************************************************************************/ CLASS_DECLARATION( Entity, Trigger, "trigger_multiple" ); ResponseDef Trigger::Responses[] = { { &EV_Trigger_SetWait, ( Response )Trigger::EventSetWait }, { &EV_Trigger_SetDelay, ( Response )Trigger::EventSetDelay }, { &EV_Trigger_SetCount, ( Response )Trigger::EventSetCount }, { &EV_Trigger_SetMessage, ( Response )Trigger::EventSetMessage }, { &EV_Trigger_SetNoise, ( Response )Trigger::EventSetNoise }, { &EV_Touch, ( Response )Trigger::TriggerStuff }, { &EV_Killed, ( Response )Trigger::TriggerStuff }, { &EV_Activate, ( Response )Trigger::TriggerStuff }, { &EV_Trigger_ActivateTargets, ( Response )Trigger::ActivateTargets }, { &EV_Trigger_SetKey, ( Response )Trigger::EventSetKey }, { &EV_Trigger_StartThread, ( Response )Trigger::StartThread }, { NULL, NULL } }; Trigger::Trigger() { triggerActivated = false; activator = NULL; trigger_time = (float)0; setMoveType( MOVETYPE_NONE ); setSolidType( SOLID_TRIGGER ); delay = G_GetFloatArg( "delay" ); wait = G_GetFloatArg( "wait", 0.2 ); health = G_GetFloatArg( "health", 0 ); max_health = health; if ( health ) { takedamage = DAMAGE_YES; setSolidType( SOLID_BBOX ); } trigger_time = (float)0; count = G_GetFloatArg( "cnt", -1 ); SetNoise( G_GetSpawnArg( "noise", "environment/switch/switch2.wav" ) ); SetMessage( G_GetSpawnArg( "message" ) ); SetKillTarget( G_GetSpawnArg( "killtarget" ) ); hideModel(); respondto = spawnflags ^ TRIGGER_PLAYERS; key = G_GetStringArg( "key" ); thread = G_GetStringArg( "thread" ); // // gross HACK for non-standard secret triggers // if ( message == "secret" ) { TriggerSecret *trig; // don't ever trigger trigger_time = -1; // clear out the message G_SetSpawnArg( "message", "" ); // spawn the replacement trigger trig = new TriggerSecret; // delete ourself on the way out. PostEvent( EV_Remove, 0 ); } } Trigger::~Trigger() { } void Trigger::StartThread ( Event *ev ) { if ( thread.length() ) { if ( !ExecuteThread( thread ) ) { warning( "StartThread", "Null game script" ); } } } void Trigger::TriggerStuff ( Event *ev ) { Entity *other; Event *event; qboolean respond; // Don't bother with testing anything if we can't trigger yet if ( ( level.time < trigger_time ) || ( trigger_time == -1 ) ) { return; } health = max_health; if ( health && ( ( int )*ev != ( int )EV_Killed ) && ( ( int )*ev != ( int )EV_Activate ) ) { // if health is set, we only respond to killed and activate messages return; } other = ev->GetEntity( 1 ); assert( other != this ); respond = ( ( ( respondto & TRIGGER_PLAYERS ) && other->isClient() ) || ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) || ( ( respondto & TRIGGER_PROJECTILES ) && other->isSubclassOf( Projectile ) ) ); // Always respond to activate messages from the world since they're probably from // the "trigger" command if ( !respond && !( ( other == world ) && ( ( int )*ev == ( int )EV_Activate ) ) ) { return; } if ( key.length() ) { if ( !other->isSubclassOf( Sentient ) || !( ( (Sentient *)other )->HasItem( key.c_str() ) ) ) { Item *item; ClassDef *cls; str dialog; cls = getClass( key.c_str() ); if ( !cls ) { gi.dprintf( "No item named '%s'\n", key.c_str() ); return; } item = ( Item * )cls->newInstance(); item->CancelEventsOfType( EV_Item_DropToFloor ); item->CancelEventsOfType( EV_Remove ); item->ProcessPendingEvents(); dialog = item->GetDialogNeeded(); if ( dialog.length() > 1 ) { if ( !ExecuteThread( dialog ) ) { warning( "TriggerStuff", "Null game script" ); } } else { gi.centerprintf ( other->edict, "jcx yv 20 string \"You need this item:\" jcx yv -20 icon %d", item->GetIconIndex() ); } delete item; return; } } trigger_time = level.time + wait; event = new Event( EV_Trigger_Effect ); event->AddEntity( other ); PostEvent( event, delay ); event = new Event( EV_Trigger_ActivateTargets ); event->AddEntity( other ); PostEvent( event, delay ); if ( thread.length() ) { // don't trigger the thread if we were triggered by the world touching us if ( ( other != world ) || ( ev->GetSource() != EV_FROM_CODE ) ) { event = new Event( EV_Trigger_StartThread ); PostEvent( event, delay ); } } if ( count > -1 ) { count--; if ( count < 1 ) { // // Don't allow it to trigger anymore // trigger_time = -1; // // Make sure we wait until we're done triggering things before removing // PostEvent( EV_Remove, delay + 0.1 ); } } } // //============================== // ActivateTargets // // "other" should be set to the entity that initiated the firing. // // Centerprints any message to the activator. // // Removes all entities with a targetname that match killtarget, // so some events can remove other triggers. // // Search in targetname of all entities that match target // and send EVENT_ACTIVATE to there event handler //============================== // void Trigger::ActivateTargets ( Event *ev ) { Entity *other; Entity *ent; Event *event; const char *name; int num; other = ev->GetEntity( 1 ); if ( !other ) other = world; if ( triggerActivated ) { // // Entity triggered itself. Prevent an infinite loop // ev->Error( "Entity targeting itself--Targetname '%s'", TargetName() ); return; } triggerActivated = true; activator = other; // // print the message // if ( message.length() && other && other->isClient() ) { gi.centerprintf( other->edict, "jcx jcy string \"%s\"", message.c_str() ); if ( Noise().length() ) { other->sound( noise, 1, CHAN_VOICE, ATTN_NORM ); } } // // kill the killtargets // name = KillTarget(); if ( name && strcmp( name, "" ) ) { num = 0; do { num = G_FindTarget( num, name ); if ( !num ) { break; } ent = G_GetEntity( num ); ent->PostEvent( EV_Remove, 0 ); } while ( 1 ); } // // fire targets // name = Target(); if ( name && strcmp( name, "" ) ) { num = 0; do { num = G_FindTarget( num, name ); if ( !num ) { break; } ent = G_GetEntity( num ); event = new Event( EV_Activate ); event->AddEntity( other ); ent->ProcessEvent( event ); } while ( 1 ); } triggerActivated = false; } void Trigger::EventSetWait ( Event *ev ) { wait = ev->GetFloat( 1 ); } void Trigger::EventSetDelay ( Event *ev ) { delay = ev->GetFloat( 1 ); } void Trigger::EventSetKey ( Event *ev ) { key = ev->GetString( 1 ); } void Trigger::EventSetCount ( Event *ev ) { count = ev->GetInteger( 1 ); } void Trigger::EventSetMessage ( Event *ev ) { SetMessage( ev->GetString( 1 ) ); } void Trigger::SetMessage ( const char *text ) { if ( text ) message = str( text ); } str &Trigger::Message ( void ) { return message; } void Trigger::EventSetNoise ( Event *ev ) { SetNoise( ev->GetString( 1 ) ); } void Trigger::SetNoise ( const char *text ) { if ( text ) { noise = str( text ); // // cache in the sound // gi.soundindex( text ); } } str &Trigger::Noise ( void ) { return noise; } CLASS_DECLARATION( Trigger, TouchField, NULL ); ResponseDef TouchField::Responses[] = { { &EV_Trigger_Effect, ( Response )TouchField::SendEvent }, { NULL, NULL } }; void TouchField::Setup ( Entity *ownerentity, Event touchevent, Vector min, Vector max, int respondto ) { assert( ownerentity ); if ( !ownerentity ) { error( "Setup", "Null owner" ); } owner = ownerentity; ontouch = touchevent; setSize( min, max ); setSolidType( SOLID_TRIGGER ); this->respondto = respondto; } void TouchField::SendEvent ( Event *ev ) { Event *event; // Check if our owner is still around if ( owner ) { event = new Event( ontouch ); event->AddEntity( ev->GetEntity( 1 ) ); owner->PostEvent( event, delay ); } else { // Our owner is gone! The bastard didn't delete us! // Guess we're no longer needed, so remove ourself. PostEvent( EV_Remove, 0 ); } } /*****************************************************************************/ /*SINED trigger_once (.5 .5 .5) ? notouch x NOT_PLAYERS MONSTERS PROJECTILES Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching If "health" is set, the trigger must be killed to activate it. If "delay" is set, the trigger waits some time after activating before firing. "targetname". If "health" is set, the trigger must be killed to activate. "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. "key" The item needed to activate this. (default nothing) If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) set "message" to text string /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerOnce, "trigger_once" ); ResponseDef TriggerOnce::Responses[] = { { NULL, NULL } }; TriggerOnce::TriggerOnce() { // // gross HACK for oilrig.bsp // if ( message == "Foreman jumps over the edge of the rig" ) { PostEvent( EV_Remove, 0 ); } // // no matter what, we only trigger once // count = 1; respondto = spawnflags ^ TRIGGER_PLAYERS; // // if it's not supposed to be touchable, clear the trigger // if ( spawnflags & 1 ) setSolidType( SOLID_NOT ); } /*****************************************************************************/ /*SINED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) x x NOT_PLAYERS MONSTERS PROJECTILES This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages. If NOT_PLAYERS is set, the trigger does not respond to events triggered by players If MONSTERS is set, the trigger will respond to events triggered by monsters If PROJECTILES is set, the trigger will respond to events triggered by projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerRelay, "trigger_relay" ); ResponseDef TriggerRelay::Responses[] = { { &EV_Touch, NULL }, { NULL, NULL } }; TriggerRelay::TriggerRelay() { setSolidType( SOLID_NOT ); } /*****************************************************************************/ /*SINED trigger_damagethreshold (0 .5 .8) ? x INVISIBLE NOT_PLAYERS MONSTERS ACCUMULATIVE Triggers only when a threshold of damage is exceeded. When used in conjunction with func_breakawaywall, allows walls that may be destroyed with a rocket blast. Will also trigger any targeted func_areaportals when not invisible. INVISIBLE tells the trigger to not be visible. "health" specifies how much damage must occur before trigger fires. Default is 60. "cnt" specifies how many times the trigger can fire before it will remove itself. -1 sepecies infinite firing. Default is 1. "key" The item needed to activate this. (default nothing) If NOT_PLAYERS is set, the trigger does not respond to damage caused by players If MONSTERS is set, the trigger will respond to damage caused by monsters /*****************************************************************************/ #define DAMAGETHRESHOLD_ACCUMULATIVE ( 1 << 4 ) CLASS_DECLARATION( Trigger, DamageThreshold, "trigger_damagethreshold" ); // Only used internally static Event EV_DamageThreshold_Setup( "setup" ); ResponseDef DamageThreshold::Responses[] = { { &EV_Damage, ( Response )DamageThreshold::DamageEvent }, { &EV_DamageThreshold_Setup, ( Response )DamageThreshold::Setup }, { &EV_Touch, NULL }, { NULL, NULL } }; void DamageThreshold::DamageEvent ( Event *ev ) { Event *event; Entity *inflictor; Entity *attacker; int damage; if ( takedamage == DAMAGE_NO ) { return; } damage = ev->GetInteger( 1 ); inflictor = ev->GetEntity( 2 ); attacker = ev->GetEntity( 3 ); if ( spawnflags & DAMAGETHRESHOLD_ACCUMULATIVE ) { health -= damage; damage_taken += damage; if ( health > 0 ) return; } else { if ( damage < health ) { return; } damage_taken = damage; } event = new Event( EV_Activate ); event->AddEntity( attacker ); ProcessEvent( event ); } void DamageThreshold::Setup ( Event *ev ) { SetAreaPortals( Target(), false ); } DamageThreshold::DamageThreshold() { // // Default behavior is one use // count = G_GetIntArg( "cnt", 1 ); damage_taken = 0; setSolidType( SOLID_BSP ); setMoveType( MOVETYPE_PUSH ); if ( !( spawnflags & INVISIBLE ) ) { showModel(); PostEvent( EV_DamageThreshold_Setup, 0 ); } health = G_GetFloatArg( "health", 60 ); max_health = health; takedamage = DAMAGE_YES; respondto = ( spawnflags ^ TRIGGER_PLAYERS ) & ~TRIGGER_PROJECTILES; } /*****************************************************************************/ /*SINED trigger_secret (.5 .5 .5) ? notouch x NOT_PLAYERS MONSTERS PROJECTILES Secret counter trigger. Automatically sets and increments script variables \ level.total_secrets and level.found_secrets. set "message" to text string "key" The item needed to activate this. (default nothing) "thread" name of thread to trigger. This can be in a different script file as well \ by using the '::' notation. (defaults to "global/universal_script.scr::secret") If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( TriggerOnce, TriggerSecret, "trigger_secret" ); ResponseDef TriggerSecret::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerSecret::FoundSecret }, { NULL, NULL } }; TriggerSecret::TriggerSecret() { if ( !LoadingSavegame ) { level.total_secrets++; levelVars.SetVariable( "total_secrets", level.total_secrets ); } respondto = spawnflags ^ TRIGGER_PLAYERS; // set the thread to trigger when secrets are found thread = G_GetStringArg( "thread", "global/universal_script.scr::secret" ); } void TriggerSecret::FoundSecret ( Event *ev ) { // // anything that causes the trigger to fire increments the number // of secrets found. This way, if the level designer triggers the // secret from the script, the player still gets credit for finding // it. This is to prevent a secret from becoming undiscoverable. // level.found_secrets++; levelVars.SetVariable( "found_secrets", level.found_secrets ); } /*****************************************************************************/ /*SINED trigger_push (.5 .5 .5) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES Pushes entities as if they were caught in a heavy wind. "speed" indicates the rate that entities are pushed (default 1000). "angle" indicates the direction the wind is blowing (-1 is up, -2 is down) "key" The item needed to activate this. (default nothing) "target" if target is set, then a velocity will be calculated based on speed If NOT_PLAYERS is set, the trigger does not push players If NOT_MONSTERS is set, the trigger will not push monsters If NOT_PROJECTILES is set, the trigger will not push projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerPush, "trigger_push" ); ResponseDef TriggerPush::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerPush::Push }, { NULL, NULL } }; void TriggerPush::Push ( Event *ev ) { Entity *other; other = ev->GetEntity( 1 ); if ( other ) { const char * targ; Entity *ent; int num; targ = Target (); if ( targ[ 0 ] ) { num = G_FindTarget( 0, Target() ); ent = G_GetEntity( num ); if ( num && ent ) { other->velocity = G_CalculateImpulse ( other->worldorigin, ent->worldorigin, speed, other->gravity ); } } else other->velocity = pushvelocity; } } TriggerPush::TriggerPush() { speed = G_GetFloatArg( "speed", 1000 ); pushvelocity = G_GetMovedir() * speed; respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); } /*****************************************************************************/ /*SINED trigger_pushany (.5 .5 .5) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES Pushes entities as if they were caught in a heavy wind. "speed" indicates the rate that entities are pushed (default 1000). "angles" indicates the direction of the push "key" The item needed to activate this. (default nothing) "target" if target is set, then a velocity will be calculated based on speed If NOT_PLAYERS is set, the trigger does not push players If NOT_MONSTERS is set, the trigger will not push monsters If NOT_PROJECTILES is set, the trigger will not push projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerPushAny, "trigger_pushany" ); ResponseDef TriggerPushAny::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerPushAny::Push }, { NULL, NULL } }; void TriggerPushAny::Push ( Event *ev ) { Entity *other; other = ev->GetEntity( 1 ); if ( other ) { const char * targ; Entity *ent; int num; targ = Target (); if ( targ[ 0 ] ) { num = G_FindTarget( 0, Target() ); ent = G_GetEntity( num ); if ( num && ent ) { other->velocity = G_CalculateImpulse ( other->worldorigin, ent->worldorigin, speed, other->gravity ); } } else other->velocity = pushvelocity; } } TriggerPushAny::TriggerPushAny() { float mat[3][3]; setAngles( G_GetVectorArg( "angles", Vector( 0, 0, 0 ) ) ); AnglesToMat( angles.vec3(), mat ); speed = G_GetFloatArg( "speed", 1000 ); pushvelocity = Vector( mat[0][0],mat[0][1], mat[0][2] ) * speed; respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); } /*****************************************************************************/ /*SINED play_sound_triggered (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) AMBIENT-ON RELIABLE NOT_PLAYERS MONSTERS PROJECTILES AMBIENT-OFF x TOGGLE DO NOT USE, USE TRIGGER_SPEAKER INSTEAD play a sound when it is used AMBIENT-ON specifies an ambient sound that starts on RELIABLE should only be set for crucial voice-overs or sounds AMBIENT-OFF specifies an ambient sound that starts off if (AMBIENT-?) is not set, then the sound is sent over explicitly this creates more net traffic "volume" how loud 0-4 (1 default full volume, ambients do not respond to volume) "noise" sound to play "channel" channel on which to play sound (0-7) (2 (voice) is default) "attenuation" attenuation factor (0 becomes 1 for non-ambients, 2 for ambients) -1 - none, send to whole level 0 - default (normal or ambient) 1 - normal fighting sounds 2 - idle monster sounds 3 - ambient sounds "key" The item needed to activate this. (default nothing) Normal sounds play each time the target is used. Ambient Looped sounds have an attenuation of 2 by default, volume 1, and the use function toggles it on/off. Multiple identical ambient looping sounds will just increase volume without any speed cost. The attenuation factor can be over-ridden by specifying an attenuation factor. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerPlaySound, "play_sound_triggered" ); Event EV_TriggerPlaySound_SetVolume( "volume" ); Event EV_TriggerPlaySound_SetAttenuation( "attenuation" ); Event EV_TriggerPlaySound_SetChannel( "channel" ); ResponseDef TriggerPlaySound::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerPlaySound::ToggleSound }, { &EV_TriggerPlaySound_SetVolume, ( Response )TriggerPlaySound::SetVolume }, { &EV_TriggerPlaySound_SetAttenuation, ( Response )TriggerPlaySound::SetAttenuation }, { &EV_TriggerPlaySound_SetChannel, ( Response )TriggerPlaySound::SetChannel }, { &EV_Touch, NULL }, { NULL, NULL } }; void TriggerPlaySound::ToggleSound ( Event *ev ) { if ( !state ) { // noise should already be initialized assert( Noise().length() ); if ( ambient || spawnflags & 128 ) state = 1; if (ambient) { int attn; attn = attenuation; if (attn > 3) attn = 3; if (attn < 0) attn = 0; edict->s.sound = ( gi.soundindex( Noise().c_str() ) ) + (attn<<14); } else { sound( Noise().c_str(), volume, channel, attenuation, pitch, timeofs, fadetime ); } } else { state = 0; if (ambient) edict->s.sound = 0; else RandomGlobalSound( "null_sound", volume, channel | CHAN_NO_PHS_ADD, -1 ); } } void TriggerPlaySound::SetVolume ( Event *ev ) { //FIXME // update sound volume on client // this cannot be done volume = ev->GetFloat( 1 ); } void TriggerPlaySound::SetAttenuation ( Event *ev ) { attenuation = ev->GetFloat( 1 ); } void TriggerPlaySound::SetChannel ( Event *ev ) { channel = ev->GetInteger( 1 ); } TriggerPlaySound::TriggerPlaySound() { // // this is here so that a baseline is created for this // edict->s.effects = EF_FORCEBASELINE; // // this is here so that it gets sent over at least once // showModel(); ambient = false; pitch = 1.0f; timeofs = 0; fadetime = 0; volume = G_GetFloatArg( "volume", 1.0 ); channel = G_GetIntArg( "channel", CHAN_VOICE ); state = 0; respondto = spawnflags ^ TRIGGER_PLAYERS; if (spawnflags & 2) channel |= CHAN_RELIABLE; if (spawnflags & 33) { ambient = true; attenuation = G_GetFloatArg( "attenuation", ATTN_IDLE ); if (spawnflags & 1) { ToggleSound( NULL ); } } else { attenuation = G_GetFloatArg( "attenuation", ATTN_NORM ); } } /*****************************************************************************/ /*SINED trigger_speaker (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) AMBIENT-ON RELIABLE NOT_PLAYERS MONSTERS PROJECTILES AMBIENT-OFF x TOGGLE play a sound when it is used AMBIENT-ON specifies an ambient sound that starts on RELIABLE should only be set for crucial voice-overs or sounds AMBIENT-OFF specifies an ambient sound that starts off if (AMBIENT-?) is not set, then the sound is sent over explicitly this creates more net traffic "volume" how loud 0-4 (1 default full volume, ambients do not respond to volume) "noise" sound to play "channel" channel on which to play sound (0 auto, 1 weapon, 2 voice, 3 item, 4 body, 8 don't use PHS) (voice is default) "pitch" the pitch of the sample ( default 1, no pitch-shift ) "fadetime" fade the sound in over a time period in seconds ( default 0, no fade ) "timeofs" start time offset in milli-seconds into this sound ( default 0, no timeofs ) "attenuation" attenuation factor (0 becomes 1 for non-ambients, 2 for ambients) -1 - none, send to whole level 0 - default (normal or ambient) 1 - normal fighting sounds 2 - idle monster sounds 3 - ambient sounds "key" The item needed to activate this. (default nothing) "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. Normal sounds play each time the target is used. Ambient Looped sounds have an attenuation of 2 by default, volume 1, and the use function toggles it on/off. Multiple identical ambient looping sounds will just increase volume without any speed cost. The attenuation factor can be over-ridden by specifying an attenuation factor. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( TriggerPlaySound, TriggerSpeaker, "trigger_speaker" ); ResponseDef TriggerSpeaker::Responses[] = { { &EV_Touch, NULL }, { NULL, NULL } }; TriggerSpeaker::TriggerSpeaker() { if (attenuation == -1) attenuation = 0; pitch = G_GetFloatArg( "pitch", 1.0 ); fadetime = G_GetFloatArg( "fadetime", 0 ); timeofs = G_GetFloatArg( "timeofs", 0 ); } /*****************************************************************************/ /*SINED trigger_randomspeaker (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) x RELIABLE NOT_PLAYERS MONSTERS PROJECTILES x x play a sound at random times RELIABLE should only be set for crucial voice-overs or sounds "mindelay" minimum delay between sound triggers (default 3) "maxdelay" maximum delay between sound triggers (default 10) "volume" how loud 0-4 (1 default full volume) "noise" sound to play "channel" channel on which to play sound (0 auto, 1 weapon, 2 voice, 3 item, 4 body, 8 don't use PHS) (voice is default) "pitch" the pitch of the sample ( default 1, no pitch-shift ) "fadetime" fade the sound in over a time period in seconds ( default 0, no fade ) "timeofs" start time offset in milli-seconds into this sound ( default 0, no timeofs ) "attenuation" attenuation factor -1 - none, send to whole level 0 - default (normal) 1 - normal fighting sounds 2 - idle monster sounds 3 - ambient sounds "key" The item needed to activate this. (default nothing) Normal sounds play each time the target is used. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( TriggerSpeaker, RandomSpeaker, "trigger_randomspeaker" ); Event EV_TriggerRandomSpeaker_TriggerSound( "triggersound" ); ResponseDef RandomSpeaker::Responses[] = { { &EV_Trigger_Effect, ( Response )RandomSpeaker::TriggerSound }, { &EV_Touch, NULL }, { NULL, NULL } }; void RandomSpeaker::TriggerSound ( Event *ev ) { ScheduleSound(); TriggerPlaySound::ToggleSound( ev ); } void RandomSpeaker::ScheduleSound ( void ) { CancelEventsOfType( EV_Trigger_Effect ); PostEvent( EV_Trigger_Effect, mindelay + G_Random( maxdelay-mindelay ) ); } RandomSpeaker::RandomSpeaker() { mindelay = G_GetFloatArg( "mindelay", 3 ); maxdelay = G_GetFloatArg( "maxdelay", 10 ); ScheduleSound(); } /*****************************************************************************/ /*SINED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION x NOT_PLAYERS MONSTERS PROJECTILES When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. "spawnspot" name of the spawn location to start at in next map. "key" The item needed to activate this. (default nothing) "thread" This defaults to "LevelComplete" and should point to a thread that is called just before the level ends. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerChangeLevel, "trigger_changelevel" ); ResponseDef TriggerChangeLevel::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerChangeLevel::ChangeLevel }, { NULL, NULL } }; TriggerChangeLevel::TriggerChangeLevel() { map = G_GetStringArg( "map" ); if ( !map.length() && !LoadingSavegame ) { error( "Init", "changelevel trigger doesn't have map" ); } // We have to handle calling the thread ourselves, so clear out Trigger's tread variable thread = ""; changethread = G_GetStringArg( "thread", "LevelComplete" ); spawnspot = G_GetStringArg( "spawnspot" ); respondto = spawnflags ^ TRIGGER_PLAYERS; } void TriggerChangeLevel::ChangeLevel ( Event *ev ) { Entity *other; other = ev->GetEntity( 1 ); if ( level.intermissiontime ) { // already activated return; } // if noexit, do a ton of damage to other if ( deathmatch->value && DM_FLAG( DF_SAME_LEVEL ) && other != world ) { other->Damage( this, other, 10 * other->max_health, other->worldorigin, vec_zero, vec_zero, 1000, 0, MOD_CRUSH, -1, -1, 1.0f ); return; } // if multiplayer, let everyone know who hit the exit if ( deathmatch->value ) { if ( other && other->edict->client ) { gi.bprintf( PRINT_HIGH, "%s exited the level.\n", other->edict->client->pers.netname ); } } // tell the script that the player's not ready so that if we return to this map, // we can do something about it. Director.PlayerNotReady(); // // make sure we execute the thread before changing // if ( changethread.length() ) { ExecuteThread( changethread ); } G_BeginIntermission( map.c_str() ); } const char *TriggerChangeLevel::Map ( void ) { return map.c_str(); } const char *TriggerChangeLevel::SpawnSpot ( void ) { return spawnspot.c_str(); } /*****************************************************************************/ /*SINED trigger_use (0.5 0.5 0.5) ? VISIBLE x NOT_PLAYERS MONSTERS Activates targets when 'used' by an entity "key" The item needed to activate this. (default nothing) "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerUse, "trigger_use" ); ResponseDef TriggerUse::Responses[] = { { &EV_Use, ( Response )TriggerUse::TriggerStuff }, { &EV_Touch, NULL }, { NULL, NULL } }; TriggerUse::TriggerUse() { if ( spawnflags & VISIBLE ) { showModel(); setMoveType( MOVETYPE_PUSH ); setSolidType( SOLID_BSP ); } respondto = ( spawnflags ^ TRIGGER_PLAYERS ) & ~TRIGGER_PROJECTILES; } /*****************************************************************************/ /*SINED trigger_useonce (0.5 0.5 0.5) ? VISIBLE x NOT_PLAYERS MONSTERS Activates targets when 'used' by an entity, but only once "key" The item needed to activate this. (default nothing) "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters /*****************************************************************************/ CLASS_DECLARATION( TriggerUse, TriggerUseOnce, "trigger_useonce" ); ResponseDef TriggerUseOnce::Responses[] = { { &EV_Touch, NULL }, { NULL, NULL } }; TriggerUseOnce::TriggerUseOnce() { // Only allow 1 use. count = 1; respondto = ( spawnflags ^ TRIGGER_PLAYERS ) & ~TRIGGER_PROJECTILES; } /*****************************************************************************/ /*SINED trigger_hurt (0.5 0.5 0.5) ? x x NOT_PLAYERS NOT_MONSTERS PROJECTILES "damage" amount of damage to cause. (default 10) "key" The item needed to activate this. (default nothing) If NOT_PLAYERS is set, the trigger does not hurt players If NOT_MONSTERS is set, the trigger does not hurt monsters If PROJECTILES is set, the trigger will hurt projectiles (rockets, grenades, etc.) /*****************************************************************************/ Event EV_TriggerHurt_SetDamage( "setdamage" ); CLASS_DECLARATION( TriggerUse, TriggerHurt, "trigger_hurt" ); ResponseDef TriggerHurt::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerHurt::Hurt }, { &EV_TriggerHurt_SetDamage, ( Response )TriggerHurt::SetDamage }, { &EV_Touch, ( Response )Trigger::TriggerStuff }, { NULL, NULL } }; TriggerHurt::TriggerHurt() { damage = G_GetFloatArg( "damage", 10 ); respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS ); } void TriggerHurt::SetDamage ( Event *ev ) { damage = ev->GetInteger( 1 ); } void TriggerHurt::Hurt ( Event *ev ) { Entity *other; other = ev->GetEntity( 1 ); if ( ( damage != 0 ) && !other->deadflag && !( other->flags & FL_GODMODE ) ) { other->Damage( this, other, damage, other->worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR|DAMAGE_NO_SKILL, MOD_CRUSH, -1, -1, 1.0f ); } } /*****************************************************************************/ /*SINED trigger_damagetargets (0.5 0.5 0.5) ? SOLID x NOT_PLAYERS NOT_MONSTERS PROJECTILES "damage" amount of damage to cause. If no damage is specified, objects\ are damaged by the current health+1 "key" The item needed to activate this. (default nothing) if a trigger_damagetargets is shot at and the SOLID flag is set,\ the damage is passed on to the targets If NOT_PLAYERS is set, the trigger does not hurt players If NOT_MONSTERS is set, the trigger does not hurt monsters If PROJECTILES is set, the trigger will hurt projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerDamageTargets, "trigger_damagetargets" ); ResponseDef TriggerDamageTargets::Responses[] = { { &EV_Trigger_ActivateTargets, ( Response )TriggerDamageTargets::DamageTargets }, { &EV_Damage, ( Response )TriggerDamageTargets::PassDamage }, { &EV_Touch, NULL }, { NULL, NULL } }; TriggerDamageTargets::TriggerDamageTargets() { damage = G_GetFloatArg( "damage", 0 ); respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS ); if (spawnflags & 1) { health = 60; max_health = health; takedamage = DAMAGE_YES; setSolidType( SOLID_BBOX ); } else { takedamage = DAMAGE_NO; setSolidType( SOLID_NOT ); } } void TriggerDamageTargets::PassDamage ( Event *ev ) { Entity *attacker; int dmg; Entity *ent; const char *name; int num; dmg = ev->GetInteger( 1 ); attacker = ev->GetEntity( 3 ); // // damage the targets // name = Target(); if ( name && strcmp( name, "" ) ) { num = 0; do { num = G_FindTarget( num, name ); if ( !num ) { break; } ent = G_GetEntity( num ); if ( !ent->deadflag && !( ent->flags & FL_GODMODE ) ) { ent->Damage( this, attacker, dmg, ent->worldorigin, vec_zero, vec_zero, 0, 0, MOD_CRUSH, -1, -1, 1.0f ); } } while ( 1 ); } } // //============================== // DamageTargets //============================== // void TriggerDamageTargets::DamageTargets ( Event *ev ) { Entity *other; Entity *ent; const char *name; int num; other = ev->GetEntity( 1 ); if ( triggerActivated ) { // // Entity triggered itself. Prevent an infinite loop // ev->Error( "Entity targeting itself--Targetname '%s'", TargetName() ); return; } triggerActivated = true; activator = other; // // print the message // if ( message.length() && other && other->isClient() ) { gi.centerprintf( other->edict, "jcx jcy string \"%s\"", message.c_str() ); if ( Noise().length() ) { other->sound( Noise(), 1, CHAN_VOICE, ATTN_NORM ); } } // // damage the targets // name = Target(); if ( name && strcmp( name, "" ) ) { num = 0; do { num = G_FindTarget( num, name ); if ( !num ) { break; } ent = G_GetEntity( num ); if ( !ent->deadflag && !( ent->flags & FL_GODMODE ) ) { if (damage) ent->Damage( this, other, damage, ent->worldorigin, vec_zero, vec_zero, 0, 0, MOD_CRUSH, -1, -1, 1.0f ); else ent->Damage( this, other, ent->health+1, ent->worldorigin, vec_zero, vec_zero, 0, 0, MOD_CRUSH, -1, -1, 1.0f ); } } while ( 1 ); } triggerActivated = false; } /*****************************************************************************/ /*SINED trigger_damagetargetsfixed (0.5 0.5 0.5) (-8 -8 -8) (8 8 8) SOLID x NOT_PLAYERS NOT_MONSTERS PROJECTILES "damage" amount of damage to cause. If no damage is specified, objects\ are damaged by the current health+1 "key" The item needed to activate this. (default nothing) if a trigger_damagetargetsfixed is shot at and the SOLID flag is set,\ the damage is passed on to the targets If NOT_PLAYERS is set, the trigger does not hurt players If NOT_MONSTERS is set, the trigger does not hurt monsters If PROJECTILES is set, the trigger will hurt projectiles (rockets, grenades, etc.) /*****************************************************************************/ CLASS_DECLARATION( TriggerDamageTargets, TriggerFixedDamageTargets, "trigger_damagetargetsfixed" ); ResponseDef TriggerFixedDamageTargets::Responses[] = { { &EV_Touch, NULL }, { NULL, NULL } }; /*****************************************************************************/ /*SINED trigger_particles (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) x NOSOUND NOT_PLAYERS MONSTERS PROJECTILES RANDSTYLE BRIGHT spawn some particles when triggered "count" number of particles to spawn ( default 10 ) "noise" sound to play when triggered ( defaults to random spark sounds ) "angles" specifies direction to spawn particles "key" The item needed to activate this. (default nothing) "particlestyle" style of particles ( default 122 (sparks) ) sample styles (121 blood, 120 gunsmoke, 123 orangeglow, 124 blueyellow, 125 debris trail, 128 oil, 129 waterspray) If NOSOUND is set, no sound will be played If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) If RANDSTYLE is set, the particles will be set to random colors in the particlestyle if BRIGHT is set, particles will be drawn extra bright /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerParticles, "trigger_particles" ); Event EV_TriggerParticles_SetDirection( "setdirection" ); ResponseDef TriggerParticles::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerParticles::SpawnParticles }, { &EV_TriggerParticles_SetDirection, ( Response )TriggerParticles::SetDirection }, { &EV_Touch, NULL }, { NULL, NULL } }; void TriggerParticles::SpawnParticles ( Event *ev ) { if ( !(spawnflags & 2) ) { if (Noise().length()) sound( Noise() ); else RandomGlobalSound( str("sparks") ); } if ( particlestyle == 122 ) SpawnTempDlight( worldorigin, 1, 1, 0, 100, 0.9, 1 ); Particles( worldorigin, dir, count, particlestyle, flags ); } void TriggerParticles::SetDirection ( Event *ev ) { const char *target; int num; Entity *end; // Set the end position if there is a target set. target = Target(); if ( target && strlen( target ) ) { if ( num = G_FindTarget( 0, target ) ) { end = G_GetEntity( num ); assert( end ); dir = end->worldorigin - worldorigin; dir.normalize(); } } else dir = Vector( 0, 0, 0 ); } TriggerParticles::TriggerParticles ( ) { const char *target; target = Target(); if ( target && strlen( target ) ) PostEvent( EV_TriggerParticles_SetDirection, 0 ); else dir = G_GetMovedir(); SetNoise( G_GetSpawnArg( "noise", "" ) ); count = G_GetIntArg( "count", 10 ); particlestyle = G_GetIntArg( "particlestyle", 122 ); if ( spawnflags & 32 ) flags |= PARTICLE_RANDOM; if ( spawnflags & 64 ) flags |= PARTICLE_OVERBRIGHT; } /*****************************************************************************/ /*SINED trigger_randomparticles (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON NOSOUND NOT_PLAYERS MONSTERS PROJECTILES RANDSTYLE BRIGHT START_ON start the effect on spawn some particles randomly triggering the object "count" number of particles to spawn ( default 10 ) "noise" sound to play when triggered ( default none ) "mindelay" minimum delay between particle triggers (default 3) "maxdelay" maximum delay between particle triggers (default 10) "key" The item needed to activate this. (default nothing) "particlestyle" style of particles ( default 122 (sparks) ) sample styles (121 blood, 120 gunsmoke, 123 orangeglow, 124 blueyellow, 125 debris trail ) If NOSOUND is set, no sound will be played If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) If RANDSTYLE is set, the particles will be set to random colors in the particlestyle if BRIGHT is set, particles will be drawn extra bright /*****************************************************************************/ Event EV_RandomTriggerParticles_SpawnParticles( "spawnparticles" ); CLASS_DECLARATION( TriggerParticles, RandomTriggerParticles, "trigger_randomparticles" ); ResponseDef RandomTriggerParticles::Responses[] = { { &EV_RandomTriggerParticles_SpawnParticles, ( Response )RandomTriggerParticles::RandomParticles }, { &EV_Trigger_Effect, ( Response )RandomTriggerParticles::ToggleParticles }, { &EV_Touch, NULL }, { NULL, NULL } }; void RandomTriggerParticles::RandomParticles ( Event *ev ) { SpawnParticles( NULL ); if (state) ScheduleParticles(); } void RandomTriggerParticles::ToggleParticles ( Event *ev ) { state ^= 1; if (state) ScheduleParticles(); else CancelEventsOfType( EV_RandomTriggerParticles_SpawnParticles ); } void RandomTriggerParticles::ScheduleParticles ( void ) { if (state) PostEvent( EV_RandomTriggerParticles_SpawnParticles, mindelay + G_Random( maxdelay-mindelay ) ); } RandomTriggerParticles::RandomTriggerParticles() { state = 0; if (spawnflags & 1) state = 1; ScheduleParticles(); mindelay = G_GetFloatArg( "mindelay", 3 ); maxdelay = G_GetFloatArg( "maxdelay", 10 ); } /*****************************************************************************/ /*SINED trigger_thread (.5 .5 .5) ? notouch x NOT_PLAYERS MONSTERS PROJECTILES Variable sized trigger. After firing cnt times, removes itself. "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. You must set the key "target" to the name of another object in the level that has a matching name "cnt" how many times it can be triggered ( default 1 ) "targetname". If "health" is set, the trigger must be killed to activate. if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. "key" The item needed to activate this. (default nothing) If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) set "message" to text string /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerThread, "trigger_thread" ); ResponseDef TriggerThread::Responses[] = { { NULL, NULL } }; TriggerThread::TriggerThread() { if ( count == -1 ) count = 1; if ( !thread.length() ) { warning( "TriggerThread", "thread string not defined.\n" ); } } /*****************************************************************************/ /*SINED trigger_camerause (0.5 0.5 0.5) ? VISIBLE x NOT_PLAYERS MONSTERS Activates 'targeted' camera when 'used' If activated, toggles through cameras "key" The item needed to activate this. (default nothing) "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters /*****************************************************************************/ CLASS_DECLARATION( TriggerUse, TriggerCameraUse, "trigger_camerause" ); ResponseDef TriggerCameraUse::Responses[] = { { &EV_Use, ( Response )TriggerCameraUse::TriggerCamera }, { &EV_Touch, NULL }, { NULL, NULL } }; void TriggerCameraUse::TriggerCamera ( Event *ev ) { str camthread; Entity *other; other = ev->GetEntity( 1 ); if ( other->isClient() ) { int num; Player * client; Entity * ent; Camera * cam; client = ( Player * )other; if ( client->ViewMode() == CAMERA_VIEW ) { str nextcam; cam = client->CurrentCamera(); // It's possible to get out of the camera if ( !cam ) return; nextcam = cam->NextCamera(); if ( nextcam.length() ) { num = G_FindTarget( 0, nextcam.c_str() ); if ( num ) { ent = (Entity *) G_GetEntity( num ); if ( ent && ent->isSubclassOf( Camera ) ) { cam = (Camera *)ent; camthread = cam->Thread(); client->SetCamera( cam ); } } } } else { num = G_FindTarget( 0, Target() ); if ( num ) { ent = (Entity *) G_GetEntity( num ); if ( ent && ent->isSubclassOf( Camera ) ) { cam = (Camera *)ent; camthread = cam->Thread(); client->SetCamera( cam ); } else { warning( "TriggerCamera", "%s is not a camera", Target() ); } } } if ( camthread.length() > 1 ) { if ( !ExecuteThread( camthread ) ) { warning( "TriggerCamera", "Null game script" ); } } } } /*****************************************************************************/ /*SINED trigger_mutate (0.5 0.5 0.5) ? x x NOT_PLAYERS NOT_MONSTERS mutates whatever triggers it. "key" The item needed to activate this. (default nothing) If NOT_PLAYERS is set, the trigger does not activate on players If NOT_MONSTERS is set, the trigger does not activate on monsters /*****************************************************************************/ CLASS_DECLARATION( TriggerUse, TriggerMutate, "trigger_mutate" ); ResponseDef TriggerMutate::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerMutate::Mutate }, { &EV_Touch, ( Response )Trigger::TriggerStuff }, { NULL, NULL } }; TriggerMutate::TriggerMutate() { respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS ); } void TriggerMutate::Mutate ( Event *ev ) { Event *event; Entity *other; other = ev->GetEntity( 1 ); if ( other && ( !( other->flags & FL_MUTATED ) ) ) { event = new Event( EV_Mutate ); other->ProcessEvent( event ); } } /*****************************************************************************/ /*SINED trigger_exit (0.5 0.5 0.5) ? When the player touches this, an exit icon will be displayed in his hud. This is to inform him that he is near an exit. /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerExit, "trigger_exit" ); ResponseDef TriggerExit::Responses[] = { { &EV_Trigger_Effect, ( Response )TriggerExit::DisplayExitSign }, { NULL, NULL } }; TriggerExit::TriggerExit() { wait = 1; respondto = TRIGGER_PLAYERS; } void TriggerExit::DisplayExitSign ( Event *ev ) { Entity * other; Player * client; other = ev->GetEntity( 1 ); if ( other && other->isClient() ) { client = ( Player * )other; // // change music // client->ChangeMusic( "success", "normal", false ); client->client->ps.stats[ STAT_EXITSIGN ] = 11; } } /*****************************************************************************/ /*SINED trigger_box (.5 .5 .5) ? x x NOT_PLAYERS MONSTERS PROJECTILES Variable sized repeatable trigger. Must be targeted at one or more entities. Made to be spawned via script. If "health" is set, the trigger must be killed to activate each time. If "delay" is set, the trigger waits some time after activating before firing. "thread" name of thread to trigger. This can be in a different script file as well\ by using the '::' notation. "wait" : Seconds between triggerings. (.2 default) "cnt" how many times it can be triggered (infinite default) If NOT_PLAYERS is set, the trigger does not respond to players If MONSTERS is set, the trigger will respond to monsters If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) set "message" to text string /*****************************************************************************/ CLASS_DECLARATION( Trigger, TriggerBox, "trigger_box" ); ResponseDef TriggerBox::Responses[] = { { NULL, NULL } }; TriggerBox::TriggerBox() { mins = G_GetVectorArg( "mins" ); maxs = G_GetVectorArg( "maxs" ); origin = ( mins + maxs ) * 0.5; setSize( mins - origin, maxs - origin ); setOrigin( origin ); }