1 /*
2 * Copyright (C) 1997-2001 Id Software, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.
12 *
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59
17 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 *
19 */
20 /* g_actor.c */
21
22 #include "g_local.h"
23 #include "m_actor.h"
24
25 #define MAX_ACTOR_NAMES 8
26 char *actor_names[MAX_ACTOR_NAMES] =
27 {
28 "Hellrot",
29 "Tokay",
30 "Killme",
31 "Disruptor",
32 "Adrianator",
33 "Rambear",
34 "Titus",
35 "Bitterman"
36 };
37
38
39 mframe_t actor_frames_stand[] =
40 {
41 {ai_stand, 0, NULL},
42 {ai_stand, 0, NULL},
43 {ai_stand, 0, NULL},
44 {ai_stand, 0, NULL},
45 {ai_stand, 0, NULL},
46 {ai_stand, 0, NULL},
47 {ai_stand, 0, NULL},
48 {ai_stand, 0, NULL},
49 {ai_stand, 0, NULL},
50 {ai_stand, 0, NULL},
51
52 {ai_stand, 0, NULL},
53 {ai_stand, 0, NULL},
54 {ai_stand, 0, NULL},
55 {ai_stand, 0, NULL},
56 {ai_stand, 0, NULL},
57 {ai_stand, 0, NULL},
58 {ai_stand, 0, NULL},
59 {ai_stand, 0, NULL},
60 {ai_stand, 0, NULL},
61 {ai_stand, 0, NULL},
62
63 {ai_stand, 0, NULL},
64 {ai_stand, 0, NULL},
65 {ai_stand, 0, NULL},
66 {ai_stand, 0, NULL},
67 {ai_stand, 0, NULL},
68 {ai_stand, 0, NULL},
69 {ai_stand, 0, NULL},
70 {ai_stand, 0, NULL},
71 {ai_stand, 0, NULL},
72 {ai_stand, 0, NULL},
73
74 {ai_stand, 0, NULL},
75 {ai_stand, 0, NULL},
76 {ai_stand, 0, NULL},
77 {ai_stand, 0, NULL},
78 {ai_stand, 0, NULL},
79 {ai_stand, 0, NULL},
80 {ai_stand, 0, NULL},
81 {ai_stand, 0, NULL},
82 {ai_stand, 0, NULL},
83 {ai_stand, 0, NULL}
84 };
85 mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL};
86
87 void
actor_stand(edict_t * self)88 actor_stand(edict_t * self)
89 {
90 self->monsterinfo.currentmove = &actor_move_stand;
91
92 /* randomize on startup */
93 if (level.time < 1.0)
94 self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
95 }
96
97
98 mframe_t actor_frames_walk[] =
99 {
100 {ai_walk, 0, NULL},
101 {ai_walk, 6, NULL},
102 {ai_walk, 10, NULL},
103 {ai_walk, 3, NULL},
104 {ai_walk, 2, NULL},
105 {ai_walk, 7, NULL},
106 {ai_walk, 10, NULL},
107 {ai_walk, 1, NULL},
108 {ai_walk, 4, NULL},
109 {ai_walk, 0, NULL},
110 {ai_walk, 0, NULL}
111 };
112 mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL};
113
114 void
actor_walk(edict_t * self)115 actor_walk(edict_t * self)
116 {
117 self->monsterinfo.currentmove = &actor_move_walk;
118 }
119
120
121 mframe_t actor_frames_run[] =
122 {
123 {ai_run, 4, NULL},
124 {ai_run, 15, NULL},
125 {ai_run, 15, NULL},
126 {ai_run, 8, NULL},
127 {ai_run, 20, NULL},
128 {ai_run, 15, NULL},
129 {ai_run, 8, NULL},
130 {ai_run, 17, NULL},
131 {ai_run, 12, NULL},
132 {ai_run, -2, NULL},
133 {ai_run, -2, NULL},
134 {ai_run, -1, NULL}
135 };
136 mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL};
137
138 void
actor_run(edict_t * self)139 actor_run(edict_t * self)
140 {
141 if ((level.time < self->pain_debounce_time) && (!self->enemy)) {
142 if (self->movetarget)
143 actor_walk(self);
144 else
145 actor_stand(self);
146 return;
147 }
148 if (self->monsterinfo.aiflags & AI_STAND_GROUND) {
149 actor_stand(self);
150 return;
151 }
152 self->monsterinfo.currentmove = &actor_move_run;
153 }
154
155
156 mframe_t actor_frames_pain1[] =
157 {
158 {ai_move, -5, NULL},
159 {ai_move, 4, NULL},
160 {ai_move, 1, NULL}
161 };
162 mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run};
163
164 mframe_t actor_frames_pain2[] =
165 {
166 {ai_move, -4, NULL},
167 {ai_move, 4, NULL},
168 {ai_move, 0, NULL}
169 };
170 mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run};
171
172 mframe_t actor_frames_pain3[] =
173 {
174 {ai_move, -1, NULL},
175 {ai_move, 1, NULL},
176 {ai_move, 0, NULL}
177 };
178 mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run};
179
180 mframe_t actor_frames_flipoff[] =
181 {
182 {ai_turn, 0, NULL},
183 {ai_turn, 0, NULL},
184 {ai_turn, 0, NULL},
185 {ai_turn, 0, NULL},
186 {ai_turn, 0, NULL},
187 {ai_turn, 0, NULL},
188 {ai_turn, 0, NULL},
189 {ai_turn, 0, NULL},
190 {ai_turn, 0, NULL},
191 {ai_turn, 0, NULL},
192 {ai_turn, 0, NULL},
193 {ai_turn, 0, NULL},
194 {ai_turn, 0, NULL},
195 {ai_turn, 0, NULL}
196 };
197 mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run};
198
199 mframe_t actor_frames_taunt[] =
200 {
201 {ai_turn, 0, NULL},
202 {ai_turn, 0, NULL},
203 {ai_turn, 0, NULL},
204 {ai_turn, 0, NULL},
205 {ai_turn, 0, NULL},
206 {ai_turn, 0, NULL},
207 {ai_turn, 0, NULL},
208 {ai_turn, 0, NULL},
209 {ai_turn, 0, NULL},
210 {ai_turn, 0, NULL},
211 {ai_turn, 0, NULL},
212 {ai_turn, 0, NULL},
213 {ai_turn, 0, NULL},
214 {ai_turn, 0, NULL},
215 {ai_turn, 0, NULL},
216 {ai_turn, 0, NULL},
217 {ai_turn, 0, NULL}
218 };
219 mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run};
220
221 char *messages[] =
222 {
223 "Watch it",
224 "#$@*&",
225 "Idiot",
226 "Check your targets"
227 };
228
229 void
actor_pain(edict_t * self,edict_t * other,float kick,int damage)230 actor_pain(edict_t * self, edict_t * other, float kick, int damage)
231 {
232 int n;
233
234 if (self->health < (self->max_health / 2))
235 self->s.skinnum = 1;
236
237 if (level.time < self->pain_debounce_time)
238 return;
239
240 self->pain_debounce_time = level.time + 3;
241 /* gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); */
242
243 if ((other->client) && (random() < 0.4)) {
244 vec3_t v;
245 char *name;
246
247 VectorSubtract(other->s.origin, self->s.origin, v);
248 self->ideal_yaw = vectoyaw(v);
249 if (random() < 0.5)
250 self->monsterinfo.currentmove = &actor_move_flipoff;
251 else
252 self->monsterinfo.currentmove = &actor_move_taunt;
253 name = actor_names[(self - g_edicts) % MAX_ACTOR_NAMES];
254 #ifdef WITH_ACEBOT
255 safe_cprintf
256 #else
257 gi.cprintf
258 #endif
259 (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand() % 3]);
260 return;
261 }
262 n = rand() % 3;
263 if (n == 0)
264 self->monsterinfo.currentmove = &actor_move_pain1;
265 else if (n == 1)
266 self->monsterinfo.currentmove = &actor_move_pain2;
267 else
268 self->monsterinfo.currentmove = &actor_move_pain3;
269 }
270
271
272 void
actorMachineGun(edict_t * self)273 actorMachineGun(edict_t * self)
274 {
275 vec3_t start , target;
276 vec3_t forward, right;
277
278 AngleVectors(self->s.angles, forward, right, NULL);
279 G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start);
280 if (self->enemy) {
281 if (self->enemy->health > 0) {
282 VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
283 target[2] += self->enemy->viewheight;
284 } else {
285 VectorCopy(self->enemy->absmin, target);
286 target[2] += (self->enemy->size[2] / 2);
287 }
288 VectorSubtract(target, start, forward);
289 VectorNormalize(forward);
290 } else {
291 AngleVectors(self->s.angles, forward, NULL, NULL);
292 }
293 monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1);
294 }
295
296
297 void
actor_dead(edict_t * self)298 actor_dead(edict_t * self)
299 {
300 VectorSet(self->mins, -16, -16, -24);
301 VectorSet(self->maxs, 16, 16, -8);
302 self->movetype = MOVETYPE_TOSS;
303 self->svflags |= SVF_DEADMONSTER;
304 self->nextthink = 0;
305 gi.linkentity(self);
306 }
307
308 mframe_t actor_frames_death1[] =
309 {
310 {ai_move, 0, NULL},
311 {ai_move, 0, NULL},
312 {ai_move, -13, NULL},
313 {ai_move, 14, NULL},
314 {ai_move, 3, NULL},
315 {ai_move, -2, NULL},
316 {ai_move, 1, NULL}
317 };
318 mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead};
319
320 mframe_t actor_frames_death2[] =
321 {
322 {ai_move, 0, NULL},
323 {ai_move, 7, NULL},
324 {ai_move, -6, NULL},
325 {ai_move, -5, NULL},
326 {ai_move, 1, NULL},
327 {ai_move, 0, NULL},
328 {ai_move, -1, NULL},
329 {ai_move, -2, NULL},
330 {ai_move, -1, NULL},
331 {ai_move, -9, NULL},
332 {ai_move, -13, NULL},
333 {ai_move, -13, NULL},
334 {ai_move, 0, NULL}
335 };
336 mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead};
337
338 void
actor_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)339 actor_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
340 {
341 int n;
342
343 /* check for gib */
344 if (self->health <= -80) {
345
346 /*
347 * gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM,
348 * 0);
349 */
350 for (n = 0; n < 2; n++)
351 ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
352 for (n = 0; n < 4; n++)
353 ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
354 ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
355 self->deadflag = DEAD_DEAD;
356 return;
357 }
358 if (self->deadflag == DEAD_DEAD)
359 return;
360
361 /* regular death */
362 /* gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); */
363 self->deadflag = DEAD_DEAD;
364 self->takedamage = DAMAGE_YES;
365
366 n = rand() % 2;
367 if (n == 0)
368 self->monsterinfo.currentmove = &actor_move_death1;
369 else
370 self->monsterinfo.currentmove = &actor_move_death2;
371 }
372
373
374 void
actor_fire(edict_t * self)375 actor_fire(edict_t * self)
376 {
377 actorMachineGun(self);
378
379 if (level.time >= self->monsterinfo.pausetime)
380 self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
381 else
382 self->monsterinfo.aiflags |= AI_HOLD_FRAME;
383 }
384
385 mframe_t actor_frames_attack[] =
386 {
387 {ai_charge, -2, actor_fire},
388 {ai_charge, -2, NULL},
389 {ai_charge, 3, NULL},
390 {ai_charge, 2, NULL}
391 };
392 mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run};
393
394 void
actor_attack(edict_t * self)395 actor_attack(edict_t * self)
396 {
397 int n;
398
399 self->monsterinfo.currentmove = &actor_move_attack;
400 n = (rand() & 15) + 3 + 7;
401 self->monsterinfo.pausetime = level.time + n * FRAMETIME;
402 }
403
404
405 void
actor_use(edict_t * self,edict_t * other,edict_t * activator)406 actor_use(edict_t * self, edict_t * other, edict_t * activator)
407 {
408 vec3_t v;
409
410 self->goalentity = self->movetarget = G_PickTarget(self->target);
411 if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) {
412 gi.dprintf("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
413 self->target = NULL;
414 self->monsterinfo.pausetime = 100000000;
415 self->monsterinfo.stand(self);
416 return;
417 }
418 VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
419 self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
420 self->monsterinfo.walk(self);
421 self->target = NULL;
422 }
423
424
425 /*
426 * QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32)
427 */
428
429 void
SP_misc_actor(edict_t * self)430 SP_misc_actor(edict_t * self)
431 {
432 if (deathmatch->value) {
433 G_FreeEdict(self);
434 return;
435 }
436 if (!self->targetname) {
437 gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
438 G_FreeEdict(self);
439 return;
440 }
441 if (!self->target) {
442 gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
443 G_FreeEdict(self);
444 return;
445 }
446 self->movetype = MOVETYPE_STEP;
447 self->solid = SOLID_BBOX;
448 self->s.modelindex = gi.modelindex("players/male/tris.md2");
449 VectorSet(self->mins, -16, -16, -24);
450 VectorSet(self->maxs, 16, 16, 32);
451
452 if (!self->health)
453 self->health = 100;
454 self->mass = 200;
455
456 self->pain = actor_pain;
457 self->die = actor_die;
458
459 self->monsterinfo.stand = actor_stand;
460 self->monsterinfo.walk = actor_walk;
461 self->monsterinfo.run = actor_run;
462 self->monsterinfo.attack = actor_attack;
463 self->monsterinfo.melee = NULL;
464 self->monsterinfo.sight = NULL;
465
466 self->monsterinfo.aiflags |= AI_GOOD_GUY;
467
468 gi.linkentity(self);
469
470 self->monsterinfo.currentmove = &actor_move_stand;
471 self->monsterinfo.scale = MODEL_SCALE;
472
473 walkmonster_start(self);
474
475 /*
476 * actors always start in a dormant state, they *must* be used to get
477 * going
478 */
479 self->use = actor_use;
480 }
481
482
483 /*
484 * QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD
485 * BRUTAL JUMP jump in set direction upon reaching this
486 * target SHOOT take a single shot at the pathtarget ATTACK
487 * ttack pathtarget until it or actor is dead
488 *
489 * "target" next target_actor "pathtarget" target of any action
490 * to be taken at this point "wait" amount of time actor
491 * should pause at this point "message" actor will "say" this to the
492 * player
493 *
494 * for JUMP only: "speed" speed thrown forward (default 200)
495 * "height" speed thrown upwards (default 200)
496 */
497
498 void
target_actor_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)499 target_actor_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
500 {
501 vec3_t v;
502
503 if (other->movetarget != self)
504 return;
505
506 if (other->enemy)
507 return;
508
509 other->goalentity = other->movetarget = NULL;
510
511 if (self->message) {
512 int n;
513 edict_t *ent;
514
515 for (n = 1; n <= game.maxclients; n++) {
516 ent = &g_edicts[n];
517 if (!ent->inuse)
518 continue;
519 #ifdef WITH_ACEBOT
520 safe_cprintf
521 #else
522 gi.cprintf
523 #endif
524 (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts) % MAX_ACTOR_NAMES], self->message);
525 }
526 }
527 if (self->spawnflags & 1) { /* jump */
528 other->velocity[0] = self->movedir[0] * self->speed;
529 other->velocity[1] = self->movedir[1] * self->speed;
530
531 if (other->groundentity) {
532 other->groundentity = NULL;
533 other->velocity[2] = self->movedir[2];
534 gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0);
535 }
536 }
537 if (self->spawnflags & 2) { /* shoot */
538 } else if (self->spawnflags & 4) { /* attack */
539 other->enemy = G_PickTarget(self->pathtarget);
540 if (other->enemy) {
541 other->goalentity = other->enemy;
542 if (self->spawnflags & 32)
543 other->monsterinfo.aiflags |= AI_BRUTAL;
544 if (self->spawnflags & 16) {
545 other->monsterinfo.aiflags |= AI_STAND_GROUND;
546 actor_stand(other);
547 } else {
548 actor_run(other);
549 }
550 }
551 }
552 if (!(self->spawnflags & 6) && (self->pathtarget)) {
553 char *savetarget;
554
555 savetarget = self->target;
556 self->target = self->pathtarget;
557 G_UseTargets(self, other);
558 self->target = savetarget;
559 }
560 other->movetarget = G_PickTarget(self->target);
561
562 if (!other->goalentity)
563 other->goalentity = other->movetarget;
564
565 if (!other->movetarget && !other->enemy) {
566 other->monsterinfo.pausetime = level.time + 100000000;
567 other->monsterinfo.stand(other);
568 } else if (other->movetarget == other->goalentity) {
569 VectorSubtract(other->movetarget->s.origin, other->s.origin, v);
570 other->ideal_yaw = vectoyaw(v);
571 }
572 }
573
574 void
SP_target_actor(edict_t * self)575 SP_target_actor(edict_t * self)
576 {
577 if (!self->targetname)
578 gi.dprintf("%s with no targetname at %s\n", self->classname, vtos(self->s.origin));
579
580 self->solid = SOLID_TRIGGER;
581 self->touch = target_actor_touch;
582 VectorSet(self->mins, -8, -8, -8);
583 VectorSet(self->maxs, 8, 8, 8);
584 self->svflags = SVF_NOCLIENT;
585
586 if (self->spawnflags & 1) {
587 if (!self->speed)
588 self->speed = 200;
589 if (!st.height)
590 st.height = 200;
591 if (self->s.angles[YAW] == 0)
592 self->s.angles[YAW] = 360;
593 G_SetMovedir(self->s.angles, self->movedir);
594 self->movedir[2] = st.height;
595 }
596 gi.linkentity(self);
597 }
598