/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* g_actor.c */ #include "g_local.h" #include "m_actor.h" #define MAX_ACTOR_NAMES 8 char *actor_names[MAX_ACTOR_NAMES] = { "Hellrot", "Tokay", "Killme", "Disruptor", "Adrianator", "Rambear", "Titus", "Bitterman" }; mframe_t actor_frames_stand[] = { {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL} }; mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL}; void actor_stand(edict_t * self) { self->monsterinfo.currentmove = &actor_move_stand; /* randomize on startup */ if (level.time < 1.0) self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); } mframe_t actor_frames_walk[] = { {ai_walk, 0, NULL}, {ai_walk, 6, NULL}, {ai_walk, 10, NULL}, {ai_walk, 3, NULL}, {ai_walk, 2, NULL}, {ai_walk, 7, NULL}, {ai_walk, 10, NULL}, {ai_walk, 1, NULL}, {ai_walk, 4, NULL}, {ai_walk, 0, NULL}, {ai_walk, 0, NULL} }; mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL}; void actor_walk(edict_t * self) { self->monsterinfo.currentmove = &actor_move_walk; } mframe_t actor_frames_run[] = { {ai_run, 4, NULL}, {ai_run, 15, NULL}, {ai_run, 15, NULL}, {ai_run, 8, NULL}, {ai_run, 20, NULL}, {ai_run, 15, NULL}, {ai_run, 8, NULL}, {ai_run, 17, NULL}, {ai_run, 12, NULL}, {ai_run, -2, NULL}, {ai_run, -2, NULL}, {ai_run, -1, NULL} }; mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL}; void actor_run(edict_t * self) { if ((level.time < self->pain_debounce_time) && (!self->enemy)) { if (self->movetarget) actor_walk(self); else actor_stand(self); return; } if (self->monsterinfo.aiflags & AI_STAND_GROUND) { actor_stand(self); return; } self->monsterinfo.currentmove = &actor_move_run; } mframe_t actor_frames_pain1[] = { {ai_move, -5, NULL}, {ai_move, 4, NULL}, {ai_move, 1, NULL} }; mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run}; mframe_t actor_frames_pain2[] = { {ai_move, -4, NULL}, {ai_move, 4, NULL}, {ai_move, 0, NULL} }; mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run}; mframe_t actor_frames_pain3[] = { {ai_move, -1, NULL}, {ai_move, 1, NULL}, {ai_move, 0, NULL} }; mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run}; mframe_t actor_frames_flipoff[] = { {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL} }; mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run}; mframe_t actor_frames_taunt[] = { {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL}, {ai_turn, 0, NULL} }; mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run}; char *messages[] = { "Watch it", "#$@*&", "Idiot", "Check your targets" }; void actor_pain(edict_t * self, edict_t * other, float kick, int damage) { int n; if (self->health < (self->max_health / 2)) self->s.skinnum = 1; if (level.time < self->pain_debounce_time) return; self->pain_debounce_time = level.time + 3; /* gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0); */ if ((other->client) && (random() < 0.4)) { vec3_t v; char *name; VectorSubtract(other->s.origin, self->s.origin, v); self->ideal_yaw = vectoyaw(v); if (random() < 0.5) self->monsterinfo.currentmove = &actor_move_flipoff; else self->monsterinfo.currentmove = &actor_move_taunt; name = actor_names[(self - g_edicts) % MAX_ACTOR_NAMES]; #ifdef WITH_ACEBOT safe_cprintf #else gi.cprintf #endif (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand() % 3]); return; } n = rand() % 3; if (n == 0) self->monsterinfo.currentmove = &actor_move_pain1; else if (n == 1) self->monsterinfo.currentmove = &actor_move_pain2; else self->monsterinfo.currentmove = &actor_move_pain3; } void actorMachineGun(edict_t * self) { vec3_t start , target; vec3_t forward, right; AngleVectors(self->s.angles, forward, right, NULL); G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start); if (self->enemy) { if (self->enemy->health > 0) { VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); target[2] += self->enemy->viewheight; } else { VectorCopy(self->enemy->absmin, target); target[2] += (self->enemy->size[2] / 2); } VectorSubtract(target, start, forward); VectorNormalize(forward); } else { AngleVectors(self->s.angles, forward, NULL, NULL); } monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1); } void actor_dead(edict_t * self) { VectorSet(self->mins, -16, -16, -24); VectorSet(self->maxs, 16, 16, -8); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; self->nextthink = 0; gi.linkentity(self); } mframe_t actor_frames_death1[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, -13, NULL}, {ai_move, 14, NULL}, {ai_move, 3, NULL}, {ai_move, -2, NULL}, {ai_move, 1, NULL} }; mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead}; mframe_t actor_frames_death2[] = { {ai_move, 0, NULL}, {ai_move, 7, NULL}, {ai_move, -6, NULL}, {ai_move, -5, NULL}, {ai_move, 1, NULL}, {ai_move, 0, NULL}, {ai_move, -1, NULL}, {ai_move, -2, NULL}, {ai_move, -1, NULL}, {ai_move, -9, NULL}, {ai_move, -13, NULL}, {ai_move, -13, NULL}, {ai_move, 0, NULL} }; mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead}; void actor_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point) { int n; /* check for gib */ if (self->health <= -80) { /* * gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, * 0); */ for (n = 0; n < 2; n++) ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); for (n = 0; n < 4; n++) ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); self->deadflag = DEAD_DEAD; return; } if (self->deadflag == DEAD_DEAD) return; /* regular death */ /* gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0); */ self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; n = rand() % 2; if (n == 0) self->monsterinfo.currentmove = &actor_move_death1; else self->monsterinfo.currentmove = &actor_move_death2; } void actor_fire(edict_t * self) { actorMachineGun(self); if (level.time >= self->monsterinfo.pausetime) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; } mframe_t actor_frames_attack[] = { {ai_charge, -2, actor_fire}, {ai_charge, -2, NULL}, {ai_charge, 3, NULL}, {ai_charge, 2, NULL} }; mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run}; void actor_attack(edict_t * self) { int n; self->monsterinfo.currentmove = &actor_move_attack; n = (rand() & 15) + 3 + 7; self->monsterinfo.pausetime = level.time + n * FRAMETIME; } void actor_use(edict_t * self, edict_t * other, edict_t * activator) { vec3_t v; self->goalentity = self->movetarget = G_PickTarget(self->target); if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0)) { gi.dprintf("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); self->target = NULL; self->monsterinfo.pausetime = 100000000; self->monsterinfo.stand(self); return; } VectorSubtract(self->goalentity->s.origin, self->s.origin, v); self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); self->monsterinfo.walk(self); self->target = NULL; } /* * QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) */ void SP_misc_actor(edict_t * self) { if (deathmatch->value) { G_FreeEdict(self); return; } if (!self->targetname) { gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); G_FreeEdict(self); return; } if (!self->target) { gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); G_FreeEdict(self); return; } self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("players/male/tris.md2"); VectorSet(self->mins, -16, -16, -24); VectorSet(self->maxs, 16, 16, 32); if (!self->health) self->health = 100; self->mass = 200; self->pain = actor_pain; self->die = actor_die; self->monsterinfo.stand = actor_stand; self->monsterinfo.walk = actor_walk; self->monsterinfo.run = actor_run; self->monsterinfo.attack = actor_attack; self->monsterinfo.melee = NULL; self->monsterinfo.sight = NULL; self->monsterinfo.aiflags |= AI_GOOD_GUY; gi.linkentity(self); self->monsterinfo.currentmove = &actor_move_stand; self->monsterinfo.scale = MODEL_SCALE; walkmonster_start(self); /* * actors always start in a dormant state, they *must* be used to get * going */ self->use = actor_use; } /* * QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD * BRUTAL JUMP jump in set direction upon reaching this * target SHOOT take a single shot at the pathtarget ATTACK * ttack pathtarget until it or actor is dead * * "target" next target_actor "pathtarget" target of any action * to be taken at this point "wait" amount of time actor * should pause at this point "message" actor will "say" this to the * player * * for JUMP only: "speed" speed thrown forward (default 200) * "height" speed thrown upwards (default 200) */ void target_actor_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf) { vec3_t v; if (other->movetarget != self) return; if (other->enemy) return; other->goalentity = other->movetarget = NULL; if (self->message) { int n; edict_t *ent; for (n = 1; n <= game.maxclients; n++) { ent = &g_edicts[n]; if (!ent->inuse) continue; #ifdef WITH_ACEBOT safe_cprintf #else gi.cprintf #endif (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts) % MAX_ACTOR_NAMES], self->message); } } if (self->spawnflags & 1) { /* jump */ other->velocity[0] = self->movedir[0] * self->speed; other->velocity[1] = self->movedir[1] * self->speed; if (other->groundentity) { other->groundentity = NULL; other->velocity[2] = self->movedir[2]; gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0); } } if (self->spawnflags & 2) { /* shoot */ } else if (self->spawnflags & 4) { /* attack */ other->enemy = G_PickTarget(self->pathtarget); if (other->enemy) { other->goalentity = other->enemy; if (self->spawnflags & 32) other->monsterinfo.aiflags |= AI_BRUTAL; if (self->spawnflags & 16) { other->monsterinfo.aiflags |= AI_STAND_GROUND; actor_stand(other); } else { actor_run(other); } } } if (!(self->spawnflags & 6) && (self->pathtarget)) { char *savetarget; savetarget = self->target; self->target = self->pathtarget; G_UseTargets(self, other); self->target = savetarget; } other->movetarget = G_PickTarget(self->target); if (!other->goalentity) other->goalentity = other->movetarget; if (!other->movetarget && !other->enemy) { other->monsterinfo.pausetime = level.time + 100000000; other->monsterinfo.stand(other); } else if (other->movetarget == other->goalentity) { VectorSubtract(other->movetarget->s.origin, other->s.origin, v); other->ideal_yaw = vectoyaw(v); } } void SP_target_actor(edict_t * self) { if (!self->targetname) gi.dprintf("%s with no targetname at %s\n", self->classname, vtos(self->s.origin)); self->solid = SOLID_TRIGGER; self->touch = target_actor_touch; VectorSet(self->mins, -8, -8, -8); VectorSet(self->maxs, 8, 8, 8); self->svflags = SVF_NOCLIENT; if (self->spawnflags & 1) { if (!self->speed) self->speed = 200; if (!st.height) st.height = 200; if (self->s.angles[YAW] == 0) self->s.angles[YAW] = 360; G_SetMovedir(self->s.angles, self->movedir); self->movedir[2] = st.height; } gi.linkentity(self); }