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