1 /*
2  * Copyright (C) 1997-2001 Id Software, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * 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 along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59
17  * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  *
19  */
20 /* g_misc.c */
21 
22 #include "g_local.h"
23 
24 
25 /*
26  * QUAKED func_group (0 0 0) ? Used to group brushes together just for editor
27  * convenience.
28  */
29 
30 /* ===================================================== */
31 
32 void
Use_Areaportal(edict_t * ent,edict_t * other,edict_t * activator)33 Use_Areaportal(edict_t * ent, edict_t * other, edict_t * activator)
34 {
35 	ent->count ^= 1;	/* toggle state */
36 	/* gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); */
37 	gi.SetAreaPortalState(ent->style, ent->count);
38 }
39 
40 /*
41  * QUAKED func_areaportal (0 0 0) ?
42  *
43  * This is a non-visible object that divides the world into areas that are
44  * seperated when this portal is not activated. Usually enclosed in the
45  * middle of a door.
46  */
47 void
SP_func_areaportal(edict_t * ent)48 SP_func_areaportal(edict_t * ent)
49 {
50 	ent->use = Use_Areaportal;
51 	ent->count = 0;		/* always start closed; */
52 }
53 
54 /* ===================================================== */
55 
56 
57 /*
58  * ================= Misc functions =================
59  */
60 void
VelocityForDamage(int damage,vec3_t v)61 VelocityForDamage(int damage, vec3_t v)
62 {
63 	v[0] = 100.0 * crandom();
64 	v[1] = 100.0 * crandom();
65 	v[2] = 200.0 + 100.0 * random();
66 
67 	if (damage < 50)
68 		VectorScale(v, 0.7, v);
69 	else
70 		VectorScale(v, 1.2, v);
71 }
72 
73 void
ClipGibVelocity(edict_t * ent)74 ClipGibVelocity(edict_t * ent)
75 {
76 	if (ent->velocity[0] < -300)
77 		ent->velocity[0] = -300;
78 	else if (ent->velocity[0] > 300)
79 		ent->velocity[0] = 300;
80 	if (ent->velocity[1] < -300)
81 		ent->velocity[1] = -300;
82 	else if (ent->velocity[1] > 300)
83 		ent->velocity[1] = 300;
84 	if (ent->velocity[2] < 200)
85 		ent->velocity[2] = 200;	/* always some upwards */
86 	else if (ent->velocity[2] > 500)
87 		ent->velocity[2] = 500;
88 }
89 
90 
91 /*
92  * ================= gibs =================
93  */
94 void
gib_think(edict_t * self)95 gib_think(edict_t * self)
96 {
97 	self->s.frame++;
98 	self->nextthink = level.time + FRAMETIME;
99 
100 	if (self->s.frame == 10) {
101 		self->think = G_FreeEdict;
102 		self->nextthink = level.time + 8 + random() * 10;
103 	}
104 }
105 
106 void
gib_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)107 gib_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
108 {
109 	vec3_t		normal_angles, right;
110 
111 	if (!self->groundentity)
112 		return;
113 
114 	self->touch = NULL;
115 
116 	if (plane) {
117 		gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0);
118 
119 		vectoangles(plane->normal, normal_angles);
120 		AngleVectors(normal_angles, NULL, right, NULL);
121 		vectoangles(right, self->s.angles);
122 
123 		if (self->s.modelindex == sm_meat_index) {
124 			self->s.frame++;
125 			self->think = gib_think;
126 			self->nextthink = level.time + FRAMETIME;
127 		}
128 	}
129 }
130 
131 void
gib_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)132 gib_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
133 {
134 	G_FreeEdict(self);
135 }
136 
137 void
ThrowGib(edict_t * self,char * gibname,int damage,int type)138 ThrowGib(edict_t * self, char *gibname, int damage, int type)
139 {
140 	edict_t        *gib;
141 	vec3_t		vd;
142 	vec3_t		origin;
143 	vec3_t		size;
144 	float		vscale;
145 
146 	gib = G_Spawn();
147 
148 	VectorScale(self->size, 0.5, size);
149 	VectorAdd(self->absmin, size, origin);
150 	gib->s.origin[0] = origin[0] + crandom() * size[0];
151 	gib->s.origin[1] = origin[1] + crandom() * size[1];
152 	gib->s.origin[2] = origin[2] + crandom() * size[2];
153 
154 	gi.setmodel(gib, gibname);
155 	gib->solid = SOLID_NOT;
156 	gib->s.effects |= EF_GIB;
157 	gib->flags |= FL_NO_KNOCKBACK;
158 	gib->takedamage = DAMAGE_YES;
159 	gib->die = gib_die;
160 
161 	if (type == GIB_ORGANIC) {
162 		gib->movetype = MOVETYPE_TOSS;
163 		gib->touch = gib_touch;
164 		vscale = 0.5;
165 	} else {
166 		gib->movetype = MOVETYPE_BOUNCE;
167 		vscale = 1.0;
168 	}
169 
170 	VelocityForDamage(damage, vd);
171 	VectorMA(self->velocity, vscale, vd, gib->velocity);
172 	ClipGibVelocity(gib);
173 	gib->avelocity[0] = random() * 600;
174 	gib->avelocity[1] = random() * 600;
175 	gib->avelocity[2] = random() * 600;
176 
177 	gib->think = G_FreeEdict;
178 	gib->nextthink = level.time + 10 + random() * 10;
179 
180 	gi.linkentity(gib);
181 }
182 
183 void
ThrowHead(edict_t * self,char * gibname,int damage,int type)184 ThrowHead(edict_t * self, char *gibname, int damage, int type)
185 {
186 	vec3_t		vd;
187 	float		vscale;
188 
189 	self->s.skinnum = 0;
190 	self->s.frame = 0;
191 	VectorClear(self->mins);
192 	VectorClear(self->maxs);
193 
194 	self->s.modelindex2 = 0;
195 	gi.setmodel(self, gibname);
196 	self->solid = SOLID_NOT;
197 	self->s.effects |= EF_GIB;
198 	self->s.effects &= ~EF_FLIES;
199 	self->s.sound = 0;
200 	self->flags |= FL_NO_KNOCKBACK;
201 	self->svflags &= ~SVF_MONSTER;
202 	self->takedamage = DAMAGE_YES;
203 	self->die = gib_die;
204 
205 	if (type == GIB_ORGANIC) {
206 		self->movetype = MOVETYPE_TOSS;
207 		self->touch = gib_touch;
208 		vscale = 0.5;
209 	} else {
210 		self->movetype = MOVETYPE_BOUNCE;
211 		vscale = 1.0;
212 	}
213 
214 	VelocityForDamage(damage, vd);
215 	VectorMA(self->velocity, vscale, vd, self->velocity);
216 	ClipGibVelocity(self);
217 
218 	self->avelocity[YAW] = crandom() * 600;
219 
220 	self->think = G_FreeEdict;
221 	self->nextthink = level.time + 10 + random() * 10;
222 
223 	gi.linkentity(self);
224 }
225 
226 
227 void
ThrowClientHead(edict_t * self,int damage)228 ThrowClientHead(edict_t * self, int damage)
229 {
230 	vec3_t		vd;
231 	char           *gibname;
232 
233 	if (rand() & 1) {
234 		gibname = "models/objects/gibs/head2/tris.md2";
235 		self->s.skinnum = 1;	/* second skin is player */
236 	} else {
237 		gibname = "models/objects/gibs/skull/tris.md2";
238 		self->s.skinnum = 0;
239 	}
240 
241 	self->s.origin[2] += 32;
242 	self->s.frame = 0;
243 	gi.setmodel(self, gibname);
244 	VectorSet(self->mins, -16, -16, 0);
245 	VectorSet(self->maxs, 16, 16, 16);
246 
247 	self->takedamage = DAMAGE_NO;
248 	self->solid = SOLID_NOT;
249 	self->s.effects = EF_GIB;
250 	self->s.sound = 0;
251 	self->flags |= FL_NO_KNOCKBACK;
252 
253 	self->movetype = MOVETYPE_BOUNCE;
254 	VelocityForDamage(damage, vd);
255 	VectorAdd(self->velocity, vd, self->velocity);
256 
257 	if (self->client) {	/* bodies in the queue don't have a client
258 				 * anymore */
259 		self->client->anim_priority = ANIM_DEATH;
260 		self->client->anim_end = self->s.frame;
261 	} else {
262 		self->think = NULL;
263 		self->nextthink = 0;
264 	}
265 
266 #ifdef GAME_MOD
267 	Blinky_OnClientTerminate(self);
268 #endif
269 	gi.linkentity(self);
270 }
271 
272 
273 /*
274  * ================= debris =================
275  */
276 void
debris_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)277 debris_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
278 {
279 	G_FreeEdict(self);
280 }
281 
282 void
ThrowDebris(edict_t * self,char * modelname,float speed,vec3_t origin)283 ThrowDebris(edict_t * self, char *modelname, float speed, vec3_t origin)
284 {
285 	edict_t        *chunk;
286 	vec3_t		v;
287 
288 	chunk = G_Spawn();
289 	VectorCopy(origin, chunk->s.origin);
290 	gi.setmodel(chunk, modelname);
291 	v[0] = 100 * crandom();
292 	v[1] = 100 * crandom();
293 	v[2] = 100 + 100 * crandom();
294 	VectorMA(self->velocity, speed, v, chunk->velocity);
295 	chunk->movetype = MOVETYPE_BOUNCE;
296 	chunk->solid = SOLID_NOT;
297 	chunk->avelocity[0] = random() * 600;
298 	chunk->avelocity[1] = random() * 600;
299 	chunk->avelocity[2] = random() * 600;
300 	chunk->think = G_FreeEdict;
301 	chunk->nextthink = level.time + 5 + random() * 5;
302 	chunk->s.frame = 0;
303 	chunk->flags = 0;
304 	chunk->classname = "debris";
305 	chunk->takedamage = DAMAGE_YES;
306 	chunk->die = debris_die;
307 	gi.linkentity(chunk);
308 }
309 
310 
311 void
BecomeExplosion1(edict_t * self)312 BecomeExplosion1(edict_t * self)
313 {
314 	gi.WriteByte(svc_temp_entity);
315 	gi.WriteByte(TE_EXPLOSION1);
316 	gi.WritePosition(self->s.origin);
317 	gi.multicast(self->s.origin, MULTICAST_PVS);
318 
319 	G_FreeEdict(self);
320 }
321 
322 
323 void
BecomeExplosion2(edict_t * self)324 BecomeExplosion2(edict_t * self)
325 {
326 	gi.WriteByte(svc_temp_entity);
327 	gi.WriteByte(TE_EXPLOSION2);
328 	gi.WritePosition(self->s.origin);
329 	gi.multicast(self->s.origin, MULTICAST_PVS);
330 
331 	G_FreeEdict(self);
332 }
333 
334 
335 /*
336  * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT Target: next path
337  * corner Pathtarget: gets used when an entity that has this path_corner
338  * targeted touches it
339  */
340 
341 void
path_corner_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)342 path_corner_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
343 {
344 	vec3_t		v;
345 	edict_t        *next;
346 
347 	if (other->movetarget != self)
348 		return;
349 
350 	if (other->enemy)
351 		return;
352 
353 	if (self->pathtarget) {
354 		char           *savetarget;
355 
356 		savetarget = self->target;
357 		self->target = self->pathtarget;
358 		G_UseTargets(self, other);
359 		self->target = savetarget;
360 	}
361 	if (self->target)
362 		next = G_PickTarget(self->target);
363 	else
364 		next = NULL;
365 
366 	if ((next) && (next->spawnflags & 1)) {
367 		VectorCopy(next->s.origin, v);
368 		v[2] += next->mins[2];
369 		v[2] -= other->mins[2];
370 		VectorCopy(v, other->s.origin);
371 		next = G_PickTarget(next->target);
372 		other->s.event = EV_OTHER_TELEPORT;
373 	}
374 	other->goalentity = other->movetarget = next;
375 
376 	if (self->wait) {
377 		other->monsterinfo.pausetime = level.time + self->wait;
378 		other->monsterinfo.stand(other);
379 		return;
380 	}
381 	if (!other->movetarget) {
382 		other->monsterinfo.pausetime = level.time + 100000000;
383 		other->monsterinfo.stand(other);
384 	} else {
385 		VectorSubtract(other->goalentity->s.origin, other->s.origin, v);
386 		other->ideal_yaw = vectoyaw(v);
387 	}
388 }
389 
390 void
SP_path_corner(edict_t * self)391 SP_path_corner(edict_t * self)
392 {
393 	if (!self->targetname) {
394 		gi.dprintf("path_corner with no targetname at %s\n", vtos(self->s.origin));
395 		G_FreeEdict(self);
396 		return;
397 	}
398 	self->solid = SOLID_TRIGGER;
399 	self->touch = path_corner_touch;
400 	VectorSet(self->mins, -8, -8, -8);
401 	VectorSet(self->maxs, 8, 8, 8);
402 	self->svflags |= SVF_NOCLIENT;
403 	gi.linkentity(self);
404 }
405 
406 
407 /*
408  * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold Makes this the
409  * target of a monster and it will head here when first activated before
410  * going after the activator.  If hold is selected, it will stay here.
411  */
412 void
point_combat_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)413 point_combat_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
414 {
415 	edict_t        *activator;
416 
417 	if (other->movetarget != self)
418 		return;
419 
420 	if (self->target) {
421 		other->target = self->target;
422 		other->goalentity = other->movetarget = G_PickTarget(other->target);
423 		if (!other->goalentity) {
424 			gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
425 			other->movetarget = self;
426 		}
427 		self->target = NULL;
428 	} else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM | FL_FLY))) {
429 		other->monsterinfo.pausetime = level.time + 100000000;
430 		other->monsterinfo.aiflags |= AI_STAND_GROUND;
431 		other->monsterinfo.stand(other);
432 	}
433 	if (other->movetarget == self) {
434 		other->target = NULL;
435 		other->movetarget = NULL;
436 		other->goalentity = other->enemy;
437 		other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
438 	}
439 	if (self->pathtarget) {
440 		char           *savetarget;
441 
442 		savetarget = self->target;
443 		self->target = self->pathtarget;
444 		if (other->enemy && other->enemy->client)
445 			activator = other->enemy;
446 		else if (other->oldenemy && other->oldenemy->client)
447 			activator = other->oldenemy;
448 		else if (other->activator && other->activator->client)
449 			activator = other->activator;
450 		else
451 			activator = other;
452 		G_UseTargets(self, activator);
453 		self->target = savetarget;
454 	}
455 }
456 
457 void
SP_point_combat(edict_t * self)458 SP_point_combat(edict_t * self)
459 {
460 	if (deathmatch->value) {
461 		G_FreeEdict(self);
462 		return;
463 	}
464 	self->solid = SOLID_TRIGGER;
465 	self->touch = point_combat_touch;
466 	VectorSet(self->mins, -8, -8, -16);
467 	VectorSet(self->maxs, 8, 8, 16);
468 	self->svflags = SVF_NOCLIENT;
469 	gi.linkentity(self);
470 }
471 
472 
473 /*
474  * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) Just for the debugging
475  * level.  Don't use
476  */
477 void
TH_viewthing(edict_t * ent)478 TH_viewthing(edict_t * ent)
479 {
480 	ent->s.frame = (ent->s.frame + 1) % 7;
481 	ent->nextthink = level.time + FRAMETIME;
482 }
483 
484 void
SP_viewthing(edict_t * ent)485 SP_viewthing(edict_t * ent)
486 {
487 	gi.dprintf("viewthing spawned\n");
488 
489 	ent->movetype = MOVETYPE_NONE;
490 	ent->solid = SOLID_BBOX;
491 	ent->s.renderfx = RF_FRAMELERP;
492 	VectorSet(ent->mins, -16, -16, -24);
493 	VectorSet(ent->maxs, 16, 16, 32);
494 	ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
495 	gi.linkentity(ent);
496 	ent->nextthink = level.time + 0.5;
497 	ent->think = TH_viewthing;
498 	return;
499 }
500 
501 
502 /*
503  * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target
504  * for spotlights, etc.
505  */
506 void
SP_info_null(edict_t * self)507 SP_info_null(edict_t * self)
508 {
509 	G_FreeEdict(self);
510 }
511 
512 
513 /*
514  * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional
515  * target for lightning.
516  */
517 void
SP_info_notnull(edict_t * self)518 SP_info_notnull(edict_t * self)
519 {
520 	VectorCopy(self->s.origin, self->absmin);
521 	VectorCopy(self->s.origin, self->absmax);
522 }
523 
524 
525 /*
526  * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF Non-displayed light.
527  * Default light value is 300. Default style is 0. If targeted, will toggle
528  * between on and off. Default _cone value is 10 (used to set size of light
529  * for spotlights)
530  */
531 
532 #define START_OFF	1
533 
534 static void
light_use(edict_t * self,edict_t * other,edict_t * activator)535 light_use(edict_t * self, edict_t * other, edict_t * activator)
536 {
537 	if (self->spawnflags & START_OFF) {
538 		gi.configstring(CS_LIGHTS + self->style, "m");
539 		self->spawnflags &= ~START_OFF;
540 	} else {
541 		gi.configstring(CS_LIGHTS + self->style, "a");
542 		self->spawnflags |= START_OFF;
543 	}
544 }
545 
546 void
SP_light(edict_t * self)547 SP_light(edict_t * self)
548 {
549 
550 	/*
551 	 * no targeted lights in deathmatch, because they cause global
552 	 * messages
553 	 */
554 	if (!self->targetname || deathmatch->value) {
555 		G_FreeEdict(self);
556 		return;
557 	}
558 	if (self->style >= 32) {
559 		self->use = light_use;
560 		if (self->spawnflags & START_OFF)
561 			gi.configstring(CS_LIGHTS + self->style, "a");
562 		else
563 			gi.configstring(CS_LIGHTS + self->style, "m");
564 	}
565 }
566 
567 /*
568  * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED
569  * ANIMATED_FAST This is just a solid wall if not inhibited
570  *
571  * TRIGGER_SPAWN	the wall will not be present until triggered it will then
572  * blink in to existance; it will kill anything that was in it's way
573  *
574  * TOGGLE			only valid for TRIGGER_SPAWN walls this allows the
575  * wall to be turned on and off
576  *
577  * START_ON		only valid for TRIGGER_SPAWN walls the wall will
578  * initially be present
579  */
580 
581 void
func_wall_use(edict_t * self,edict_t * other,edict_t * activator)582 func_wall_use(edict_t * self, edict_t * other, edict_t * activator)
583 {
584 	if (self->solid == SOLID_NOT) {
585 		self->solid = SOLID_BSP;
586 		self->svflags &= ~SVF_NOCLIENT;
587 		KillBox(self);
588 	} else {
589 		self->solid = SOLID_NOT;
590 		self->svflags |= SVF_NOCLIENT;
591 	}
592 	gi.linkentity(self);
593 
594 	if (!(self->spawnflags & 2))
595 		self->use = NULL;
596 }
597 
598 void
SP_func_wall(edict_t * self)599 SP_func_wall(edict_t * self)
600 {
601 	self->movetype = MOVETYPE_PUSH;
602 	gi.setmodel(self, self->model);
603 
604 	if (self->spawnflags & 8)
605 		self->s.effects |= EF_ANIM_ALL;
606 	if (self->spawnflags & 16)
607 		self->s.effects |= EF_ANIM_ALLFAST;
608 
609 	/* just a wall */
610 	if ((self->spawnflags & 7) == 0) {
611 		self->solid = SOLID_BSP;
612 		gi.linkentity(self);
613 		return;
614 	}
615 	/* it must be TRIGGER_SPAWN */
616 	if (!(self->spawnflags & 1)) {
617 		/* gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); */
618 		self->spawnflags |= 1;
619 	}
620 	/* yell if the spawnflags are odd */
621 	if (self->spawnflags & 4) {
622 		if (!(self->spawnflags & 2)) {
623 			gi.dprintf("func_wall START_ON without TOGGLE\n");
624 			self->spawnflags |= 2;
625 		}
626 	}
627 	self->use = func_wall_use;
628 	if (self->spawnflags & 4) {
629 		self->solid = SOLID_BSP;
630 	} else {
631 		self->solid = SOLID_NOT;
632 		self->svflags |= SVF_NOCLIENT;
633 	}
634 	gi.linkentity(self);
635 }
636 
637 
638 /*
639  * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST This
640  * is solid bmodel that will fall if it's support it removed.
641  */
642 
643 void
func_object_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)644 func_object_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
645 {
646 	/* only squash thing we fall on top of */
647 	if (!plane)
648 		return;
649 	if (plane->normal[2] < 1.0)
650 		return;
651 	if (other->takedamage == DAMAGE_NO)
652 		return;
653 	T_Damage(other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
654 }
655 
656 void
func_object_release(edict_t * self)657 func_object_release(edict_t * self)
658 {
659 	self->movetype = MOVETYPE_TOSS;
660 	self->touch = func_object_touch;
661 }
662 
663 void
func_object_use(edict_t * self,edict_t * other,edict_t * activator)664 func_object_use(edict_t * self, edict_t * other, edict_t * activator)
665 {
666 	self->solid = SOLID_BSP;
667 	self->svflags &= ~SVF_NOCLIENT;
668 	self->use = NULL;
669 	KillBox(self);
670 	func_object_release(self);
671 }
672 
673 void
SP_func_object(edict_t * self)674 SP_func_object(edict_t * self)
675 {
676 	gi.setmodel(self, self->model);
677 
678 	self->mins[0] += 1;
679 	self->mins[1] += 1;
680 	self->mins[2] += 1;
681 	self->maxs[0] -= 1;
682 	self->maxs[1] -= 1;
683 	self->maxs[2] -= 1;
684 
685 	if (!self->dmg)
686 		self->dmg = 100;
687 
688 	if (self->spawnflags == 0) {
689 		self->solid = SOLID_BSP;
690 		self->movetype = MOVETYPE_PUSH;
691 		self->think = func_object_release;
692 		self->nextthink = level.time + 2 * FRAMETIME;
693 	} else {
694 		self->solid = SOLID_NOT;
695 		self->movetype = MOVETYPE_PUSH;
696 		self->use = func_object_use;
697 		self->svflags |= SVF_NOCLIENT;
698 	}
699 
700 	if (self->spawnflags & 2)
701 		self->s.effects |= EF_ANIM_ALL;
702 	if (self->spawnflags & 4)
703 		self->s.effects |= EF_ANIM_ALLFAST;
704 
705 	self->clipmask = MASK_MONSTERSOLID;
706 
707 	gi.linkentity(self);
708 }
709 
710 
711 /*
712  * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST Any
713  * brush that you want to explode or break apart.  If you want an ex0plosion,
714  * set dmg and it will do a radius explosion of that amount at the center of
715  * the bursh.
716  *
717  * If targeted it will not be shootable.
718  *
719  * health defaults to 100.
720  *
721  * mass defaults to 75.  This determines how much debris is emitted when it
722  * explodes.  You get one large chunk per 100 of mass (up to 8) and one small
723  * chunk per 25 of mass (up to 16).  So 800 gives the most.
724  */
725 void
func_explosive_explode(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)726 func_explosive_explode(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
727 {
728 	vec3_t		origin;
729 	vec3_t		chunkorigin;
730 	vec3_t		size;
731 	int		count;
732 	int		mass;
733 
734 	/* bmodel origins are (0 0 0), we need to adjust that here */
735 	VectorScale(self->size, 0.5, size);
736 	VectorAdd(self->absmin, size, origin);
737 	VectorCopy(origin, self->s.origin);
738 
739 	self->takedamage = DAMAGE_NO;
740 
741 	if (self->dmg)
742 		T_RadiusDamage(self, attacker, self->dmg, NULL, self->dmg + 40, MOD_EXPLOSIVE);
743 
744 	VectorSubtract(self->s.origin, inflictor->s.origin, self->velocity);
745 	VectorNormalize(self->velocity);
746 	VectorScale(self->velocity, 150, self->velocity);
747 
748 	/* start chunks towards the center */
749 	VectorScale(size, 0.5, size);
750 
751 	mass = self->mass;
752 	if (!mass)
753 		mass = 75;
754 
755 	/* big chunks */
756 	if (mass >= 100) {
757 		count = mass / 100;
758 		if (count > 8)
759 			count = 8;
760 		while (count--) {
761 			chunkorigin[0] = origin[0] + crandom() * size[0];
762 			chunkorigin[1] = origin[1] + crandom() * size[1];
763 			chunkorigin[2] = origin[2] + crandom() * size[2];
764 			ThrowDebris(self, "models/objects/debris1/tris.md2", 1, chunkorigin);
765 		}
766 	}
767 	/* small chunks */
768 	count = mass / 25;
769 	if (count > 16)
770 		count = 16;
771 	while (count--) {
772 		chunkorigin[0] = origin[0] + crandom() * size[0];
773 		chunkorigin[1] = origin[1] + crandom() * size[1];
774 		chunkorigin[2] = origin[2] + crandom() * size[2];
775 		ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin);
776 	}
777 
778 	G_UseTargets(self, attacker);
779 
780 	if (self->dmg)
781 		BecomeExplosion1(self);
782 	else
783 		G_FreeEdict(self);
784 }
785 
786 void
func_explosive_use(edict_t * self,edict_t * other,edict_t * activator)787 func_explosive_use(edict_t * self, edict_t * other, edict_t * activator)
788 {
789 	func_explosive_explode(self, self, other, self->health, vec3_origin);
790 }
791 
792 void
func_explosive_spawn(edict_t * self,edict_t * other,edict_t * activator)793 func_explosive_spawn(edict_t * self, edict_t * other, edict_t * activator)
794 {
795 	self->solid = SOLID_BSP;
796 	self->svflags &= ~SVF_NOCLIENT;
797 	self->use = NULL;
798 	KillBox(self);
799 	gi.linkentity(self);
800 }
801 
802 void
SP_func_explosive(edict_t * self)803 SP_func_explosive(edict_t * self)
804 {
805 	if (deathmatch->value) {/* auto-remove for deathmatch */
806 		G_FreeEdict(self);
807 		return;
808 	}
809 	self->movetype = MOVETYPE_PUSH;
810 
811 	gi.modelindex("models/objects/debris1/tris.md2");
812 	gi.modelindex("models/objects/debris2/tris.md2");
813 
814 	gi.setmodel(self, self->model);
815 
816 	if (self->spawnflags & 1) {
817 		self->svflags |= SVF_NOCLIENT;
818 		self->solid = SOLID_NOT;
819 		self->use = func_explosive_spawn;
820 	} else {
821 		self->solid = SOLID_BSP;
822 		if (self->targetname)
823 			self->use = func_explosive_use;
824 	}
825 
826 	if (self->spawnflags & 2)
827 		self->s.effects |= EF_ANIM_ALL;
828 	if (self->spawnflags & 4)
829 		self->s.effects |= EF_ANIM_ALLFAST;
830 
831 	if (self->use != func_explosive_use) {
832 		if (!self->health)
833 			self->health = 100;
834 		self->die = func_explosive_explode;
835 		self->takedamage = DAMAGE_YES;
836 	}
837 	gi.linkentity(self);
838 }
839 
840 
841 /*
842  * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) Large exploding box.
843  * You can override its mass (100), health (80), and dmg (150).
844  */
845 
846 void
barrel_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)847 barrel_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
848 {
849 	float		ratio;
850 	vec3_t		v;
851 
852 	if ((!other->groundentity) || (other->groundentity == self))
853 		return;
854 
855 	ratio = (float)other->mass / (float)self->mass;
856 	VectorSubtract(self->s.origin, other->s.origin, v);
857 	M_walkmove(self, vectoyaw(v), 20 * ratio * FRAMETIME);
858 }
859 
860 void
barrel_explode(edict_t * self)861 barrel_explode(edict_t * self)
862 {
863 	vec3_t		org;
864 	float		spd;
865 	vec3_t		save;
866 
867 	T_RadiusDamage(self, self->activator, self->dmg, NULL, self->dmg + 40, MOD_BARREL);
868 
869 	VectorCopy(self->s.origin, save);
870 	VectorMA(self->absmin, 0.5, self->size, self->s.origin);
871 
872 	/* a few big chunks */
873 	spd = 1.5 * (float)self->dmg / 200.0;
874 	org[0] = self->s.origin[0] + crandom() * self->size[0];
875 	org[1] = self->s.origin[1] + crandom() * self->size[1];
876 	org[2] = self->s.origin[2] + crandom() * self->size[2];
877 	ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
878 	org[0] = self->s.origin[0] + crandom() * self->size[0];
879 	org[1] = self->s.origin[1] + crandom() * self->size[1];
880 	org[2] = self->s.origin[2] + crandom() * self->size[2];
881 	ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
882 
883 	/* bottom corners */
884 	spd = 1.75 * (float)self->dmg / 200.0;
885 	VectorCopy(self->absmin, org);
886 	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
887 	VectorCopy(self->absmin, org);
888 	org[0] += self->size[0];
889 	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
890 	VectorCopy(self->absmin, org);
891 	org[1] += self->size[1];
892 	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
893 	VectorCopy(self->absmin, org);
894 	org[0] += self->size[0];
895 	org[1] += self->size[1];
896 	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
897 
898 	/* a bunch of little chunks */
899 	spd = 2 * self->dmg / 200;
900 	org[0] = self->s.origin[0] + crandom() * self->size[0];
901 	org[1] = self->s.origin[1] + crandom() * self->size[1];
902 	org[2] = self->s.origin[2] + crandom() * self->size[2];
903 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
904 	org[0] = self->s.origin[0] + crandom() * self->size[0];
905 	org[1] = self->s.origin[1] + crandom() * self->size[1];
906 	org[2] = self->s.origin[2] + crandom() * self->size[2];
907 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
908 	org[0] = self->s.origin[0] + crandom() * self->size[0];
909 	org[1] = self->s.origin[1] + crandom() * self->size[1];
910 	org[2] = self->s.origin[2] + crandom() * self->size[2];
911 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
912 	org[0] = self->s.origin[0] + crandom() * self->size[0];
913 	org[1] = self->s.origin[1] + crandom() * self->size[1];
914 	org[2] = self->s.origin[2] + crandom() * self->size[2];
915 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
916 	org[0] = self->s.origin[0] + crandom() * self->size[0];
917 	org[1] = self->s.origin[1] + crandom() * self->size[1];
918 	org[2] = self->s.origin[2] + crandom() * self->size[2];
919 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
920 	org[0] = self->s.origin[0] + crandom() * self->size[0];
921 	org[1] = self->s.origin[1] + crandom() * self->size[1];
922 	org[2] = self->s.origin[2] + crandom() * self->size[2];
923 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
924 	org[0] = self->s.origin[0] + crandom() * self->size[0];
925 	org[1] = self->s.origin[1] + crandom() * self->size[1];
926 	org[2] = self->s.origin[2] + crandom() * self->size[2];
927 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
928 	org[0] = self->s.origin[0] + crandom() * self->size[0];
929 	org[1] = self->s.origin[1] + crandom() * self->size[1];
930 	org[2] = self->s.origin[2] + crandom() * self->size[2];
931 	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
932 
933 	VectorCopy(save, self->s.origin);
934 	if (self->groundentity)
935 		BecomeExplosion2(self);
936 	else
937 		BecomeExplosion1(self);
938 }
939 
940 void
barrel_delay(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)941 barrel_delay(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
942 {
943 	self->takedamage = DAMAGE_NO;
944 	self->nextthink = level.time + 2 * FRAMETIME;
945 	self->think = barrel_explode;
946 	self->activator = attacker;
947 }
948 
949 void
SP_misc_explobox(edict_t * self)950 SP_misc_explobox(edict_t * self)
951 {
952 	if (deathmatch->value) {/* auto-remove for deathmatch */
953 		G_FreeEdict(self);
954 		return;
955 	}
956 	gi.modelindex("models/objects/debris1/tris.md2");
957 	gi.modelindex("models/objects/debris2/tris.md2");
958 	gi.modelindex("models/objects/debris3/tris.md2");
959 
960 	self->solid = SOLID_BBOX;
961 	self->movetype = MOVETYPE_STEP;
962 
963 	self->model = "models/objects/barrels/tris.md2";
964 	self->s.modelindex = gi.modelindex(self->model);
965 	VectorSet(self->mins, -16, -16, 0);
966 	VectorSet(self->maxs, 16, 16, 40);
967 
968 	if (!self->mass)
969 		self->mass = 400;
970 	if (!self->health)
971 		self->health = 10;
972 	if (!self->dmg)
973 		self->dmg = 150;
974 
975 	self->die = barrel_delay;
976 	self->takedamage = DAMAGE_YES;
977 	self->monsterinfo.aiflags = AI_NOSTEP;
978 
979 	self->touch = barrel_touch;
980 
981 	self->think = M_droptofloor;
982 	self->nextthink = level.time + 2 * FRAMETIME;
983 
984 	gi.linkentity(self);
985 }
986 
987 
988 //
989 /* miscellaneous specialty items */
990 //
991 
992 /*
993  * QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
994  */
995 
996 void
misc_blackhole_use(edict_t * ent,edict_t * other,edict_t * activator)997 misc_blackhole_use(edict_t * ent, edict_t * other, edict_t * activator)
998 {
999 
1000 	/*
1001 	 * gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BOSSTPORT);
1002 	 * gi.WritePosition (ent->s.origin); gi.multicast (ent->s.origin,
1003 	 * MULTICAST_PVS);
1004 	 */
1005 	G_FreeEdict(ent);
1006 }
1007 
1008 void
misc_blackhole_think(edict_t * self)1009 misc_blackhole_think(edict_t * self)
1010 {
1011 	if (++self->s.frame < 19)
1012 		self->nextthink = level.time + FRAMETIME;
1013 	else {
1014 		self->s.frame = 0;
1015 		self->nextthink = level.time + FRAMETIME;
1016 	}
1017 }
1018 
1019 void
SP_misc_blackhole(edict_t * ent)1020 SP_misc_blackhole(edict_t * ent)
1021 {
1022 	ent->movetype = MOVETYPE_NONE;
1023 	ent->solid = SOLID_NOT;
1024 	VectorSet(ent->mins, -64, -64, 0);
1025 	VectorSet(ent->maxs, 64, 64, 8);
1026 	ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2");
1027 	ent->s.renderfx = RF_TRANSLUCENT;
1028 	ent->use = misc_blackhole_use;
1029 	ent->think = misc_blackhole_think;
1030 	ent->nextthink = level.time + 2 * FRAMETIME;
1031 	gi.linkentity(ent);
1032 }
1033 
1034 /*
1035  * QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
1036  */
1037 
1038 void
misc_eastertank_think(edict_t * self)1039 misc_eastertank_think(edict_t * self)
1040 {
1041 	if (++self->s.frame < 293)
1042 		self->nextthink = level.time + FRAMETIME;
1043 	else {
1044 		self->s.frame = 254;
1045 		self->nextthink = level.time + FRAMETIME;
1046 	}
1047 }
1048 
1049 void
SP_misc_eastertank(edict_t * ent)1050 SP_misc_eastertank(edict_t * ent)
1051 {
1052 	ent->movetype = MOVETYPE_NONE;
1053 	ent->solid = SOLID_BBOX;
1054 	VectorSet(ent->mins, -32, -32, -16);
1055 	VectorSet(ent->maxs, 32, 32, 32);
1056 	ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
1057 	ent->s.frame = 254;
1058 	ent->think = misc_eastertank_think;
1059 	ent->nextthink = level.time + 2 * FRAMETIME;
1060 	gi.linkentity(ent);
1061 }
1062 
1063 /*
1064  * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
1065  */
1066 
1067 
1068 void
misc_easterchick_think(edict_t * self)1069 misc_easterchick_think(edict_t * self)
1070 {
1071 	if (++self->s.frame < 247)
1072 		self->nextthink = level.time + FRAMETIME;
1073 	else {
1074 		self->s.frame = 208;
1075 		self->nextthink = level.time + FRAMETIME;
1076 	}
1077 }
1078 
1079 void
SP_misc_easterchick(edict_t * ent)1080 SP_misc_easterchick(edict_t * ent)
1081 {
1082 	ent->movetype = MOVETYPE_NONE;
1083 	ent->solid = SOLID_BBOX;
1084 	VectorSet(ent->mins, -32, -32, 0);
1085 	VectorSet(ent->maxs, 32, 32, 32);
1086 	ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
1087 	ent->s.frame = 208;
1088 	ent->think = misc_easterchick_think;
1089 	ent->nextthink = level.time + 2 * FRAMETIME;
1090 	gi.linkentity(ent);
1091 }
1092 
1093 /*
1094  * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
1095  */
1096 
1097 
1098 void
misc_easterchick2_think(edict_t * self)1099 misc_easterchick2_think(edict_t * self)
1100 {
1101 	if (++self->s.frame < 287)
1102 		self->nextthink = level.time + FRAMETIME;
1103 	else {
1104 		self->s.frame = 248;
1105 		self->nextthink = level.time + FRAMETIME;
1106 	}
1107 }
1108 
1109 void
SP_misc_easterchick2(edict_t * ent)1110 SP_misc_easterchick2(edict_t * ent)
1111 {
1112 	ent->movetype = MOVETYPE_NONE;
1113 	ent->solid = SOLID_BBOX;
1114 	VectorSet(ent->mins, -32, -32, 0);
1115 	VectorSet(ent->maxs, 32, 32, 32);
1116 	ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
1117 	ent->s.frame = 248;
1118 	ent->think = misc_easterchick2_think;
1119 	ent->nextthink = level.time + 2 * FRAMETIME;
1120 	gi.linkentity(ent);
1121 }
1122 
1123 
1124 /*
1125  * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) Not really a
1126  * monster, this is the Tank Commander's decapitated body. There should be a
1127  * item_commander_head that has this as it's target.
1128  */
1129 
1130 void
commander_body_think(edict_t * self)1131 commander_body_think(edict_t * self)
1132 {
1133 	if (++self->s.frame < 24)
1134 		self->nextthink = level.time + FRAMETIME;
1135 	else
1136 		self->nextthink = 0;
1137 
1138 	if (self->s.frame == 22)
1139 		gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0);
1140 }
1141 
1142 void
commander_body_use(edict_t * self,edict_t * other,edict_t * activator)1143 commander_body_use(edict_t * self, edict_t * other, edict_t * activator)
1144 {
1145 	self->think = commander_body_think;
1146 	self->nextthink = level.time + FRAMETIME;
1147 	gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0);
1148 }
1149 
1150 void
commander_body_drop(edict_t * self)1151 commander_body_drop(edict_t * self)
1152 {
1153 	self->movetype = MOVETYPE_TOSS;
1154 	self->s.origin[2] += 2;
1155 }
1156 
1157 void
SP_monster_commander_body(edict_t * self)1158 SP_monster_commander_body(edict_t * self)
1159 {
1160 	self->movetype = MOVETYPE_NONE;
1161 	self->solid = SOLID_BBOX;
1162 	self->model = "models/monsters/commandr/tris.md2";
1163 	self->s.modelindex = gi.modelindex(self->model);
1164 	VectorSet(self->mins, -32, -32, 0);
1165 	VectorSet(self->maxs, 32, 32, 48);
1166 	self->use = commander_body_use;
1167 	self->takedamage = DAMAGE_YES;
1168 	self->flags = FL_GODMODE;
1169 	self->s.renderfx |= RF_FRAMELERP;
1170 	gi.linkentity(self);
1171 
1172 	gi.soundindex("tank/thud.wav");
1173 	gi.soundindex("tank/pain.wav");
1174 
1175 	self->think = commander_body_drop;
1176 	self->nextthink = level.time + 5 * FRAMETIME;
1177 }
1178 
1179 
1180 /*
1181  * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) The origin is the bottom of
1182  * the banner. The banner is 128 tall.
1183  */
1184 void
misc_banner_think(edict_t * ent)1185 misc_banner_think(edict_t * ent)
1186 {
1187 	ent->s.frame = (ent->s.frame + 1) % 16;
1188 	ent->nextthink = level.time + FRAMETIME;
1189 }
1190 
1191 void
SP_misc_banner(edict_t * ent)1192 SP_misc_banner(edict_t * ent)
1193 {
1194 	ent->movetype = MOVETYPE_NONE;
1195 	ent->solid = SOLID_NOT;
1196 	ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
1197 	ent->s.renderfx |= RF_NOSHADOW;
1198 	ent->s.frame = rand() % 16;
1199 	gi.linkentity(ent);
1200 
1201 	ent->think = misc_banner_think;
1202 	ent->nextthink = level.time + FRAMETIME;
1203 }
1204 
1205 /*
1206  * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH
1207  * BACK_DECAP FETAL_POS SIT_DECAP IMPALED This is the dead player model.
1208  * Comes in 6 exciting different poses!
1209  */
1210 void
misc_deadsoldier_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1211 misc_deadsoldier_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
1212 {
1213 	int		n;
1214 
1215 	if (self->health > -80)
1216 		return;
1217 
1218 	gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
1219 	for (n = 0; n < 4; n++)
1220 		ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
1221 	ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
1222 }
1223 
1224 void
SP_misc_deadsoldier(edict_t * ent)1225 SP_misc_deadsoldier(edict_t * ent)
1226 {
1227 	if (deathmatch->value) {/* auto-remove for deathmatch */
1228 		G_FreeEdict(ent);
1229 		return;
1230 	}
1231 	ent->movetype = MOVETYPE_NONE;
1232 	ent->solid = SOLID_BBOX;
1233 	ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2");
1234 
1235 	/* Defaults to frame 0 */
1236 	if (ent->spawnflags & 2)
1237 		ent->s.frame = 1;
1238 	else if (ent->spawnflags & 4)
1239 		ent->s.frame = 2;
1240 	else if (ent->spawnflags & 8)
1241 		ent->s.frame = 3;
1242 	else if (ent->spawnflags & 16)
1243 		ent->s.frame = 4;
1244 	else if (ent->spawnflags & 32)
1245 		ent->s.frame = 5;
1246 	else
1247 		ent->s.frame = 0;
1248 
1249 	VectorSet(ent->mins, -16, -16, 0);
1250 	VectorSet(ent->maxs, 16, 16, 16);
1251 	ent->deadflag = DEAD_DEAD;
1252 	ent->takedamage = DAMAGE_YES;
1253 	ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER;
1254 	ent->die = misc_deadsoldier_die;
1255 	ent->monsterinfo.aiflags |= AI_GOOD_GUY;
1256 
1257 	gi.linkentity(ent);
1258 }
1259 
1260 /*
1261  * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) This is the Viper for
1262  * the flyby bombing. It is trigger_spawned, so you must have something use
1263  * it for it to show up. There must be a path for it to follow once it is
1264  * activated.
1265  *
1266  * "speed"		How fast the Viper should fly
1267  */
1268 
1269 extern void	train_use(edict_t * self, edict_t * other, edict_t * activator);
1270 extern void	func_train_find(edict_t * self);
1271 
1272 void
misc_viper_use(edict_t * self,edict_t * other,edict_t * activator)1273 misc_viper_use(edict_t * self, edict_t * other, edict_t * activator)
1274 {
1275 	self->svflags &= ~SVF_NOCLIENT;
1276 	self->use = train_use;
1277 	train_use(self, other, activator);
1278 }
1279 
1280 void
SP_misc_viper(edict_t * ent)1281 SP_misc_viper(edict_t * ent)
1282 {
1283 	if (!ent->target) {
1284 		gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin));
1285 		G_FreeEdict(ent);
1286 		return;
1287 	}
1288 	if (!ent->speed)
1289 		ent->speed = 300;
1290 
1291 	ent->movetype = MOVETYPE_PUSH;
1292 	ent->solid = SOLID_NOT;
1293 	ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2");
1294 	VectorSet(ent->mins, -16, -16, 0);
1295 	VectorSet(ent->maxs, 16, 16, 32);
1296 
1297 	ent->think = func_train_find;
1298 	ent->nextthink = level.time + FRAMETIME;
1299 	ent->use = misc_viper_use;
1300 	ent->svflags |= SVF_NOCLIENT;
1301 	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
1302 
1303 	gi.linkentity(ent);
1304 }
1305 
1306 
1307 /*
1308  * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) This is a large
1309  * stationary viper as seen in Paul's intro
1310  */
1311 void
SP_misc_bigviper(edict_t * ent)1312 SP_misc_bigviper(edict_t * ent)
1313 {
1314 	ent->movetype = MOVETYPE_NONE;
1315 	ent->solid = SOLID_BBOX;
1316 	VectorSet(ent->mins, -176, -120, -24);
1317 	VectorSet(ent->maxs, 176, 120, 72);
1318 	ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
1319 	gi.linkentity(ent);
1320 }
1321 
1322 
1323 /*
1324  * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) "dmg"	how much boom
1325  * should the bomb make?
1326  */
1327 void
misc_viper_bomb_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1328 misc_viper_bomb_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
1329 {
1330 	G_UseTargets(self, self->activator);
1331 
1332 	self->s.origin[2] = self->absmin[2] + 1;
1333 	T_RadiusDamage(self, self, self->dmg, NULL, self->dmg + 40, MOD_BOMB);
1334 	BecomeExplosion2(self);
1335 }
1336 
1337 void
misc_viper_bomb_prethink(edict_t * self)1338 misc_viper_bomb_prethink(edict_t * self)
1339 {
1340 	vec3_t		v;
1341 	float		diff;
1342 
1343 	self->groundentity = NULL;
1344 
1345 	diff = self->timestamp - level.time;
1346 	if (diff < -1.0)
1347 		diff = -1.0;
1348 
1349 	VectorScale(self->moveinfo.dir, 1.0 + diff, v);
1350 	v[2] = diff;
1351 
1352 	diff = self->s.angles[2];
1353 	vectoangles(v, self->s.angles);
1354 	self->s.angles[2] = diff + 10;
1355 }
1356 
1357 void
misc_viper_bomb_use(edict_t * self,edict_t * other,edict_t * activator)1358 misc_viper_bomb_use(edict_t * self, edict_t * other, edict_t * activator)
1359 {
1360 	edict_t        *viper;
1361 
1362 	self->solid = SOLID_BBOX;
1363 	self->svflags &= ~SVF_NOCLIENT;
1364 	self->s.effects |= EF_ROCKET;
1365 	self->use = NULL;
1366 	self->movetype = MOVETYPE_TOSS;
1367 	self->prethink = misc_viper_bomb_prethink;
1368 	self->touch = misc_viper_bomb_touch;
1369 	self->activator = activator;
1370 
1371 	viper = G_Find(NULL, FOFS(classname), "misc_viper");
1372 	VectorScale(viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
1373 
1374 	self->timestamp = level.time;
1375 	VectorCopy(viper->moveinfo.dir, self->moveinfo.dir);
1376 }
1377 
1378 void
SP_misc_viper_bomb(edict_t * self)1379 SP_misc_viper_bomb(edict_t * self)
1380 {
1381 	self->movetype = MOVETYPE_NONE;
1382 	self->solid = SOLID_NOT;
1383 	VectorSet(self->mins, -8, -8, -8);
1384 	VectorSet(self->maxs, 8, 8, 8);
1385 
1386 	self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");
1387 
1388 	if (!self->dmg)
1389 		self->dmg = 1000;
1390 
1391 	self->use = misc_viper_bomb_use;
1392 	self->svflags |= SVF_NOCLIENT;
1393 
1394 	gi.linkentity(self);
1395 }
1396 
1397 
1398 /*
1399  * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) This is a Storgg
1400  * ship for the flybys. It is trigger_spawned, so you must have something use
1401  * it for it to show up. There must be a path for it to follow once it is
1402  * activated.
1403  *
1404  * "speed"		How fast it should fly
1405  */
1406 
1407 extern void	train_use(edict_t * self, edict_t * other, edict_t * activator);
1408 extern void	func_train_find(edict_t * self);
1409 
1410 void
misc_strogg_ship_use(edict_t * self,edict_t * other,edict_t * activator)1411 misc_strogg_ship_use(edict_t * self, edict_t * other, edict_t * activator)
1412 {
1413 	self->svflags &= ~SVF_NOCLIENT;
1414 	self->use = train_use;
1415 	train_use(self, other, activator);
1416 }
1417 
1418 void
SP_misc_strogg_ship(edict_t * ent)1419 SP_misc_strogg_ship(edict_t * ent)
1420 {
1421 	if (!ent->target) {
1422 		gi.dprintf("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
1423 		G_FreeEdict(ent);
1424 		return;
1425 	}
1426 	if (!ent->speed)
1427 		ent->speed = 300;
1428 
1429 	ent->movetype = MOVETYPE_PUSH;
1430 	ent->solid = SOLID_NOT;
1431 	ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
1432 	VectorSet(ent->mins, -16, -16, 0);
1433 	VectorSet(ent->maxs, 16, 16, 32);
1434 
1435 	ent->think = func_train_find;
1436 	ent->nextthink = level.time + FRAMETIME;
1437 	ent->use = misc_strogg_ship_use;
1438 	ent->svflags |= SVF_NOCLIENT;
1439 	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
1440 
1441 	gi.linkentity(ent);
1442 }
1443 
1444 
1445 /*
1446  * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
1447  */
1448 void
misc_satellite_dish_think(edict_t * self)1449 misc_satellite_dish_think(edict_t * self)
1450 {
1451 	self->s.frame++;
1452 	if (self->s.frame < 38)
1453 		self->nextthink = level.time + FRAMETIME;
1454 }
1455 
1456 void
misc_satellite_dish_use(edict_t * self,edict_t * other,edict_t * activator)1457 misc_satellite_dish_use(edict_t * self, edict_t * other, edict_t * activator)
1458 {
1459 	self->s.frame = 0;
1460 	self->think = misc_satellite_dish_think;
1461 	self->nextthink = level.time + FRAMETIME;
1462 }
1463 
1464 void
SP_misc_satellite_dish(edict_t * ent)1465 SP_misc_satellite_dish(edict_t * ent)
1466 {
1467 	ent->movetype = MOVETYPE_NONE;
1468 	ent->solid = SOLID_BBOX;
1469 	VectorSet(ent->mins, -64, -64, 0);
1470 	VectorSet(ent->maxs, 64, 64, 128);
1471 	ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2");
1472 	ent->use = misc_satellite_dish_use;
1473 	gi.linkentity(ent);
1474 }
1475 
1476 
1477 /*
1478  * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
1479  */
1480 void
SP_light_mine1(edict_t * ent)1481 SP_light_mine1(edict_t * ent)
1482 {
1483 	ent->movetype = MOVETYPE_NONE;
1484 	ent->solid = SOLID_BBOX;
1485 	ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2");
1486 	ent->s.renderfx |= RF_NOSHADOW;
1487 	gi.linkentity(ent);
1488 }
1489 
1490 
1491 /*
1492  * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
1493  */
1494 void
SP_light_mine2(edict_t * ent)1495 SP_light_mine2(edict_t * ent)
1496 {
1497 	ent->movetype = MOVETYPE_NONE;
1498 	ent->solid = SOLID_BBOX;
1499 	ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2");
1500 	ent->s.renderfx |= RF_NOSHADOW;
1501 	gi.linkentity(ent);
1502 }
1503 
1504 
1505 /*
1506  * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
1507  * target_spawner
1508  */
1509 void
SP_misc_gib_arm(edict_t * ent)1510 SP_misc_gib_arm(edict_t * ent)
1511 {
1512 	gi.setmodel(ent, "models/objects/gibs/arm/tris.md2");
1513 	ent->solid = SOLID_NOT;
1514 	ent->s.effects |= EF_GIB;
1515 	ent->takedamage = DAMAGE_YES;
1516 	ent->die = gib_die;
1517 	ent->movetype = MOVETYPE_TOSS;
1518 	ent->svflags |= SVF_MONSTER;
1519 	ent->deadflag = DEAD_DEAD;
1520 	ent->avelocity[0] = random() * 200;
1521 	ent->avelocity[1] = random() * 200;
1522 	ent->avelocity[2] = random() * 200;
1523 	ent->think = G_FreeEdict;
1524 	ent->nextthink = level.time + 30;
1525 	gi.linkentity(ent);
1526 }
1527 
1528 /*
1529  * QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
1530  * target_spawner
1531  */
1532 void
SP_misc_gib_leg(edict_t * ent)1533 SP_misc_gib_leg(edict_t * ent)
1534 {
1535 	gi.setmodel(ent, "models/objects/gibs/leg/tris.md2");
1536 	ent->solid = SOLID_NOT;
1537 	ent->s.effects |= EF_GIB;
1538 	ent->takedamage = DAMAGE_YES;
1539 	ent->die = gib_die;
1540 	ent->movetype = MOVETYPE_TOSS;
1541 	ent->svflags |= SVF_MONSTER;
1542 	ent->deadflag = DEAD_DEAD;
1543 	ent->avelocity[0] = random() * 200;
1544 	ent->avelocity[1] = random() * 200;
1545 	ent->avelocity[2] = random() * 200;
1546 	ent->think = G_FreeEdict;
1547 	ent->nextthink = level.time + 30;
1548 	gi.linkentity(ent);
1549 }
1550 
1551 /*
1552  * QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
1553  * target_spawner
1554  */
1555 void
SP_misc_gib_head(edict_t * ent)1556 SP_misc_gib_head(edict_t * ent)
1557 {
1558 	gi.setmodel(ent, "models/objects/gibs/head/tris.md2");
1559 	ent->solid = SOLID_NOT;
1560 	ent->s.effects |= EF_GIB;
1561 	ent->takedamage = DAMAGE_YES;
1562 	ent->die = gib_die;
1563 	ent->movetype = MOVETYPE_TOSS;
1564 	ent->svflags |= SVF_MONSTER;
1565 	ent->deadflag = DEAD_DEAD;
1566 	ent->avelocity[0] = random() * 200;
1567 	ent->avelocity[1] = random() * 200;
1568 	ent->avelocity[2] = random() * 200;
1569 	ent->think = G_FreeEdict;
1570 	ent->nextthink = level.time + 30;
1571 	gi.linkentity(ent);
1572 }
1573 
1574 /* ===================================================== */
1575 
1576 /*
1577  * QUAKED target_character (0 0 1) ? used with target_string (must be on same
1578  * "team") "count" is position in the string (starts at 1)
1579  */
1580 
1581 void
SP_target_character(edict_t * self)1582 SP_target_character(edict_t * self)
1583 {
1584 	self->movetype = MOVETYPE_PUSH;
1585 	gi.setmodel(self, self->model);
1586 	self->solid = SOLID_BSP;
1587 	self->s.frame = 12;
1588 	gi.linkentity(self);
1589 	return;
1590 }
1591 
1592 
1593 /*
1594  * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
1595  */
1596 
1597 void
target_string_use(edict_t * self,edict_t * other,edict_t * activator)1598 target_string_use(edict_t * self, edict_t * other, edict_t * activator)
1599 {
1600 	edict_t        *e;
1601 	int		n         , l;
1602 	char		c;
1603 
1604 	l = strlen(self->message);
1605 	for (e = self->teammaster; e; e = e->teamchain) {
1606 		if (!e->count)
1607 			continue;
1608 		n = e->count - 1;
1609 		if (n > l) {
1610 			e->s.frame = 12;
1611 			continue;
1612 		}
1613 		c = self->message[n];
1614 		if (c >= '0' && c <= '9')
1615 			e->s.frame = c - '0';
1616 		else if (c == '-')
1617 			e->s.frame = 10;
1618 		else if (c == ':')
1619 			e->s.frame = 11;
1620 		else
1621 			e->s.frame = 12;
1622 	}
1623 }
1624 
1625 void
SP_target_string(edict_t * self)1626 SP_target_string(edict_t * self)
1627 {
1628 	if (!self->message)
1629 		self->message = "";
1630 	self->use = target_string_use;
1631 }
1632 
1633 
1634 /*
1635  * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF
1636  * MULTI_USE target a target_string with this
1637  *
1638  * The default is to be a time of day clock
1639  *
1640  * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" If
1641  * START_OFF, this entity must be used before it starts
1642  *
1643  * "style"		0 "xx" 1 "xx:xx" 2 "xx:xx:xx"
1644  */
1645 
1646 #define	CLOCK_MESSAGE_SIZE	16
1647 
1648 /* don't let field width of any clock messages change, or it */
1649 /* could cause an overwrite after a game load */
1650 
1651 static void
func_clock_reset(edict_t * self)1652 func_clock_reset(edict_t * self)
1653 {
1654 	self->activator = NULL;
1655 	if (self->spawnflags & 1) {
1656 		self->health = 0;
1657 		self->wait = self->count;
1658 	} else if (self->spawnflags & 2) {
1659 		self->health = self->count;
1660 		self->wait = 0;
1661 	}
1662 }
1663 
1664 static void
func_clock_format_countdown(edict_t * self)1665 func_clock_format_countdown(edict_t * self)
1666 {
1667 	if (self->style == 0) {
1668 		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
1669 		return;
1670 	}
1671 	if (self->style == 1) {
1672 		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60);
1673 		if (self->message[3] == ' ')
1674 			self->message[3] = '0';
1675 		return;
1676 	}
1677 	if (self->style == 2) {
1678 		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
1679 		if (self->message[3] == ' ')
1680 			self->message[3] = '0';
1681 		if (self->message[6] == ' ')
1682 			self->message[6] = '0';
1683 		return;
1684 	}
1685 }
1686 
1687 void
func_clock_think(edict_t * self)1688 func_clock_think(edict_t * self)
1689 {
1690 	if (!self->enemy) {
1691 		self->enemy = G_Find(NULL, FOFS(targetname), self->target);
1692 		if (!self->enemy)
1693 			return;
1694 	}
1695 	if (self->spawnflags & 1) {
1696 		func_clock_format_countdown(self);
1697 		self->health++;
1698 	} else if (self->spawnflags & 2) {
1699 		func_clock_format_countdown(self);
1700 		self->health--;
1701 	} else {
1702 		struct tm      *ltime;
1703 		time_t		gmtime;
1704 
1705 		time(&gmtime);
1706 		ltime = localtime(&gmtime);
1707 		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
1708 		if (self->message[3] == ' ')
1709 			self->message[3] = '0';
1710 		if (self->message[6] == ' ')
1711 			self->message[6] = '0';
1712 	}
1713 
1714 	self->enemy->message = self->message;
1715 	self->enemy->use(self->enemy, self, self);
1716 
1717 	if (((self->spawnflags & 1) && (self->health > self->wait)) ||
1718 	    ((self->spawnflags & 2) && (self->health < self->wait))) {
1719 		if (self->pathtarget) {
1720 			char           *savetarget;
1721 			char           *savemessage;
1722 
1723 			savetarget = self->target;
1724 			savemessage = self->message;
1725 			self->target = self->pathtarget;
1726 			self->message = NULL;
1727 			G_UseTargets(self, self->activator);
1728 			self->target = savetarget;
1729 			self->message = savemessage;
1730 		}
1731 		if (!(self->spawnflags & 8))
1732 			return;
1733 
1734 		func_clock_reset(self);
1735 
1736 		if (self->spawnflags & 4)
1737 			return;
1738 	}
1739 	self->nextthink = level.time + 1;
1740 }
1741 
1742 void
func_clock_use(edict_t * self,edict_t * other,edict_t * activator)1743 func_clock_use(edict_t * self, edict_t * other, edict_t * activator)
1744 {
1745 	if (!(self->spawnflags & 8))
1746 		self->use = NULL;
1747 	if (self->activator)
1748 		return;
1749 	self->activator = activator;
1750 	self->think(self);
1751 }
1752 
1753 void
SP_func_clock(edict_t * self)1754 SP_func_clock(edict_t * self)
1755 {
1756 	if (!self->target) {
1757 		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
1758 		G_FreeEdict(self);
1759 		return;
1760 	}
1761 	if ((self->spawnflags & 2) && (!self->count)) {
1762 		gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin));
1763 		G_FreeEdict(self);
1764 		return;
1765 	}
1766 	if ((self->spawnflags & 1) && (!self->count))
1767 		self->count = 60 * 60;;
1768 
1769 	func_clock_reset(self);
1770 
1771 	self->message = gi.TagMalloc(CLOCK_MESSAGE_SIZE, TAG_LEVEL);
1772 
1773 	self->think = func_clock_think;
1774 
1775 	if (self->spawnflags & 4)
1776 		self->use = func_clock_use;
1777 	else
1778 		self->nextthink = level.time + 1;
1779 }
1780 
1781 /*
1782  * ===========================================================================
1783  * ======
1784  */
1785 
1786 void
teleporter_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1787 teleporter_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
1788 {
1789 	edict_t        *dest;
1790 	int		i;
1791 
1792 	if (!other->client)
1793 		return;
1794 	dest = G_Find(NULL, FOFS(targetname), self->target);
1795 	if (!dest) {
1796 		gi.dprintf("Couldn't find destination\n");
1797 		return;
1798 	}
1799 	/* unlink to make sure it can't possibly interfere with KillBox */
1800 	gi.unlinkentity(other);
1801 
1802 	VectorCopy(dest->s.origin, other->s.origin);
1803 	VectorCopy(dest->s.origin, other->s.old_origin);
1804 	other->s.origin[2] += 10;
1805 
1806 	/* clear the velocity and hold them in place briefly */
1807 	VectorClear(other->velocity);
1808 	other->client->ps.pmove.pm_time = 160 >> 3;	/* hold time */
1809 	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
1810 
1811 	/* draw the teleport splash at source and on the player */
1812 	self->owner->s.event = EV_PLAYER_TELEPORT;
1813 	other->s.event = EV_PLAYER_TELEPORT;
1814 
1815 	/* set angles */
1816 	for (i = 0; i < 3; i++) {
1817 		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
1818 	}
1819 
1820 	VectorClear(other->s.angles);
1821 	VectorClear(other->client->ps.viewangles);
1822 	VectorClear(other->client->v_angle);
1823 
1824 	/* kill anything at the destination */
1825 	KillBox(other);
1826 
1827 	gi.linkentity(other);
1828 }
1829 
1830 /*
1831  * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) Stepping onto
1832  * this disc will teleport players to the targeted misc_teleporter_dest
1833  * object.
1834  */
1835 void
SP_misc_teleporter(edict_t * ent)1836 SP_misc_teleporter(edict_t * ent)
1837 {
1838 	edict_t        *trig;
1839 
1840 	if (!ent->target) {
1841 		gi.dprintf("teleporter without a target.\n");
1842 		G_FreeEdict(ent);
1843 		return;
1844 	}
1845 	gi.setmodel(ent, "models/objects/dmspot/tris.md2");
1846 	ent->s.skinnum = 1;
1847 	ent->s.effects = EF_TELEPORTER;
1848 	ent->s.sound = gi.soundindex("world/amb10.wav");
1849 	ent->solid = SOLID_BBOX;
1850 
1851 	VectorSet(ent->mins, -32, -32, -24);
1852 	VectorSet(ent->maxs, 32, 32, -16);
1853 	gi.linkentity(ent);
1854 
1855 	trig = G_Spawn();
1856 	trig->touch = teleporter_touch;
1857 	trig->solid = SOLID_TRIGGER;
1858 	trig->target = ent->target;
1859 	trig->owner = ent;
1860 	VectorCopy(ent->s.origin, trig->s.origin);
1861 	VectorSet(trig->mins, -8, -8, 8);
1862 	VectorSet(trig->maxs, 8, 8, 24);
1863 	gi.linkentity(trig);
1864 
1865 }
1866 
1867 /*
1868  * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point
1869  * teleporters at these.
1870  */
1871 void
SP_misc_teleporter_dest(edict_t * ent)1872 SP_misc_teleporter_dest(edict_t * ent)
1873 {
1874 	gi.setmodel(ent, "models/objects/dmspot/tris.md2");
1875 	ent->s.skinnum = 0;
1876 	ent->solid = SOLID_BBOX;
1877 	/* ent->s.effects |= EF_FLIES; */
1878 	VectorSet(ent->mins, -32, -32, -24);
1879 	VectorSet(ent->maxs, 32, 32, -16);
1880 	gi.linkentity(ent);
1881 }
1882