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