|
|
1.1 root 1: #include "g_local.h"
2:
3:
4: //
5: // monster weapons
6: //
7:
8: //FIXME mosnters should call these with a totally accurate direction
9: // and we can mess it up based on skill. Spread should be for normal
10: // and we can tighten or loosen based on skill. We could muck with
11: // the damages too, but I'm not sure that's such a good idea.
12: void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
13: {
14: fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
15:
16: gi.WriteByte (svc_muzzleflash2);
17: gi.WriteShort (self - g_edicts);
18: gi.WriteByte (flashtype);
19: gi.multicast (start, MULTICAST_PVS);
20: }
21:
22: void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
23: {
24: fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
25:
26: gi.WriteByte (svc_muzzleflash2);
27: gi.WriteShort (self - g_edicts);
28: gi.WriteByte (flashtype);
29: gi.multicast (start, MULTICAST_PVS);
30: }
31:
32: void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
33: {
34: fire_blaster (self, start, dir, damage, speed, effect, false);
35:
36: gi.WriteByte (svc_muzzleflash2);
37: gi.WriteShort (self - g_edicts);
38: gi.WriteByte (flashtype);
39: gi.multicast (start, MULTICAST_PVS);
40: }
41:
42: void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
43: {
44: fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
45:
46: gi.WriteByte (svc_muzzleflash2);
47: gi.WriteShort (self - g_edicts);
48: gi.WriteByte (flashtype);
49: gi.multicast (start, MULTICAST_PVS);
50: }
51:
52: void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
53: {
54: fire_rocket (self, start, dir, damage, speed, damage+20, damage);
55:
56: gi.WriteByte (svc_muzzleflash2);
57: gi.WriteShort (self - g_edicts);
58: gi.WriteByte (flashtype);
59: gi.multicast (start, MULTICAST_PVS);
60: }
61:
62: void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
63: {
64: fire_rail (self, start, aimdir, damage, kick);
65:
66: gi.WriteByte (svc_muzzleflash2);
67: gi.WriteShort (self - g_edicts);
68: gi.WriteByte (flashtype);
69: gi.multicast (start, MULTICAST_PVS);
70: }
71:
72: void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
73: {
74: fire_bfg (self, start, aimdir, damage, speed, damage_radius);
75:
76: gi.WriteByte (svc_muzzleflash2);
77: gi.WriteShort (self - g_edicts);
78: gi.WriteByte (flashtype);
79: gi.multicast (start, MULTICAST_PVS);
80: }
81:
82:
83:
84: //
85: // Monster utility functions
86: //
87:
88: static void M_FliesOff (edict_t *self)
89: {
90: self->s.effects &= ~EF_FLIES;
91: self->s.sound = 0;
92: }
93:
94: static void M_FliesOn (edict_t *self)
95: {
96: if (self->waterlevel)
97: return;
98: self->s.effects |= EF_FLIES;
99: self->s.sound = gi.soundindex ("infantry/inflies1.wav");
100: self->think = M_FliesOff;
101: self->nextthink = level.time + 60;
102: }
103:
104: void M_FlyCheck (edict_t *self)
105: {
106: if (self->waterlevel)
107: return;
108:
109: if (random() > 0.5)
110: return;
111:
112: self->think = M_FliesOn;
113: self->nextthink = level.time + 5 + 10 * random();
114: }
115:
116: void AttackFinished (edict_t *self, float time)
117: {
118: self->monsterinfo.attack_finished = level.time + time;
119: }
120:
121:
122: void M_CheckGround (edict_t *ent)
123: {
124: vec3_t point;
125: trace_t trace;
126:
127: if (ent->flags & (FL_SWIM|FL_FLY))
128: return;
129:
130: if (ent->velocity[2] > 100)
131: {
132: ent->groundentity = NULL;
133: return;
134: }
135:
136: // if the hull point one-quarter unit down is solid the entity is on ground
137: point[0] = ent->s.origin[0];
138: point[1] = ent->s.origin[1];
139: point[2] = ent->s.origin[2] - 0.25;
140:
141: trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
142:
143: // check steepness
144: if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
145: {
146: ent->groundentity = NULL;
147: return;
148: }
149:
150: // ent->groundentity = trace.ent;
151: // ent->groundentity_linkcount = trace.ent->linkcount;
152: // if (!trace.startsolid && !trace.allsolid)
153: // VectorCopy (trace.endpos, ent->s.origin);
154: if (!trace.startsolid && !trace.allsolid)
155: {
156: VectorCopy (trace.endpos, ent->s.origin);
157: ent->groundentity = trace.ent;
158: ent->groundentity_linkcount = trace.ent->linkcount;
159: ent->velocity[2] = 0;
160: }
161: }
162:
163:
164: void M_CatagorizePosition (edict_t *ent)
165: {
166: vec3_t point;
167: int cont;
168:
169: //
170: // get waterlevel
171: //
172: point[0] = ent->s.origin[0];
173: point[1] = ent->s.origin[1];
174: point[2] = ent->s.origin[2] + ent->mins[2] + 1;
175: cont = gi.pointcontents (point);
176:
177: if (!(cont & MASK_WATER))
178: {
179: ent->waterlevel = 0;
180: ent->watertype = 0;
181: return;
182: }
183:
184: ent->watertype = cont;
185: ent->waterlevel = 1;
186: point[2] += 26;
187: cont = gi.pointcontents (point);
188: if (!(cont & MASK_WATER))
189: return;
190:
191: ent->waterlevel = 2;
192: point[2] += 22;
193: cont = gi.pointcontents (point);
194: if (cont & MASK_WATER)
195: ent->waterlevel = 3;
196: }
197:
198:
199: void M_WorldEffects (edict_t *ent)
200: {
201: int dmg;
202:
203: if (ent->health > 0)
204: {
205: if (!(ent->flags & FL_SWIM))
206: {
207: if (ent->waterlevel < 3)
208: {
209: ent->air_finished = level.time + 12;
210: }
211: else if (ent->air_finished < level.time)
212: { // drown!
213: if (ent->pain_debounce_time < level.time)
214: {
215: dmg = 2 + 2 * floor(level.time - ent->air_finished);
216: if (dmg > 15)
217: dmg = 15;
218: T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
219: ent->pain_debounce_time = level.time + 1;
220: }
221: }
222: }
223: else
224: {
225: if (ent->waterlevel > 0)
226: {
227: ent->air_finished = level.time + 9;
228: }
229: else if (ent->air_finished < level.time)
230: { // suffocate!
231: if (ent->pain_debounce_time < level.time)
232: {
233: dmg = 2 + 2 * floor(level.time - ent->air_finished);
234: if (dmg > 15)
235: dmg = 15;
236: T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
237: ent->pain_debounce_time = level.time + 1;
238: }
239: }
240: }
241: }
242:
243: if (ent->waterlevel == 0)
244: {
245: if (ent->flags & FL_INWATER)
246: {
247: gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
248: ent->flags &= ~FL_INWATER;
249: }
250: return;
251: }
252:
253: if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
254: {
255: if (ent->damage_debounce_time < level.time)
256: {
257: ent->damage_debounce_time = level.time + 0.2;
258: T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
259: }
260: }
261: if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
262: {
263: if (ent->damage_debounce_time < level.time)
264: {
265: ent->damage_debounce_time = level.time + 1;
266: T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
267: }
268: }
269:
270: if ( !(ent->flags & FL_INWATER) )
271: {
272: if (!(ent->svflags & SVF_DEADMONSTER))
273: {
274: if (ent->watertype & CONTENTS_LAVA)
275: if (random() <= 0.5)
276: gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
277: else
278: gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
279: else if (ent->watertype & CONTENTS_SLIME)
280: gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
281: else if (ent->watertype & CONTENTS_WATER)
282: gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
283: }
284:
285: ent->flags |= FL_INWATER;
286: ent->damage_debounce_time = 0;
287: }
288: }
289:
290:
291: void M_droptofloor (edict_t *ent)
292: {
293: vec3_t end;
294: trace_t trace;
295:
296: ent->s.origin[2] += 1;
297: VectorCopy (ent->s.origin, end);
298: end[2] -= 256;
299:
300: trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
301:
302: if (trace.fraction == 1 || trace.allsolid)
303: return;
304:
305: VectorCopy (trace.endpos, ent->s.origin);
306:
307: gi.linkentity (ent);
308: M_CheckGround (ent);
309: M_CatagorizePosition (ent);
310: }
311:
312:
313: void M_SetEffects (edict_t *ent)
314: {
315: ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
316: ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
317:
318: if (ent->monsterinfo.aiflags & AI_RESURRECTING)
319: {
320: ent->s.effects |= EF_COLOR_SHELL;
321: ent->s.renderfx |= RF_SHELL_RED;
322: }
323:
324: if (ent->health <= 0)
325: return;
326:
327: if (ent->powerarmor_time > level.time)
328: {
329: if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
330: {
331: ent->s.effects |= EF_POWERSCREEN;
332: }
333: else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
334: {
335: ent->s.effects |= EF_COLOR_SHELL;
336: ent->s.renderfx |= RF_SHELL_GREEN;
337: }
338: }
339: }
340:
341:
342: void M_MoveFrame (edict_t *self)
343: {
344: mmove_t *move;
345: int index;
346:
347: move = self->monsterinfo.currentmove;
348: self->nextthink = level.time + FRAMETIME;
349:
350: if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
351: {
352: self->s.frame = self->monsterinfo.nextframe;
353: self->monsterinfo.nextframe = 0;
354: }
355: else
356: {
357: if (self->s.frame == move->lastframe)
358: {
359: if (move->endfunc)
360: {
361: move->endfunc (self);
362:
363: // regrab move, endfunc is very likely to change it
364: move = self->monsterinfo.currentmove;
365:
366: // check for death
367: if (self->svflags & SVF_DEADMONSTER)
368: return;
369: }
370: }
371:
372: if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
373: {
374: self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
375: self->s.frame = move->firstframe;
376: }
377: else
378: {
379: if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
380: {
381: self->s.frame++;
382: if (self->s.frame > move->lastframe)
383: self->s.frame = move->firstframe;
384: }
385: }
386: }
387:
388: index = self->s.frame - move->firstframe;
389: if (move->frame[index].aifunc)
390: if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
391: move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
392: else
393: move->frame[index].aifunc (self, 0);
394:
395: if (move->frame[index].thinkfunc)
396: move->frame[index].thinkfunc (self);
397: }
398:
399:
400: void monster_think (edict_t *self)
401: {
402: M_MoveFrame (self);
403: if (self->linkcount != self->monsterinfo.linkcount)
404: {
405: self->monsterinfo.linkcount = self->linkcount;
406: M_CheckGround (self);
407: }
408: M_CatagorizePosition (self);
409: M_WorldEffects (self);
410: M_SetEffects (self);
411: }
412:
413:
414: /*
415: ================
416: monster_use
417:
418: Using a monster makes it angry at the current activator
419: ================
420: */
421: void monster_use (edict_t *self, edict_t *other, edict_t *activator)
422: {
423: if (self->enemy)
424: return;
425: if (self->health <= 0)
426: return;
427: if (activator->flags & FL_NOTARGET)
428: return;
429: if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
430: return;
431:
432: // delay reaction so if the monster is teleported, its sound is still heard
433: self->enemy = activator;
434: FoundTarget (self);
435: }
436:
437:
438: void monster_start_go (edict_t *self);
439:
440:
441: void monster_triggered_spawn (edict_t *self)
442: {
443: self->s.origin[2] += 1;
444: KillBox (self);
445:
446: self->solid = SOLID_BBOX;
447: self->movetype = MOVETYPE_STEP;
448: self->svflags &= ~SVF_NOCLIENT;
449: self->air_finished = level.time + 12;
450: gi.linkentity (self);
451:
452: monster_start_go (self);
453:
454: if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
455: {
456: FoundTarget (self);
457: }
458: else
459: {
460: self->enemy = NULL;
461: }
462: }
463:
464: void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
465: {
466: // we have a one frame delay here so we don't telefrag the guy who activated us
467: self->think = monster_triggered_spawn;
468: self->nextthink = level.time + FRAMETIME;
469: if (activator->client)
470: self->enemy = activator;
471: self->use = monster_use;
472: }
473:
474: void monster_triggered_start (edict_t *self)
475: {
476: self->solid = SOLID_NOT;
477: self->movetype = MOVETYPE_NONE;
478: self->svflags |= SVF_NOCLIENT;
479: self->nextthink = 0;
480: self->use = monster_triggered_spawn_use;
481: }
482:
483:
484: /*
485: ================
486: monster_death_use
487:
488: When a monster dies, it fires all of its targets with the current
489: enemy as activator.
490: ================
491: */
492: void monster_death_use (edict_t *self)
493: {
494: self->flags &= ~(FL_FLY|FL_SWIM);
495: self->monsterinfo.aiflags &= AI_GOOD_GUY;
496:
497: if (self->item)
498: {
499: Drop_Item (self, self->item);
500: self->item = NULL;
501: }
502:
503: if (self->deathtarget)
504: self->target = self->deathtarget;
505:
506: if (!self->target)
507: return;
508:
509: G_UseTargets (self, self->enemy);
510: }
511:
512:
513: //============================================================================
514:
515: qboolean monster_start (edict_t *self)
516: {
517: if (deathmatch->value)
518: {
519: G_FreeEdict (self);
520: return false;
521: }
522:
523: if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
524: {
525: self->spawnflags &= ~4;
526: self->spawnflags |= 1;
527: // gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
528: }
529:
530: if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
531: level.total_monsters++;
532:
533: self->nextthink = level.time + FRAMETIME;
534: self->svflags |= SVF_MONSTER;
535: self->s.renderfx |= RF_FRAMELERP;
536: self->takedamage = DAMAGE_AIM;
537: self->air_finished = level.time + 12;
538: self->use = monster_use;
539: self->max_health = self->health;
540: self->clipmask = MASK_MONSTERSOLID;
541:
542: self->s.skinnum = 0;
543: self->deadflag = DEAD_NO;
544: self->svflags &= ~SVF_DEADMONSTER;
545:
546: if (!self->monsterinfo.checkattack)
547: self->monsterinfo.checkattack = M_CheckAttack;
548: VectorCopy (self->s.origin, self->s.old_origin);
549:
550: if (st.item)
551: {
552: self->item = FindItemByClassname (st.item);
553: if (!self->item)
554: gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
555: }
556:
557: // randomize what frame they start on
558: if (self->monsterinfo.currentmove)
559: self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
560:
561: return true;
562: }
563:
564: void monster_start_go (edict_t *self)
565: {
566: vec3_t v;
567:
568: if (self->health <= 0)
569: return;
570:
571: // check for target to combat_point and change to combattarget
572: if (self->target)
573: {
574: qboolean notcombat;
575: qboolean fixup;
576: edict_t *target;
577:
578: target = NULL;
579: notcombat = false;
580: fixup = false;
581: while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
582: {
583: if (strcmp(target->classname, "point_combat") == 0)
584: {
585: self->combattarget = self->target;
586: fixup = true;
587: }
588: else
589: {
590: notcombat = true;
591: }
592: }
593: if (notcombat && self->combattarget)
594: gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
595: if (fixup)
596: self->target = NULL;
597: }
598:
599: // validate combattarget
600: if (self->combattarget)
601: {
602: edict_t *target;
603:
604: target = NULL;
605: while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
606: {
607: if (strcmp(target->classname, "point_combat") != 0)
608: {
609: gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
610: self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
611: self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
612: (int)target->s.origin[2]);
613: }
614: }
615: }
616:
617: if (self->target)
618: {
619: self->goalentity = self->movetarget = G_PickTarget(self->target);
620: if (!self->movetarget)
621: {
622: gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
623: self->target = NULL;
624: self->monsterinfo.pausetime = 100000000;
625: self->monsterinfo.stand (self);
626: }
627: else if (strcmp (self->movetarget->classname, "path_corner") == 0)
628: {
629: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
630: self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
631: self->monsterinfo.walk (self);
632: self->target = NULL;
633: }
634: else
635: {
636: self->goalentity = self->movetarget = NULL;
637: self->monsterinfo.pausetime = 100000000;
638: self->monsterinfo.stand (self);
639: }
640: }
641: else
642: {
643: self->monsterinfo.pausetime = 100000000;
644: self->monsterinfo.stand (self);
645: }
646:
647: self->think = monster_think;
648: self->nextthink = level.time + FRAMETIME;
649: }
650:
651:
652: void walkmonster_start_go (edict_t *self)
653: {
654: if (!(self->spawnflags & 2) && level.time < 1)
655: {
656: M_droptofloor (self);
657:
658: if (self->groundentity)
659: if (!M_walkmove (self, 0, 0))
660: gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
661: }
662:
663: if (!self->yaw_speed)
664: self->yaw_speed = 20;
665: self->viewheight = 25;
666:
667: monster_start_go (self);
668:
669: if (self->spawnflags & 2)
670: monster_triggered_start (self);
671: }
672:
673: void walkmonster_start (edict_t *self)
674: {
675: self->think = walkmonster_start_go;
676: monster_start (self);
677: }
678:
679:
680: void flymonster_start_go (edict_t *self)
681: {
682: if (!M_walkmove (self, 0, 0))
683: gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
684:
685: if (!self->yaw_speed)
686: self->yaw_speed = 10;
687: self->viewheight = 25;
688:
689: monster_start_go (self);
690:
691: if (self->spawnflags & 2)
692: monster_triggered_start (self);
693: }
694:
695:
696: void flymonster_start (edict_t *self)
697: {
698: self->flags |= FL_FLY;
699: self->think = flymonster_start_go;
700: monster_start (self);
701: }
702:
703:
704: void swimmonster_start_go (edict_t *self)
705: {
706: if (!self->yaw_speed)
707: self->yaw_speed = 10;
708: self->viewheight = 10;
709:
710: monster_start_go (self);
711:
712: if (self->spawnflags & 2)
713: monster_triggered_start (self);
714: }
715:
716: void swimmonster_start (edict_t *self)
717: {
718: self->flags |= FL_SWIM;
719: self->think = swimmonster_start_go;
720: monster_start (self);
721: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.