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