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