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