/* Copyright (C) 1997-2001 Id Software, Inc. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" #include "g_gametypes.h" /*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) Fire an origin based temp entity event to the clients. "style" type byte */ void Use_Target_Tent( edict_t *ent, edict_t *other, edict_t *activator ) { G_SpawnEvent( ent->style, 0, ent->s.origin ); } void SP_target_temp_entity( edict_t *ent ) { ent->use = Use_Target_Tent; } //========================================================== //========================================================== /*QUAKED target_speaker (0 .7 .7) (-8 -8 -8) (8 8 8) LOOPED_ON LOOPED_OFF RELIABLE GLOBAL ACTIVATOR Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. Multiple identical looping sounds will just increase volume without any speed cost. -------- KEYS -------- noise : path/name of .wav file to play (eg. sounds/world/growl1.wav - see Notes). volume : 0.0 to 1.0 attenuation : -1 = none, send to whole level, 1 = normal fighting sounds, 2 = idle sound level, 3 = ambient sound level wait : delay in seconds between each time the sound is played ("random" key must be set - see Notes). random : random time variance in seconds added or subtracted from "wait" delay ("wait" key must be set - see Notes). targetname : the activating button or trigger points to this. notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- LOOPED_ON : &1 sound will loop and initially start on in level (will toggle on/off when triggered). LOOPED_OFF : &2 sound will loop and initially start off in level (will toggle on/off when triggered). RELIABLE : &4 GLOBAL : &8 Overrides attenuation setting. Sound will play full volume throughout the level as if it had attenuation -1 ACTIVATOR : &16 sound will play only for the player that activated the target. -------- NOTES -------- The path portion value of the "noise" key can be replaced by the implicit folder character "*" for triggered sounds that belong to a particular player model. For example, if you want to create a "bottomless pit" in which the player screams and dies when he falls into, you would place a trigger_multiple over the floor of the pit and target a target_speaker with it. Then, you would set the "noise" key to "*falling1.wav". The * character means the current player model's sound folder. So if your current player model is Visor, * = sounds/players/visor, if your current player model is Sarge, * = sounds/players/sarge, etc. This cool feature provides an excellent way to create "player-specific" triggered sounds in your levels.*/ void Use_Target_Speaker( edict_t *ent, edict_t *other, edict_t *activator ) { if( ent->spawnflags & 3 ) { // looping sound toggles if (ent->s.sound) ent->s.sound = 0; // turn it off else ent->s.sound = ent->noise_index; // start it } else { // normal sound if( ent->spawnflags & 8 ) G_Sound( activator, CHAN_VOICE|CHAN_RELIABLE, ent->noise_index, ent->volume, ent->attenuation ); // use a G_PositionedSound, because this entity won't normally be // sent to any clients because it is invisible else if( ent->spawnflags & 4 ) G_PositionedSound( ent->s.origin, ent, CHAN_VOICE|CHAN_RELIABLE, ent->noise_index, ent->volume, ent->attenuation ); else G_PositionedSound( ent->s.origin, ent, CHAN_VOICE, ent->noise_index, ent->volume, ent->attenuation ); } } void SP_target_speaker( edict_t *ent ) { char buffer[MAX_QPATH]; if( !st.noise ) { if( developer->integer ) G_Printf( "target_speaker with no noise set at %s\n", vtos(ent->s.origin) ); return; } if( !strstr (st.noise, ".wav") ) Q_snprintfz( buffer, sizeof(buffer), "%s.wav", st.noise ); else Q_strncpyz( buffer, st.noise, sizeof(buffer) ); ent->noise_index = trap_SoundIndex( buffer ); if( !ent->volume ) ent->volume = 1.0; if( ent->attenuation == -1 || ent->spawnflags & 8 ) // use -1 so 0 defaults to ATTN_NONE ent->attenuation = ATTN_NONE; else if( !ent->attenuation ) ent->attenuation = ATTN_NORM; // check for prestarted looping sound if( ent->spawnflags & 1 ) ent->s.sound = ent->noise_index; ent->use = Use_Target_Speaker; // must link the entity so we get areas and clusters so // the server can determine who to send updates to trap_LinkEntity(ent); } //========================================================== /*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) Spawns an explosion temporary entity when used. "delay" wait this long before going off "dmg" how much radius damage should be done, defaults to 0 */ void target_explosion_explode( edict_t *self ) { float save; G_SpawnEvent( EV_EXPLOSION1, 0, self->s.origin ); T_RadiusDamage( self, self->activator, NULL, self->dmg, self->dmg, 0, NULL, self->dmg+40, MOD_EXPLOSIVE ); save = self->delay; self->delay = 0; G_UseTargets( self, self->activator ); self->delay = save; } void use_target_explosion( edict_t *self, edict_t *other, edict_t *activator ) { self->activator = activator; if( !self->delay ) { target_explosion_explode( self ); return; } self->think = target_explosion_explode; self->nextthink = level.timemsec + self->delay * 1000; } void SP_target_explosion( edict_t *ent ) { ent->use = use_target_explosion; ent->r.svflags = SVF_NOCLIENT; } //========================================================== /*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) Creates a particle splash effect when used. "count" how many pixels in the splash "dmg" if set, does a radius damage at this location when it splashes useful for lava/sparks "color" color in r g b floating point format */ void use_target_splash( edict_t *self, edict_t *other, edict_t *activator ) { edict_t *event; event = G_SpawnEvent( EV_LASER_SPARKS, DirToByte (self->movedir), self->s.origin ); event->s.eventCount = self->count; // default to none if( VectorCompare (self->color, vec3_origin) ) event->s.colorRGBA = 0; else event->s.colorRGBA = COLOR_RGBA ( (int)(self->color[0]*255)&255, (int)(self->color[1]*255)&255, (int)(self->color[2]*255)&255, 255); if( self->dmg ) T_RadiusDamage( self, activator, NULL, self->dmg, self->dmg, 0, NULL, self->dmg+40, MOD_SPLASH ); } void SP_target_splash( edict_t *self ) { self->use = use_target_splash; G_SetMovedir( self->s.angles, self->movedir ); if( !self->count ) self->count = 32; self->r.svflags = SVF_NOCLIENT; } //========================================================== /*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) Set target to the type of entity you want spawned. Useful for spawning monsters and gibs in the factory levels. For monsters: Set direction to the facing you want it to have. For gibs: Set direction if you want it moving and speed how fast it should be moving otherwise it will just be dropped */ void use_target_spawner( edict_t *self, edict_t *other, edict_t *activator ) { edict_t *ent; ent = G_Spawn(); ent->classname = self->target; VectorCopy( self->s.origin, ent->s.origin ); VectorCopy( self->s.angles, ent->s.angles ); G_CallSpawn( ent ); trap_UnlinkEntity( ent ); KillBox( ent ); trap_LinkEntity( ent ); if( self->speed ) VectorCopy( self->movedir, ent->velocity ); } void SP_target_spawner( edict_t *self ) { self->use = use_target_spawner; self->r.svflags = SVF_NOCLIENT; if( self->speed ) { G_SetMovedir( self->s.angles, self->movedir ); VectorScale( self->movedir, self->speed, self->movedir ); } } //========================================================== /*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS Fires a blaster bolt in the set direction when triggered. dmg default is 15 speed default is 1000 */ void use_target_blaster( edict_t *self, edict_t *other, edict_t *activator ) { int type; if( self->spawnflags & 2 ) type = ET_GENERIC; else if( self->spawnflags & 1 ) type = ET_ELECTRO_WEAK; else type = ET_BLASTER; // FIXME //fire_blaster( self, self->s.origin, self->movedir, self->dmg, self->speed, type, MOD_TARGET_BLASTER ); //W_Fire_Plasma( self, self->s.origin, self->movedir, self->dmg, self->dmg, 70, self->speed, MOD_TARGET_BLASTER ); G_Sound( self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM ); } void SP_target_blaster( edict_t *self ) { self->use = use_target_blaster; G_SetMovedir (self->s.angles, self->movedir); self->noise_index = trap_SoundIndex ("sounds/weapons/laser2.wav"); if (!self->dmg) self->dmg = 15; if (!self->speed) self->speed = 1000; self->r.svflags = SVF_NOCLIENT; } //========================================================== /*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. */ void trigger_crosslevel_trigger_use( edict_t *self, edict_t *other, edict_t *activator ) { game.serverflags |= self->spawnflags; G_FreeEdict( self ); } void SP_target_crosslevel_trigger( edict_t *self ) { self->r.svflags = SVF_NOCLIENT; self->use = trigger_crosslevel_trigger_use; } /*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and killtarget also work. "delay" delay before using targets if the trigger has been activated (default 1) */ void target_crosslevel_target_think (edict_t *self) { if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) { G_UseTargets (self, self); G_FreeEdict (self); } } void SP_target_crosslevel_target (edict_t *self) { if (! self->delay) self->delay = 1; self->r.svflags = SVF_NOCLIENT; self->think = target_crosslevel_target_think; self->nextthink = level.timemsec + self->delay * 1000; } //========================================================== /* QUAKED target_laser (0 .5 0) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT When triggered, fires a laser. You can either set a target or a direction. -------- KEYS -------- angles: alternate "pitch, yaw, roll" angles method of aiming laser (default 0 0 0). target : point this to a target_position entity to set the laser's aiming direction. targetname : the activating trigger points to this. notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- START_ON : when set, the laser will start on in the game. RED : GREEN : BLUE : YELLOW : ORANGE : FAT : */ void target_laser_think( edict_t *self ) { edict_t *ignore; vec3_t start; vec3_t end; trace_t tr; vec3_t point; vec3_t last_movedir; int count; // our lifetime has expired if( self->delay && (self->wait < level.time) ) { if ( self->r.owner && self->r.owner->use ) { self->r.owner->use ( self->r.owner, self, self->activator ); } G_FreeEdict( self ); return; } if( self->spawnflags & 0x80000000 ) count = 8; else count = 4; if( self->enemy ) { VectorCopy( self->movedir, last_movedir ); VectorMA( self->enemy->r.absmin, 0.5, self->enemy->r.size, point ); VectorSubtract( point, self->s.origin, self->movedir ); VectorNormalize( self->movedir ); if( !VectorCompare(self->movedir, last_movedir) ) self->spawnflags |= 0x80000000; } ignore = self; VectorCopy( self->s.origin, start ); VectorMA( start, 2048, self->movedir, end ); VectorClear( tr.endpos ); // shut up compiler while( 1 ) { trap_Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT ); if( tr.fraction == 1 ) break; // hurt it if we can if( (game.edicts[tr.ent].takedamage) && !(game.edicts[tr.ent].flags & FL_IMMUNE_LASER) ) { if( game.edicts[tr.ent].r.client && self->activator->r.client ) { if ( !GS_Gametype_IsTeamBased(game.gametype) || game.edicts[tr.ent].s.team != self->activator->s.team ) T_Damage( &game.edicts[tr.ent], self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, self->count ); } else { T_Damage( &game.edicts[tr.ent], self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, self->count ); } } // if we hit something that's not a monster or player or is immune to lasers, we're done if( !(game.edicts[tr.ent].r.svflags & SVF_MONSTER) && (!game.edicts[tr.ent].r.client) ) { if( self->spawnflags & 0x80000000 ) { edict_t *event; self->spawnflags &= ~0x80000000; event = G_SpawnEvent( EV_LASER_SPARKS, DirToByte (tr.plane.normal), tr.endpos ); event->s.eventCount = count; event->s.colorRGBA = self->s.colorRGBA; } break; } ignore = &game.edicts[tr.ent]; VectorCopy( tr.endpos, start ); } VectorCopy( tr.endpos, self->s.origin2 ); self->nextthink = level.timemsec + game.framemsec; } void target_laser_on( edict_t *self ) { if( !self->activator ) self->activator = self; self->spawnflags |= 0x80000001; self->r.svflags &= ~SVF_NOCLIENT; self->wait = level.time + self->delay; target_laser_think( self ); } void target_laser_off( edict_t *self ) { self->spawnflags &= ~1; self->r.svflags |= SVF_NOCLIENT; self->nextthink = 0; } void target_laser_use( edict_t *self, edict_t *other, edict_t *activator ) { self->activator = activator; if( self->spawnflags & 1 ) target_laser_off( self ); else target_laser_on( self ); } void target_laser_start( edict_t *self ) { edict_t *ent; self->movetype = MOVETYPE_NONE; self->r.solid = SOLID_NOT; self->s.type = ET_BEAM; self->s.modelindex = 1; // must be non-zero self->r.svflags = SVF_FORCEOLDORIGIN; // set the beam diameter if( self->spawnflags & 64 ) self->s.frame = 16; else self->s.frame = 4; // set the color if (self->spawnflags & 2) self->s.colorRGBA = COLOR_RGBA(220, 0, 0, 76); else if (self->spawnflags & 4) self->s.colorRGBA = COLOR_RGBA(0, 220, 0, 76); else if (self->spawnflags & 8) self->s.colorRGBA = COLOR_RGBA(0, 0, 220, 76); else if (self->spawnflags & 16) self->s.colorRGBA = COLOR_RGBA(220, 220, 0, 76); else if (self->spawnflags & 32) self->s.colorRGBA = COLOR_RGBA(255, 255, 0, 76); if( !self->enemy ) { if( self->target ) { ent = G_Find( NULL, FOFS(targetname), self->target ); if( !ent ) if( developer->integer ) G_Printf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); self->enemy = ent; } else { G_SetMovedir( self->s.angles, self->movedir ); } } self->use = target_laser_use; self->think = target_laser_think; if( !self->dmg ) self->dmg = 1; VectorSet( self->r.mins, -8, -8, -8 ); VectorSet( self->r.maxs, 8, 8, 8 ); trap_LinkEntity( self ); if( self->spawnflags & 1 ) target_laser_on( self ); else target_laser_off( self ); } void SP_target_laser( edict_t *self ) { // let everything else get spawned before we start firing self->think = target_laser_start; self->nextthink = level.timemsec + 1000; self->count = MOD_TARGET_LASER; } //========================================================== /*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE speed How many seconds the ramping will take message two letters; starting lightlevel and ending lightlevel */ void target_lightramp_think (edict_t *self) { char style[2]; style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; style[1] = 0; trap_ConfigString (CS_LIGHTS+self->enemy->style, style); if ((level.time - self->timestamp) < self->speed) { self->nextthink = level.timemsec + game.framemsec; } else if (self->spawnflags & 1) { char temp; temp = self->movedir[0]; self->movedir[0] = self->movedir[1]; self->movedir[1] = temp; self->movedir[2] *= -1; } } void target_lightramp_use( edict_t *self, edict_t *other, edict_t *activator ) { if( !self->enemy ) { edict_t *e; // check all the targets e = NULL; while( 1 ) { e = G_Find( e, FOFS(targetname), self->target ); if( !e ) break; if( Q_stricmp(e->classname, "light") ) { if( developer->integer ) { G_Printf( "%s at %s ", self->classname, vtos(self->s.origin) ); G_Printf( "target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin) ); } } else { self->enemy = e; } } if( !self->enemy ) { if( developer->integer ) G_Printf( "%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin) ); G_FreeEdict( self ); return; } } self->timestamp = level.time; target_lightramp_think( self ); } void SP_target_lightramp( edict_t *self ) { if( !self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1] ) { if( developer->integer ) G_Printf( "target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin) ); G_FreeEdict( self ); return; } /* if( deathmatch->value ) { G_FreeEdict( self ); return; } */ if( !self->target ) { if( developer->integer ) G_Printf( "%s with no target at %s\n", self->classname, vtos(self->s.origin) ); G_FreeEdict( self ); return; } self->r.svflags |= SVF_NOCLIENT; self->use = target_lightramp_use; self->think = target_lightramp_think; self->movedir[0] = self->message[0] - 'a'; self->movedir[1] = self->message[1] - 'a'; self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); } //========================================================== /*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) When triggered, this initiates a level-wide earthquake. All players and monsters are affected. "speed" severity of the quake (default:200) "count" duration of the quake (default:5) */ void target_earthquake_think (edict_t *self) { int i; edict_t *e; if (self->last_move_time < level.time) { G_Sound (self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE); self->last_move_time = level.time + 0.5; } for (i=1, e=game.edicts+i; i < game.numentities; i++,e++) { if (!e->r.inuse) continue; if (!e->r.client) continue; if (!e->groundentity) continue; e->groundentity = NULL; e->velocity[0] += crandom()* 150; e->velocity[1] += crandom()* 150; e->velocity[2] = self->speed * (100.0 / e->mass); } if (level.time < self->timestamp) self->nextthink = level.timemsec + game.framemsec; } void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) { self->timestamp = level.time + self->count; self->nextthink = level.timemsec + game.framemsec; self->activator = activator; self->last_move_time = 0; } void SP_target_earthquake (edict_t *self) { if (!self->targetname) if (developer->integer) G_Printf ("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); if (!self->count) self->count = 5; if (!self->speed) self->speed = 200; self->r.svflags |= SVF_NOCLIENT; self->think = target_earthquake_think; self->use = target_earthquake_use; self->noise_index = trap_SoundIndex ("sounds/world/quake.wav"); } //===================================================== /*QUAKED target_character (0 0 1) ? used with target_string (must be on same "team") "count" is position in the string (starts at 1) */ void SP_target_character (edict_t *self) { self->movetype = MOVETYPE_PUSH; trap_SetBrushModel (self, self->model); self->r.solid = SOLID_BSP; self->s.frame = 12; trap_LinkEntity (self); return; } //========================================================== /*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) */ void target_string_use( edict_t *self, edict_t *other, edict_t *activator ) { edict_t *e; int n, l; char c; l = strlen(self->message); for( e = self->teammaster; e; e = e->teamchain ) { if( !e->count ) continue; n = e->count - 1; if( n > l ) { e->s.frame = 12; continue; } c = self->message[n]; if( c >= '0' && c <= '9' ) e->s.frame = c - '0'; else if (c == '-') e->s.frame = 10; else if (c == ':') e->s.frame = 11; else e->s.frame = 12; } } void SP_target_string( edict_t *self ) { if( !self->message ) self->message = ""; self->use = target_string_use; } //========================================================== /*QUAKED target_position (0 .5 0) (-8 -8 -8) (8 8 8) Aiming target for entities like light, misc_portal_camera and trigger_push (jump pads) in particular. -------- KEYS -------- targetname : the entity that requires an aiming direction points to this. notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- NOTES -------- To make a jump pad, place this entity at the highest point of the jump and target it with a trigger_push entity.*/ void SP_target_position (edict_t *self) { self->r.svflags |= SVF_NOCLIENT; } /*QUAKED target_location (0 .5 0) (-8 -8 -8) (8 8 8) Location marker used by bots and players for team orders and team chat in the course of Teamplay games. The closest target_location in sight is used for the location. If none is in sight, the closest in distance is used. -------- KEYS -------- message : name of the location (text string). Displayed in parentheses in front of all team chat and order messages. count : color of the location text displayed in parentheses during team chat. Set to 0-7 for color. 0 : white (default) 1 : red 2 : green 3 : yellow 4 : blue 5 : cyan 6 : magenta 7 : white notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) */ void SP_target_location( edict_t *self ) { self->r.svflags |= SVF_NOCLIENT; if ( self->count ) { if ( self->count < 0 ) { self->count = 0; } else if ( self->count > 7 ) { self->count = 7; } } } /*QUAKED target_print (0 .5 0) (-8 -8 -8) (8 8 8) SAMETEAM OTHERTEAM PRIVATE This will print a message on the center of the screen when triggered. By default, all the clients will see the message. -------- KEYS -------- message : text string to print on screen. targetname : the activating trigger points to this. notsingle : when set to 1, entity will not spawn in Single Player mode notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo) -------- SPAWNFLAGS -------- SAMETEAM : &1 only players in activator's team will see the message. OTHERTEAM : &2 only players in other than activator's team will see the message. PRIVATE : &4 only the player that activates the target will see the message.*/ void SP_target_print_use( edict_t *self, edict_t *other, edict_t *activator ) { int n; edict_t *player; if( activator->r.client && (self->spawnflags & 4) ) { G_CenterPrintMsg( activator, self->message ); return; } // print to team if ( activator->r.client && self->spawnflags & 3 ) { edict_t *e; for( e = game.edicts + 1; PLAYERNUM(e) < game.maxclients; e++ ) { if( e->r.inuse && e->s.team ) { if( self->spawnflags & 1 && e->s.team == activator->s.team ) G_CenterPrintMsg( e, self->message ); if( self->spawnflags & 2 && e->s.team != activator->s.team ) G_CenterPrintMsg( e, self->message ); } } return; } for (n = 1; n <= game.maxclients; n++) { player = &game.edicts[n]; if (!player->r.inuse) continue; G_CenterPrintMsg( player, self->message ); } } void SP_target_print (edict_t *self) { if ( !self->message ) { G_FreeEdict ( self ); return; } self->use = SP_target_print_use; // do nothing } // JALFIXME: We have trigger_relay (and I already commented it should be a target), Q3 has // this target_relay. IMO we should do the move into target_relay too. //============================================================================= /*QUAKED target_relay (0 .7 .7) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM This can only be activated by other triggers which will cause it in turn to activate its own targets. -------- KEYS -------- targetname : activating trigger points to this. target : this points to entities to activate when this entity is triggered. notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes. notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. notsingle : when set to 1, entity will not spawn in Single Player mode (bot play mode). -------- SPAWNFLAGS -------- RED_ONLY : only red team players can activate trigger. BLUE_ONLY : only red team players can activate trigger. RANDOM : one one of the targeted entities will be triggered at random.*/ /************************************************************************/ /* RACES ENTITIES (For a minimal DEFRAG support) */ /************************************************************************/ //============================================================================= /*QUAKED target_startTimer (1 0 0) (-8 -8 -8) (8 8 8) Timer Start */ void target_starttimer_use( edict_t *self, edict_t *other, edict_t *activator ) { if(activator->r.client->resp.race_in_race==qfalse) { // dispatch start of race event to client G_Match_AutorecordCommand(qtrue,qfalse); } // restart the timer activator->r.client->resp.race_start_time=level.timemsec; activator->r.client->resp.race_in_race = qtrue; // checkpoint activator->r.client->resp.race_current_checkpoint=0; memset(activator->r.client->resp.race_checkpoint_used,qfalse,sizeof(qboolean)*MAX_RACE_CHECKPOINT); } void SP_target_starttimer( edict_t *self ) { if( game.gametype != GAMETYPE_RACE ) { G_FreeEdict( self ); return; } self->use = target_starttimer_use; } //============================================================================= extern void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mod); void target_stoptimer_respawn( edict_t *self) { // we should only kill the player if he did not already killed himself if(self->activator->deathtimestampactivator, self->activator, world, 100000, vec3_origin, MOD_SUICIDE ); trap_UnlinkEntity( self ); } /*QUAKED target_stopTimer (1 0 0) (-8 -8 -8) (8 8 8) Timer Stop -------- KEYS -------- target: stopTimer triggers its targets when a best time occurs. */ void target_stoptimer_use( edict_t *self, edict_t *other, edict_t *activator ) { float delta; unsigned int min,sec,milli; unsigned int dmin,dsec,dmilli; int type; edict_t *ent; if( activator->r.client->resp.race_in_race ) { activator->r.client->resp.race_last_lap_time=level.timemsec-activator->r.client->resp.race_start_time; // convert time into MM:SS:mm milli=activator->r.client->resp.race_last_lap_time/100.0f; min=milli/600; milli-=min*600; sec=milli/10; milli-=sec*10; // only one digit for deciseconds // in case of no record ? type=0; delta=fabs(activator->r.client->teamchange.race_best_lap_time-activator->r.client->resp.race_last_lap_time); if( (activator->r.client->teamchange.race_best_lap_time > activator->r.client->resp.race_last_lap_time) || (activator->r.client->teamchange.race_best_lap_time == 0.0) ) { // best personal delta=fabs(activator->r.client->teamchange.race_best_lap_time-activator->r.client->resp.race_last_lap_time); activator->r.client->teamchange.race_best_lap_time=activator->r.client->resp.race_last_lap_time; type=1; } activator->r.client->resp.race_in_race = qfalse; if( (game.besttime > activator->r.client->resp.race_last_lap_time) || (game.besttime == 0.0) ) { // best on server delta=fabs(game.besttime-activator->r.client->resp.race_last_lap_time); game.besttime = activator->r.client->resp.race_last_lap_time; type=2; } // extract SS:mm from delta dmilli=delta/100.0f; dmin=dmilli/600; dmilli-=dmin*600; dsec=dmilli/10; dmilli-=dsec*10; // only one digit for deci seconds switch(type) { case 0: // standard time G_CenterPrintMsg( activator, va("%sRace finished: %02d:%02d.%1d\nTry Again\n%s+%02d:%02d.%1d", S_COLOR_WHITE,min,sec,milli, S_COLOR_RED, dmin,dsec,dmilli)); break; case 1: // best personal lap G_CenterPrintMsg( activator, va("%sRace finished: %02d:%02d.%1d\nPersonal Record\n%s-%02d:%02d.%1d", S_COLOR_WHITE,min,sec,milli, S_COLOR_GREEN,dmin,dsec,dmilli)); break; case 2: // server best time G_CenterPrintMsg( activator, va("%sRace finished: %02d:%02d.%1d\nServer Record\n%s-%02d:%02d.%1d", S_COLOR_WHITE,min,sec,milli, S_COLOR_GREEN,dmin,dsec,dmilli)); break; } // dispatch end of race event to client G_Match_AutorecordCommand(qfalse,qfalse); // wsw : pb kill the player at the end of the race after 5 sec ent=G_Spawn( ); ent->nextthink= level.timemsec + 5000; ent->think=target_stoptimer_respawn; ent->activator=activator; // the player to kill trap_LinkEntity( ent ); } } void SP_target_stoptimer( edict_t *self ) { if( game.gametype != GAMETYPE_RACE ) { G_FreeEdict( self ); return; } self->use = target_stoptimer_use; } //============================================================================= /*QUAKED target_checkpoint (1 0 0) (-8 -8 -8) (8 8 8) Checkpoint */ void target_checkpoint_use( edict_t *self, edict_t *other, edict_t *activator ) { // player just pass a checkpoint unsigned int min,sec,milli; unsigned int dmin,dsec,dmilli; float time; float previous_time=0.0f; float delta; char color; char c; unsigned int current_checkpoint; if(activator->r.client->resp.race_in_race==qtrue) { current_checkpoint=activator->r.client->resp.race_current_checkpoint; time=level.timemsec-activator->r.client->resp.race_start_time; // store time in checkpoint structure if(current_checkpointr.client->resp.race_checkpoint_used[current_checkpoint]==qtrue) return; activator->r.client->resp.race_checkpoint_used[current_checkpoint]=qtrue; previous_time=activator->r.client->teamchange.race_checkpoint_time[current_checkpoint]; activator->r.client->teamchange.race_checkpoint_time[current_checkpoint]=time; activator->r.client->resp.race_current_checkpoint++; if(previous_time==0.0f) { // first time on this checkpoint color=COLOR_GREEN; c='-'; delta=time; } else if(timeuse = target_checkpoint_use; level.nbcheckpoint++; } //target_give wait classname weapon_xxx void target_give_use( edict_t *self, edict_t *other, edict_t *activator ) { edict_t *give; give=NULL; // more than one item can be given while((give=G_Find(give, FOFS(targetname), self->target))!=NULL) { // sanity if(!give->item) continue; if( !(give->item->flags & ITFLAG_PICKABLE) || (give->item->type & IT_FLAG) ) { continue; } G_PickupItem(give, activator); SetRespawn(give, 0.001f); // give it to player //Touch_Item(give,activator,NULL,0); //item=G_ItemForEntity( give ); } } void SP_target_give( edict_t *self ) { self->r.svflags |= SVF_NOCLIENT; self->use = target_give_use; }