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