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