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