Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_target.c (20523B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 #include "g_local.h"
     21 
     22 /*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
     23 Fire an origin based temp entity event to the clients.
     24 "style"		type byte
     25 */
     26 void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator)
     27 {
     28 	gi.WriteByte (svc_temp_entity);
     29 	gi.WriteByte (ent->style);
     30 	gi.WritePosition (ent->s.origin);
     31 	gi.multicast (ent->s.origin, MULTICAST_PVS);
     32 }
     33 
     34 void SP_target_temp_entity (edict_t *ent)
     35 {
     36 	ent->use = Use_Target_Tent;
     37 }
     38 
     39 
     40 //==========================================================
     41 
     42 //==========================================================
     43 
     44 /*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
     45 "noise"		wav file to play
     46 "attenuation"
     47 -1 = none, send to whole level
     48 1 = normal fighting sounds
     49 2 = idle sound level
     50 3 = ambient sound level
     51 "volume"	0.0 to 1.0
     52 
     53 Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.
     54 
     55 Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
     56 Multiple identical looping sounds will just increase volume without any speed cost.
     57 */
     58 void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
     59 {
     60 	int		chan;
     61 
     62 	if (ent->spawnflags & 3)
     63 	{	// looping sound toggles
     64 		if (ent->s.sound)
     65 			ent->s.sound = 0;	// turn it off
     66 		else
     67 			ent->s.sound = ent->noise_index;	// start it
     68 	}
     69 	else
     70 	{	// normal sound
     71 		if (ent->spawnflags & 4)
     72 			chan = CHAN_VOICE|CHAN_RELIABLE;
     73 		else
     74 			chan = CHAN_VOICE;
     75 		// use a positioned_sound, because this entity won't normally be
     76 		// sent to any clients because it is invisible
     77 		gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
     78 	}
     79 }
     80 
     81 void SP_target_speaker (edict_t *ent)
     82 {
     83 	char	buffer[MAX_QPATH];
     84 
     85 	if(!st.noise)
     86 	{
     87 		gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
     88 		return;
     89 	}
     90 	if (!strstr (st.noise, ".wav"))
     91 		Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
     92 	else
     93 		strncpy (buffer, st.noise, sizeof(buffer));
     94 	ent->noise_index = gi.soundindex (buffer);
     95 
     96 	if (!ent->volume)
     97 		ent->volume = 1.0;
     98 
     99 	if (!ent->attenuation)
    100 		ent->attenuation = 1.0;
    101 	else if (ent->attenuation == -1)	// use -1 so 0 defaults to 1
    102 		ent->attenuation = 0;
    103 
    104 	// check for prestarted looping sound
    105 	if (ent->spawnflags & 1)
    106 		ent->s.sound = ent->noise_index;
    107 
    108 	ent->use = Use_Target_Speaker;
    109 
    110 	// must link the entity so we get areas and clusters so
    111 	// the server can determine who to send updates to
    112 	gi.linkentity (ent);
    113 }
    114 
    115 
    116 //==========================================================
    117 
    118 void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
    119 {
    120 	if (ent->spawnflags & 1)
    121 		strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
    122 	else
    123 		strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
    124 
    125 	game.helpchanged++;
    126 }
    127 
    128 /*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
    129 When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
    130 */
    131 void SP_target_help(edict_t *ent)
    132 {
    133 	if (deathmatch->value)
    134 	{	// auto-remove for deathmatch
    135 		G_FreeEdict (ent);
    136 		return;
    137 	}
    138 
    139 	if (!ent->message)
    140 	{
    141 		gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
    142 		G_FreeEdict (ent);
    143 		return;
    144 	}
    145 	ent->use = Use_Target_Help;
    146 }
    147 
    148 //==========================================================
    149 
    150 /*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
    151 Counts a secret found.
    152 These are single use targets.
    153 */
    154 void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
    155 {
    156 	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
    157 
    158 	level.found_secrets++;
    159 
    160 	G_UseTargets (ent, activator);
    161 	G_FreeEdict (ent);
    162 }
    163 
    164 void SP_target_secret (edict_t *ent)
    165 {
    166 	if (deathmatch->value)
    167 	{	// auto-remove for deathmatch
    168 		G_FreeEdict (ent);
    169 		return;
    170 	}
    171 
    172 	ent->use = use_target_secret;
    173 	if (!st.noise)
    174 		st.noise = "misc/secret.wav";
    175 	ent->noise_index = gi.soundindex (st.noise);
    176 	ent->svflags = SVF_NOCLIENT;
    177 	level.total_secrets++;
    178 	// map bug hack
    179 	if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
    180 		ent->message = "You have found a secret area.";
    181 }
    182 
    183 //==========================================================
    184 
    185 /*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
    186 Counts a goal completed.
    187 These are single use targets.
    188 */
    189 void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
    190 {
    191 	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
    192 
    193 	level.found_goals++;
    194 
    195 	if (level.found_goals == level.total_goals)
    196 		gi.configstring (CS_CDTRACK, "0");
    197 
    198 	G_UseTargets (ent, activator);
    199 	G_FreeEdict (ent);
    200 }
    201 
    202 void SP_target_goal (edict_t *ent)
    203 {
    204 	if (deathmatch->value)
    205 	{	// auto-remove for deathmatch
    206 		G_FreeEdict (ent);
    207 		return;
    208 	}
    209 
    210 	ent->use = use_target_goal;
    211 	if (!st.noise)
    212 		st.noise = "misc/secret.wav";
    213 	ent->noise_index = gi.soundindex (st.noise);
    214 	ent->svflags = SVF_NOCLIENT;
    215 	level.total_goals++;
    216 }
    217 
    218 //==========================================================
    219 
    220 
    221 /*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
    222 Spawns an explosion temporary entity when used.
    223 
    224 "delay"		wait this long before going off
    225 "dmg"		how much radius damage should be done, defaults to 0
    226 */
    227 void target_explosion_explode (edict_t *self)
    228 {
    229 	float		save;
    230 
    231 	gi.WriteByte (svc_temp_entity);
    232 	gi.WriteByte (TE_EXPLOSION1);
    233 	gi.WritePosition (self->s.origin);
    234 	gi.multicast (self->s.origin, MULTICAST_PHS);
    235 
    236 	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
    237 
    238 	save = self->delay;
    239 	self->delay = 0;
    240 	G_UseTargets (self, self->activator);
    241 	self->delay = save;
    242 }
    243 
    244 void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
    245 {
    246 	self->activator = activator;
    247 
    248 	if (!self->delay)
    249 	{
    250 		target_explosion_explode (self);
    251 		return;
    252 	}
    253 
    254 	self->think = target_explosion_explode;
    255 	self->nextthink = level.time + self->delay;
    256 }
    257 
    258 void SP_target_explosion (edict_t *ent)
    259 {
    260 	ent->use = use_target_explosion;
    261 	ent->svflags = SVF_NOCLIENT;
    262 }
    263 
    264 
    265 //==========================================================
    266 
    267 /*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
    268 Changes level to "map" when fired
    269 */
    270 void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
    271 {
    272 	if (level.intermissiontime)
    273 		return;		// already activated
    274 
    275 	if (!deathmatch->value && !coop->value)
    276 	{
    277 		if (g_edicts[1].health <= 0)
    278 			return;
    279 	}
    280 
    281 	// if noexit, do a ton of damage to other
    282 	if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
    283 	{
    284 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
    285 		return;
    286 	}
    287 
    288 	// if multiplayer, let everyone know who hit the exit
    289 	if (deathmatch->value)
    290 	{
    291 		if (activator && activator->client)
    292 			gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
    293 	}
    294 
    295 	// if going to a new unit, clear cross triggers
    296 	if (strstr(self->map, "*"))	
    297 		game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
    298 
    299 	BeginIntermission (self);
    300 }
    301 
    302 void SP_target_changelevel (edict_t *ent)
    303 {
    304 	if (!ent->map)
    305 	{
    306 		gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
    307 		G_FreeEdict (ent);
    308 		return;
    309 	}
    310 
    311 	// ugly hack because *SOMEBODY* screwed up their map
    312    if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
    313 	   ent->map = "fact3$secret1";
    314 
    315 	ent->use = use_target_changelevel;
    316 	ent->svflags = SVF_NOCLIENT;
    317 }
    318 
    319 
    320 //==========================================================
    321 
    322 /*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
    323 Creates a particle splash effect when used.
    324 
    325 Set "sounds" to one of the following:
    326   1) sparks
    327   2) blue water
    328   3) brown water
    329   4) slime
    330   5) lava
    331   6) blood
    332 
    333 "count"	how many pixels in the splash
    334 "dmg"	if set, does a radius damage at this location when it splashes
    335 		useful for lava/sparks
    336 */
    337 
    338 void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
    339 {
    340 	gi.WriteByte (svc_temp_entity);
    341 	gi.WriteByte (TE_SPLASH);
    342 	gi.WriteByte (self->count);
    343 	gi.WritePosition (self->s.origin);
    344 	gi.WriteDir (self->movedir);
    345 	gi.WriteByte (self->sounds);
    346 	gi.multicast (self->s.origin, MULTICAST_PVS);
    347 
    348 	if (self->dmg)
    349 		T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
    350 }
    351 
    352 void SP_target_splash (edict_t *self)
    353 {
    354 	self->use = use_target_splash;
    355 	G_SetMovedir (self->s.angles, self->movedir);
    356 
    357 	if (!self->count)
    358 		self->count = 32;
    359 
    360 	self->svflags = SVF_NOCLIENT;
    361 }
    362 
    363 
    364 //==========================================================
    365 
    366 /*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
    367 Set target to the type of entity you want spawned.
    368 Useful for spawning monsters and gibs in the factory levels.
    369 
    370 For monsters:
    371 	Set direction to the facing you want it to have.
    372 
    373 For gibs:
    374 	Set direction if you want it moving and
    375 	speed how fast it should be moving otherwise it
    376 	will just be dropped
    377 */
    378 void ED_CallSpawn (edict_t *ent);
    379 
    380 void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
    381 {
    382 	edict_t	*ent;
    383 
    384 	ent = G_Spawn();
    385 	ent->classname = self->target;
    386 	VectorCopy (self->s.origin, ent->s.origin);
    387 	VectorCopy (self->s.angles, ent->s.angles);
    388 	ED_CallSpawn (ent);
    389 	gi.unlinkentity (ent);
    390 	KillBox (ent);
    391 	gi.linkentity (ent);
    392 	if (self->speed)
    393 		VectorCopy (self->movedir, ent->velocity);
    394 }
    395 
    396 void SP_target_spawner (edict_t *self)
    397 {
    398 	self->use = use_target_spawner;
    399 	self->svflags = SVF_NOCLIENT;
    400 	if (self->speed)
    401 	{
    402 		G_SetMovedir (self->s.angles, self->movedir);
    403 		VectorScale (self->movedir, self->speed, self->movedir);
    404 	}
    405 }
    406 
    407 //==========================================================
    408 
    409 /*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
    410 Fires a blaster bolt in the set direction when triggered.
    411 
    412 dmg		default is 15
    413 speed	default is 1000
    414 */
    415 
    416 void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
    417 {
    418 	int effect;
    419 
    420 	if (self->spawnflags & 2)
    421 		effect = 0;
    422 	else if (self->spawnflags & 1)
    423 		effect = EF_HYPERBLASTER;
    424 	else
    425 		effect = EF_BLASTER;
    426 
    427 	fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
    428 	gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
    429 }
    430 
    431 void SP_target_blaster (edict_t *self)
    432 {
    433 	self->use = use_target_blaster;
    434 	G_SetMovedir (self->s.angles, self->movedir);
    435 	self->noise_index = gi.soundindex ("weapons/laser2.wav");
    436 
    437 	if (!self->dmg)
    438 		self->dmg = 15;
    439 	if (!self->speed)
    440 		self->speed = 1000;
    441 
    442 	self->svflags = SVF_NOCLIENT;
    443 }
    444 
    445 
    446 //==========================================================
    447 
    448 /*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
    449 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.
    450 */
    451 void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
    452 {
    453 	game.serverflags |= self->spawnflags;
    454 	G_FreeEdict (self);
    455 }
    456 
    457 void SP_target_crosslevel_trigger (edict_t *self)
    458 {
    459 	self->svflags = SVF_NOCLIENT;
    460 	self->use = trigger_crosslevel_trigger_use;
    461 }
    462 
    463 /*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
    464 Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
    465 killtarget also work.
    466 
    467 "delay"		delay before using targets if the trigger has been activated (default 1)
    468 */
    469 void target_crosslevel_target_think (edict_t *self)
    470 {
    471 	if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
    472 	{
    473 		G_UseTargets (self, self);
    474 		G_FreeEdict (self);
    475 	}
    476 }
    477 
    478 void SP_target_crosslevel_target (edict_t *self)
    479 {
    480 	if (! self->delay)
    481 		self->delay = 1;
    482 	self->svflags = SVF_NOCLIENT;
    483 
    484 	self->think = target_crosslevel_target_think;
    485 	self->nextthink = level.time + self->delay;
    486 }
    487 
    488 //==========================================================
    489 
    490 /*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
    491 When triggered, fires a laser.  You can either set a target
    492 or a direction.
    493 */
    494 
    495 void target_laser_think (edict_t *self)
    496 {
    497 	edict_t	*ignore;
    498 	vec3_t	start;
    499 	vec3_t	end;
    500 	trace_t	tr;
    501 	vec3_t	point;
    502 	vec3_t	last_movedir;
    503 	int		count;
    504 
    505 	if (self->spawnflags & 0x80000000)
    506 		count = 8;
    507 	else
    508 		count = 4;
    509 
    510 	if (self->enemy)
    511 	{
    512 		VectorCopy (self->movedir, last_movedir);
    513 		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
    514 		VectorSubtract (point, self->s.origin, self->movedir);
    515 		VectorNormalize (self->movedir);
    516 		if (!VectorCompare(self->movedir, last_movedir))
    517 			self->spawnflags |= 0x80000000;
    518 	}
    519 
    520 	ignore = self;
    521 	VectorCopy (self->s.origin, start);
    522 	VectorMA (start, 2048, self->movedir, end);
    523 	while(1)
    524 	{
    525 		tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
    526 
    527 		if (!tr.ent)
    528 			break;
    529 
    530 		// hurt it if we can
    531 		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
    532 			T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
    533 
    534 		// if we hit something that's not a monster or player or is immune to lasers, we're done
    535 		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
    536 		{
    537 			if (self->spawnflags & 0x80000000)
    538 			{
    539 				self->spawnflags &= ~0x80000000;
    540 				gi.WriteByte (svc_temp_entity);
    541 				gi.WriteByte (TE_LASER_SPARKS);
    542 				gi.WriteByte (count);
    543 				gi.WritePosition (tr.endpos);
    544 				gi.WriteDir (tr.plane.normal);
    545 				gi.WriteByte (self->s.skinnum);
    546 				gi.multicast (tr.endpos, MULTICAST_PVS);
    547 			}
    548 			break;
    549 		}
    550 
    551 		ignore = tr.ent;
    552 		VectorCopy (tr.endpos, start);
    553 	}
    554 
    555 	VectorCopy (tr.endpos, self->s.old_origin);
    556 
    557 	self->nextthink = level.time + FRAMETIME;
    558 }
    559 
    560 void target_laser_on (edict_t *self)
    561 {
    562 	if (!self->activator)
    563 		self->activator = self;
    564 	self->spawnflags |= 0x80000001;
    565 	self->svflags &= ~SVF_NOCLIENT;
    566 	target_laser_think (self);
    567 }
    568 
    569 void target_laser_off (edict_t *self)
    570 {
    571 	self->spawnflags &= ~1;
    572 	self->svflags |= SVF_NOCLIENT;
    573 	self->nextthink = 0;
    574 }
    575 
    576 void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
    577 {
    578 	self->activator = activator;
    579 	if (self->spawnflags & 1)
    580 		target_laser_off (self);
    581 	else
    582 		target_laser_on (self);
    583 }
    584 
    585 void target_laser_start (edict_t *self)
    586 {
    587 	edict_t *ent;
    588 
    589 	self->movetype = MOVETYPE_NONE;
    590 	self->solid = SOLID_NOT;
    591 	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
    592 	self->s.modelindex = 1;			// must be non-zero
    593 
    594 	// set the beam diameter
    595 	if (self->spawnflags & 64)
    596 		self->s.frame = 16;
    597 	else
    598 		self->s.frame = 4;
    599 
    600 	// set the color
    601 	if (self->spawnflags & 2)
    602 		self->s.skinnum = 0xf2f2f0f0;
    603 	else if (self->spawnflags & 4)
    604 		self->s.skinnum = 0xd0d1d2d3;
    605 	else if (self->spawnflags & 8)
    606 		self->s.skinnum = 0xf3f3f1f1;
    607 	else if (self->spawnflags & 16)
    608 		self->s.skinnum = 0xdcdddedf;
    609 	else if (self->spawnflags & 32)
    610 		self->s.skinnum = 0xe0e1e2e3;
    611 
    612 	if (!self->enemy)
    613 	{
    614 		if (self->target)
    615 		{
    616 			ent = G_Find (NULL, FOFS(targetname), self->target);
    617 			if (!ent)
    618 				gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
    619 			self->enemy = ent;
    620 		}
    621 		else
    622 		{
    623 			G_SetMovedir (self->s.angles, self->movedir);
    624 		}
    625 	}
    626 	self->use = target_laser_use;
    627 	self->think = target_laser_think;
    628 
    629 	if (!self->dmg)
    630 		self->dmg = 1;
    631 
    632 	VectorSet (self->mins, -8, -8, -8);
    633 	VectorSet (self->maxs, 8, 8, 8);
    634 	gi.linkentity (self);
    635 
    636 	if (self->spawnflags & 1)
    637 		target_laser_on (self);
    638 	else
    639 		target_laser_off (self);
    640 }
    641 
    642 void SP_target_laser (edict_t *self)
    643 {
    644 	// let everything else get spawned before we start firing
    645 	self->think = target_laser_start;
    646 	self->nextthink = level.time + 1;
    647 }
    648 
    649 //==========================================================
    650 
    651 /*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
    652 speed		How many seconds the ramping will take
    653 message		two letters; starting lightlevel and ending lightlevel
    654 */
    655 
    656 void target_lightramp_think (edict_t *self)
    657 {
    658 	char	style[2];
    659 
    660 	style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
    661 	style[1] = 0;
    662 	gi.configstring (CS_LIGHTS+self->enemy->style, style);
    663 
    664 	if ((level.time - self->timestamp) < self->speed)
    665 	{
    666 		self->nextthink = level.time + FRAMETIME;
    667 	}
    668 	else if (self->spawnflags & 1)
    669 	{
    670 		char	temp;
    671 
    672 		temp = self->movedir[0];
    673 		self->movedir[0] = self->movedir[1];
    674 		self->movedir[1] = temp;
    675 		self->movedir[2] *= -1;
    676 	}
    677 }
    678 
    679 void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
    680 {
    681 	if (!self->enemy)
    682 	{
    683 		edict_t		*e;
    684 
    685 		// check all the targets
    686 		e = NULL;
    687 		while (1)
    688 		{
    689 			e = G_Find (e, FOFS(targetname), self->target);
    690 			if (!e)
    691 				break;
    692 			if (strcmp(e->classname, "light") != 0)
    693 			{
    694 				gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
    695 				gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
    696 			}
    697 			else
    698 			{
    699 				self->enemy = e;
    700 			}
    701 		}
    702 
    703 		if (!self->enemy)
    704 		{
    705 			gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
    706 			G_FreeEdict (self);
    707 			return;
    708 		}
    709 	}
    710 
    711 	self->timestamp = level.time;
    712 	target_lightramp_think (self);
    713 }
    714 
    715 void SP_target_lightramp (edict_t *self)
    716 {
    717 	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])
    718 	{
    719 		gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
    720 		G_FreeEdict (self);
    721 		return;
    722 	}
    723 
    724 	if (deathmatch->value)
    725 	{
    726 		G_FreeEdict (self);
    727 		return;
    728 	}
    729 
    730 	if (!self->target)
    731 	{
    732 		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
    733 		G_FreeEdict (self);
    734 		return;
    735 	}
    736 
    737 	self->svflags |= SVF_NOCLIENT;
    738 	self->use = target_lightramp_use;
    739 	self->think = target_lightramp_think;
    740 
    741 	self->movedir[0] = self->message[0] - 'a';
    742 	self->movedir[1] = self->message[1] - 'a';
    743 	self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
    744 }
    745 
    746 //==========================================================
    747 
    748 /*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
    749 When triggered, this initiates a level-wide earthquake.
    750 All players and monsters are affected.
    751 "speed"		severity of the quake (default:200)
    752 "count"		duration of the quake (default:5)
    753 */
    754 
    755 void target_earthquake_think (edict_t *self)
    756 {
    757 	int		i;
    758 	edict_t	*e;
    759 
    760 	if (self->last_move_time < level.time)
    761 	{
    762 		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
    763 		self->last_move_time = level.time + 0.5;
    764 	}
    765 
    766 	for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
    767 	{
    768 		if (!e->inuse)
    769 			continue;
    770 		if (!e->client)
    771 			continue;
    772 		if (!e->groundentity)
    773 			continue;
    774 
    775 		e->groundentity = NULL;
    776 		e->velocity[0] += crandom()* 150;
    777 		e->velocity[1] += crandom()* 150;
    778 		e->velocity[2] = self->speed * (100.0 / e->mass);
    779 	}
    780 
    781 	if (level.time < self->timestamp)
    782 		self->nextthink = level.time + FRAMETIME;
    783 }
    784 
    785 void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
    786 {
    787 	self->timestamp = level.time + self->count;
    788 	self->nextthink = level.time + FRAMETIME;
    789 	self->activator = activator;
    790 	self->last_move_time = 0;
    791 }
    792 
    793 void SP_target_earthquake (edict_t *self)
    794 {
    795 	if (!self->targetname)
    796 		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
    797 
    798 	if (!self->count)
    799 		self->count = 5;
    800 
    801 	if (!self->speed)
    802 		self->speed = 200;
    803 
    804 	self->svflags |= SVF_NOCLIENT;
    805 	self->think = target_earthquake_think;
    806 	self->use = target_earthquake_use;
    807 
    808 	self->noise_index = gi.soundindex ("world/quake.wav");
    809 }