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