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