1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 20?? COR Entertainment, LLC.
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "g_local.h"
26
AttackFinished(edict_t * self,float time)27 void AttackFinished (edict_t *self, float time)
28 {
29 self->monsterinfo.attack_finished = level.time + time;
30 }
31
32
M_CheckGround(edict_t * ent)33 void M_CheckGround (edict_t *ent)
34 {
35 vec3_t point;
36 trace_t trace;
37
38 if (ent->flags & (FL_SWIM|FL_FLY))
39 return;
40
41 if (ent->velocity[2] > 100)
42 {
43 ent->groundentity = NULL;
44 return;
45 }
46
47 // if the hull point one-quarter unit down is solid the entity is on ground
48 point[0] = ent->s.origin[0];
49 point[1] = ent->s.origin[1];
50 point[2] = ent->s.origin[2] - 0.25;
51
52 trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
53
54 // check steepness
55 if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
56 {
57 ent->groundentity = NULL;
58 return;
59 }
60
61 // ent->groundentity = trace.ent;
62 // ent->groundentity_linkcount = trace.ent->linkcount;
63 // if (!trace.startsolid && !trace.allsolid)
64 // VectorCopy (trace.endpos, ent->s.origin);
65 if (!trace.startsolid && !trace.allsolid)
66 {
67 VectorCopy (trace.endpos, ent->s.origin);
68 ent->groundentity = trace.ent;
69 ent->groundentity_linkcount = trace.ent->linkcount;
70 ent->velocity[2] = 0;
71 }
72 }
73
74
M_CatagorizePosition(edict_t * ent)75 void M_CatagorizePosition (edict_t *ent)
76 {
77 vec3_t point;
78 int cont;
79
80 //
81 // get waterlevel
82 //
83 point[0] = ent->s.origin[0];
84 point[1] = ent->s.origin[1];
85 point[2] = ent->s.origin[2] + ent->mins[2] + 1;
86 cont = gi.pointcontents (point);
87
88 if (!(cont & MASK_WATER))
89 {
90 ent->waterlevel = 0;
91 ent->watertype = 0;
92 return;
93 }
94
95 ent->watertype = cont;
96 ent->waterlevel = 1;
97 point[2] += 26;
98 cont = gi.pointcontents (point);
99 if (!(cont & MASK_WATER))
100 return;
101
102 ent->waterlevel = 2;
103 point[2] += 22;
104 cont = gi.pointcontents (point);
105 if (cont & MASK_WATER)
106 ent->waterlevel = 3;
107 }
108
109
M_WorldEffects(edict_t * ent)110 void M_WorldEffects (edict_t *ent)
111 {
112 int dmg;
113
114 if (ent->health > 0)
115 {
116 if (!(ent->flags & FL_SWIM))
117 {
118 if (ent->waterlevel < 3)
119 {
120 ent->air_finished = level.time + 12;
121 }
122 else if (ent->air_finished < level.time)
123 { // drown!
124 if (ent->pain_debounce_time < level.time)
125 {
126 dmg = 2 + 2 * floor(level.time - ent->air_finished);
127 if (dmg > 15)
128 dmg = 15;
129 T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
130 ent->pain_debounce_time = level.time + 1;
131 }
132 }
133 }
134 else
135 {
136 if (ent->waterlevel > 0)
137 {
138 ent->air_finished = level.time + 9;
139 }
140 else if (ent->air_finished < level.time)
141 { // suffocate!
142 if (ent->pain_debounce_time < level.time)
143 {
144 dmg = 2 + 2 * floor(level.time - ent->air_finished);
145 if (dmg > 15)
146 dmg = 15;
147 T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
148 ent->pain_debounce_time = level.time + 1;
149 }
150 }
151 }
152 }
153
154 if (ent->waterlevel == 0)
155 {
156 if (ent->flags & FL_INWATER)
157 {
158 gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
159 ent->flags &= ~FL_INWATER;
160 }
161 return;
162 }
163
164 if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
165 {
166 if (ent->damage_debounce_time < level.time)
167 {
168 ent->damage_debounce_time = level.time + 0.2;
169 T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
170 }
171 }
172 if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
173 {
174 if (ent->damage_debounce_time < level.time)
175 {
176 ent->damage_debounce_time = level.time + 1;
177 T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
178 }
179 }
180
181 if ( !(ent->flags & FL_INWATER) )
182 {
183 if (!(ent->svflags & SVF_DEADMONSTER))
184 {
185 if (ent->watertype & CONTENTS_LAVA)
186 if (random() <= 0.5)
187 gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
188 else
189 gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
190 else if (ent->watertype & CONTENTS_SLIME)
191 gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
192 else if (ent->watertype & CONTENTS_WATER)
193 gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
194 }
195
196 ent->flags |= FL_INWATER;
197 ent->damage_debounce_time = 0;
198 }
199 }
200
201
M_droptofloor(edict_t * ent)202 void M_droptofloor (edict_t *ent)
203 {
204 vec3_t end;
205 trace_t trace;
206
207 ent->s.origin[2] += 1;
208 VectorCopy (ent->s.origin, end);
209 end[2] -= 256;
210
211 trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
212
213 if (trace.fraction == 1 || trace.allsolid)
214 return;
215
216 VectorCopy (trace.endpos, ent->s.origin);
217
218 gi.linkentity (ent);
219 M_CheckGround (ent);
220 M_CatagorizePosition (ent);
221 }
222
223
M_SetEffects(edict_t * ent)224 void M_SetEffects (edict_t *ent)
225 {
226 ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
227 ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
228
229 if (ent->monsterinfo.aiflags & AI_RESURRECTING)
230 {
231 ent->s.effects |= EF_COLOR_SHELL;
232 ent->s.renderfx |= RF_SHELL_BLUE;
233 }
234
235 if (ent->health <= 0)
236 return;
237 }
238
239
M_MoveFrame(edict_t * self)240 void M_MoveFrame (edict_t *self)
241 {
242 mmove_t *move;
243 int index;
244
245 move = self->monsterinfo.currentmove;
246 self->nextthink = level.time + FRAMETIME;
247
248 if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
249 {
250 self->s.frame = self->monsterinfo.nextframe;
251 self->monsterinfo.nextframe = 0;
252 }
253 else
254 {
255 if (self->s.frame == move->lastframe)
256 {
257 if (move->endfunc)
258 {
259 move->endfunc (self);
260
261 // regrab move, endfunc is very likely to change it
262 move = self->monsterinfo.currentmove;
263
264 // check for death
265 if (self->svflags & SVF_DEADMONSTER)
266 return;
267 }
268 }
269
270 if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
271 {
272 self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
273 self->s.frame = move->firstframe;
274 }
275 else
276 {
277 if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
278 {
279 self->s.frame++;
280 if (self->s.frame > move->lastframe)
281 self->s.frame = move->firstframe;
282 }
283 }
284 }
285
286 index = self->s.frame - move->firstframe;
287 if (move->frame[index].aifunc)
288 {
289 if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
290 move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
291 else
292 move->frame[index].aifunc (self, 0);
293 }
294
295 if (move->frame[index].thinkfunc)
296 move->frame[index].thinkfunc (self);
297 }
298
299
monster_think(edict_t * self)300 void monster_think (edict_t *self)
301 {
302 M_MoveFrame (self);
303 if (self->linkcount != self->monsterinfo.linkcount)
304 {
305 self->monsterinfo.linkcount = self->linkcount;
306 M_CheckGround (self);
307 }
308 M_CatagorizePosition (self);
309 M_WorldEffects (self);
310 M_SetEffects (self);
311 }
312
313
314 /*
315 ================
316 monster_use
317
318 Using a monster makes it angry at the current activator
319 ================
320 */
monster_use(edict_t * self,edict_t * other,edict_t * activator)321 void monster_use (edict_t *self, edict_t *other, edict_t *activator)
322 {
323 if (self->enemy)
324 return;
325 if (self->health <= 0)
326 return;
327 if (activator->flags & FL_NOTARGET)
328 return;
329 if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
330 return;
331
332 // delay reaction so if the monster is teleported, its sound is still heard
333 self->enemy = activator;
334 FoundTarget (self);
335 }
336
337
338 void monster_start_go (edict_t *self);
339
340
monster_triggered_spawn(edict_t * self)341 void monster_triggered_spawn (edict_t *self)
342 {
343 self->s.origin[2] += 1;
344 KillBox (self);
345
346 self->solid = SOLID_BBOX;
347 self->movetype = MOVETYPE_STEP;
348 self->svflags &= ~SVF_NOCLIENT;
349 self->air_finished = level.time + 12;
350 gi.linkentity (self);
351
352 monster_start_go (self);
353
354 if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
355 {
356 FoundTarget (self);
357 }
358 else
359 {
360 self->enemy = NULL;
361 }
362 }
363
monster_triggered_spawn_use(edict_t * self,edict_t * other,edict_t * activator)364 void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
365 {
366 // we have a one frame delay here so we don't telefrag the guy who activated us
367 self->think = monster_triggered_spawn;
368 self->nextthink = level.time + FRAMETIME;
369 if (activator->client)
370 self->enemy = activator;
371 self->use = monster_use;
372 }
373
monster_triggered_start(edict_t * self)374 void monster_triggered_start (edict_t *self)
375 {
376 self->solid = SOLID_NOT;
377 self->movetype = MOVETYPE_NONE;
378 self->svflags |= SVF_NOCLIENT;
379 self->nextthink = 0;
380 self->use = monster_triggered_spawn_use;
381 }
382
383
384 /*
385 ================
386 monster_death_use
387
388 When a monster dies, it fires all of its targets with the current
389 enemy as activator.
390 ================
391 */
monster_death_use(edict_t * self)392 void monster_death_use (edict_t *self)
393 {
394 self->flags &= ~(FL_FLY|FL_SWIM);
395 self->monsterinfo.aiflags &= AI_GOOD_GUY;
396
397 if (self->item)
398 {
399 Drop_Item (self, self->item);
400 self->item = NULL;
401 }
402
403 if (self->deathtarget)
404 self->target = self->deathtarget;
405
406 if (!self->target)
407 return;
408
409 G_UseTargets (self, self->enemy);
410 }
411
412
413 //============================================================================
414
monster_start(edict_t * self)415 qboolean monster_start (edict_t *self)
416 {
417
418 if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
419 {
420 self->spawnflags &= ~4;
421 self->spawnflags |= 1;
422 // gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
423 }
424
425 if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
426 level.total_monsters++;
427
428 self->nextthink = level.time + FRAMETIME;
429 self->svflags |= SVF_MONSTER;
430 self->takedamage = DAMAGE_AIM;
431 self->air_finished = level.time + 12;
432 self->use = monster_use;
433 self->max_health = self->health;
434 self->clipmask = MASK_MONSTERSOLID;
435
436 self->s.skinnum = 0;
437 self->deadflag = DEAD_NO;
438 self->svflags &= ~SVF_DEADMONSTER;
439
440 if (!self->monsterinfo.checkattack)
441 self->monsterinfo.checkattack = M_CheckAttack;
442 VectorCopy (self->s.origin, self->s.old_origin);
443
444 if (st.item)
445 {
446 self->item = FindItemByClassname (st.item);
447 if (!self->item)
448 gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
449 }
450
451 // randomize what frame they start on
452 if (self->monsterinfo.currentmove)
453 self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
454
455 return true;
456 }
457
monster_start_go(edict_t * self)458 void monster_start_go (edict_t *self)
459 {
460 vec3_t v;
461
462 if (self->health <= 0)
463 return;
464
465 // check for target to combat_point and change to combattarget
466 if (self->target)
467 {
468 qboolean notcombat;
469 qboolean fixup;
470 edict_t *target;
471
472 target = NULL;
473 notcombat = false;
474 fixup = false;
475 while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
476 {
477 if (strcmp(target->classname, "point_combat") == 0)
478 {
479 self->combattarget = self->target;
480 fixup = true;
481 }
482 else
483 {
484 notcombat = true;
485 }
486 }
487 if (notcombat && self->combattarget)
488 gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
489 if (fixup)
490 self->target = NULL;
491 }
492
493 // validate combattarget
494 if (self->combattarget)
495 {
496 edict_t *target;
497
498 target = NULL;
499
500 while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
501 {
502 if (strcmp(target->classname, "point_combat") != 0)
503 {
504 gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
505 self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
506 self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
507 (int)target->s.origin[2]);
508 }
509 }
510 }
511
512 if (self->target)
513 {
514 self->goalentity = self->movetarget = G_PickTarget(self->target);
515 if (!self->movetarget)
516 {
517 gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
518 self->target = NULL;
519 self->monsterinfo.pausetime = 100000000;
520 self->monsterinfo.stand (self);
521 }
522 else if (strcmp (self->movetarget->classname, "path_corner") == 0)
523 {
524 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
525 self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
526 self->monsterinfo.walk (self);
527 self->target = NULL;
528 }
529 else
530 {
531 self->goalentity = self->movetarget = NULL;
532 self->monsterinfo.pausetime = 100000000;
533 self->monsterinfo.stand (self);
534 }
535 }
536 else
537 {
538 self->monsterinfo.pausetime = 100000000;
539 self->monsterinfo.stand (self);
540 }
541
542 self->think = monster_think;
543 self->nextthink = level.time + FRAMETIME;
544 }
545
546
walkmonster_start_go(edict_t * self)547 void walkmonster_start_go (edict_t *self)
548 {
549 if (!(self->spawnflags & 2) && level.time < 1)
550 {
551 M_droptofloor (self);
552
553 if (self->groundentity)
554 if (!M_walkmove (self, 0, 0))
555 gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
556 }
557
558 if (!self->yaw_speed)
559 self->yaw_speed = 20;
560 self->viewheight = 25;
561
562 monster_start_go (self);
563
564 if (self->spawnflags & 2)
565 monster_triggered_start (self);
566 }
567
walkmonster_start(edict_t * self)568 void walkmonster_start (edict_t *self)
569 {
570 self->think = walkmonster_start_go;
571 monster_start (self);
572 }
573
574
flymonster_start_go(edict_t * self)575 void flymonster_start_go (edict_t *self)
576 {
577 if (!M_walkmove (self, 0, 0))
578 gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
579
580 if (!self->yaw_speed)
581 self->yaw_speed = 10;
582 self->viewheight = 25;
583
584 monster_start_go (self);
585
586 if (self->spawnflags & 2)
587 monster_triggered_start (self);
588 }
589
590
flymonster_start(edict_t * self)591 void flymonster_start (edict_t *self)
592 {
593 self->flags |= FL_FLY;
594 self->think = flymonster_start_go;
595 monster_start (self);
596 }
597
598
swimmonster_start_go(edict_t * self)599 void swimmonster_start_go (edict_t *self)
600 {
601 if (!self->yaw_speed)
602 self->yaw_speed = 10;
603 self->viewheight = 10;
604
605 monster_start_go (self);
606
607 if (self->spawnflags & 2)
608 monster_triggered_start (self);
609 }
610
swimmonster_start(edict_t * self)611 void swimmonster_start (edict_t *self)
612 {
613 self->flags |= FL_SWIM;
614 self->think = swimmonster_start_go;
615 monster_start (self);
616 }
617